木灵鱼儿
阅读:1406
vue-i18n 的使用方式
安装
vue2版需要安装8.x版本的,9.x的是vue3版本
使用上大同小异。
vue2安装:
yarn add vue-i18n@8
vue3安装:
yarn add vue-i18n
封装
官方虽然支持很不错的用法,但是自定义处理是难免的。
vue3
文件目录结构
├─ src
│ ├─ language
│ │ ├─ lang
│ │ │ ├─ en.json
│ │ │ └─ zh.json
│ │ ├─ core
│ │ │ ├─ i18n.ts
│ │ │ ├─ customization.ts
│ │ │ └─ language.ts
│ │ ├─ index.ts
│ │ └─ types.ts
│ ├─ main.ts
lang目录中存放所有翻译的文件,语言文件建议就取前两位的小写,因为通过浏览器navigator.language
取得的语言文件会有很多变种:en-US、en-AU...;这并不利于我们判断,所以我的做法就是取其前两位的小写。
core为核心文件:
language.ts
封装的自定义语言处理类,比如首次加载语言,异步加载语言,切换语言customization.ts
自定义多语言处理,对$t
的二次封装i18n.ts
实例化处理,频繁的修改在此文件
其他:
index.ts
入口文件,包含i18n注册,导出一些自定义语言处理函数types.ts
一些类型声明
本来我是想用用9版本的i18n的Composition API
写法的,但是考虑到需要适配两个vue的版本,所以统一采用低版本的写法,有兴趣可以查看官方的这种写法:《Composition API》
下面是核心代码了!
language.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-30 13:59:14
* @LastEditTime: 2022-05-30 16:38:59
* @LastEditors: mulingyuer
* @Description:
* @FilePath: \casino-xian\src\language\core\language.ts
* 怎么可能会有bug!!!
*/
import { I18n } from "vue-i18n";
import { getItem, setItem } from "@/utils";
import { nextTick } from "vue";
import { LanguageOptions } from "../types";
class Language {
private i18n: I18n; //i18n实例
private defaultLocale = "en"; //默认语言
private SUPPORT_LOCALES: Array<string> = []; //支持的语言数组
private localKey = "language"; //本地持久化key
private loadLocale: Array<string> = []; //已加载的语言
constructor(i18n: I18n, options: LanguageOptions) {
this.i18n = i18n;
if (options) {
this.initOptions(options);
}
}
private initOptions(options: LanguageOptions): void {
const { defaultLocale, supportLocales, localKey, loadLocale } = options;
if (typeof defaultLocale === "string" && defaultLocale.trim() !== "") {
this.defaultLocale = defaultLocale;
}
if (Array.isArray(supportLocales)) {
this.SUPPORT_LOCALES = supportLocales;
}
if (this.SUPPORT_LOCALES.length === 0) {
this.SUPPORT_LOCALES.push(this.defaultLocale);
}
if (typeof localKey === "string" && localKey.trim() !== "") {
this.localKey = localKey;
}
if (Array.isArray(loadLocale)) {
this.loadLocale = loadLocale;
}
}
/**
* @description: 设置语言
* @param {string} locale 语言
* @Date: 2022-05-16 16:48:21
* @Author: mulingyuer
*/
private setI18nLanguage(locale: string) {
if (this.i18n.mode === "legacy") {
this.i18n.global.locale = locale;
} else {
(this.i18n.global.locale as any).value = locale;
}
document.querySelector("html")?.setAttribute("lang", locale);
setItem(this.localKey, locale);
}
/**
* @description: 异步加载语言并使用
* @param {string} locale 语言
* @Date: 2022-05-16 15:25:55
* @Author: mulingyuer
*/
public async loadLocaleMessages(locale: string): Promise<void> {
try {
//是否已经加载了
if (this.loadLocale.includes(locale)) {
this.setI18nLanguage(locale);
return nextTick();
}
//不合法的语言
if (!this.SUPPORT_LOCALES.includes(locale)) {
//使用默认的语言
return this.loadLocaleMessages(this.defaultLocale);
}
//加载语言
const messages = await import(/* webpackChunkName: "locale-[request]" */ `../lang/${locale}.json`);
//设置i18n的messages
this.i18n.global.setLocaleMessage(locale, messages.default);
this.loadLocale.push(locale);
this.setI18nLanguage(locale);
return nextTick();
} catch (error) {
console.error(`获取语言文件失败:${locale}`, error);
return nextTick();
}
}
/**
* @description: 首次加载语言文件
* @Date: 2022-05-16 17:18:33
* @Author: mulingyuer
*/
public async firstLoadI18nLocale(specifyLang?: string) {
const htmlLang = this.getDocumentLanguage();
const localLang = getItem(this.localKey);
const systemLang = this.getStystemLanguage();
const lang = specifyLang || localLang || systemLang || htmlLang || this.defaultLocale;
await this.loadLocaleMessages(lang);
this.firstLoadI18nLocale = () => Promise.resolve();
}
/**
* @description: 获取系统语言
* @Date: 2022-05-18 19:39:48
* @Author: mulingyuer
*/
private getStystemLanguage() {
const lang = navigator.language;
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
/**
* @description: 获取文档语言
* @Date: 2022-05-19 10:54:51
* @Author: mulingyuer
*/
private getDocumentLanguage() {
const lang = document.documentElement.getAttribute("lang");
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
}
export default Language;
types.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 14:18:51
* @LastEditTime: 2022-05-30 15:36:33
* @LastEditors: mulingyuer
* @Description: 类型声明
* @FilePath: \casino-xian\src\language\types.ts
* 怎么可能会有bug!!!
*/
//api中的info字段
export type Info = string | null | undefined;
//language类的options
export type LanguageOptions = {
defaultLocale?: string;
supportLocales: Array<string>;
localKey?: string;
loadLocale?: Array<string>;
};
getItem, setItem
是对于localStorage
的封装,可以看成是:localStorage.getItem,localStorage.setItem
customization.ts
import i18n from "./i18n";
const $t = i18n.global.t;
/**
* @description: 多语言保底输出 tOriginalOutput
* @param message 文本
* @Date: 2022-05-19 19:36:23
* @Author: mulingyuer
*/
export function $toop(message: string, backMessage: any = "") {
const langMessage = $t(message);
//如果没有对应的文本,用最后一个.后面的内容作为翻译文本
if (langMessage === message) {
return backMessage;
}
return langMessage;
}
i18n.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-19 17:21:29
* @LastEditTime: 2022-05-30 16:34:40
* @LastEditors: mulingyuer
* @Description: i18n
* @FilePath: \casino-xian\src\language\core\i18n.ts
* 怎么可能会有bug!!!
*/
import { createI18n } from "vue-i18n";
import Language from "./language";
//创建实例
const i18n = createI18n({
// locale: getItem("language") || defaultLocale, //读取本地存储的语言
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
// fallbackLocale: "en", //回退用的语言
messages: {
// en: require("../lang/en.json"),
},
});
const language = new Language(i18n, {
supportLocales: ["en", "zh"],
defaultLocale: "en",
});
export { language };
export default i18n;
index.ts
/*
* @Author: mulingyuer
* @Date: 2022-03-04 11:30:23
* @LastEditTime: 2022-05-30 16:34:59
* @LastEditors: mulingyuer
* @Description: 多语言
* @FilePath: \casino-xian\src\language\index.ts
* 怎么可能会有bug!!!
*/
import i18n, { language } from "./core/i18n";
import { $toop } from "./core/customization";
//方便的获取i18n的方法
const $t = i18n.global.t;
export { language, $toop, $t };
export default {
install(app: any) {
//全局注册自定义方法
app.config.globalProperties.$toop = $toop;
//注册i18n
app.use(i18n);
},
};
基本处理都在这里了,下面是使用!
vue3使用
main.ts中注册
import { createApp } from "vue";
import App from "./App.vue";
import VueI18n from "./language";
createApp(App).use(VueI18n);
路由守卫中使用
因为语言采用异步加载的方式引入,所以需要保证在页面首次进入时加载了一份语言包,如果在i18n.ts
初始化i18n实例时默认在messages中require引入了一份语言,其实就可以考虑不用再路由守卫中处理首次加载的问题,因为你一定会有一份语言包的。
import { language } from "@/language";
router.beforeEach(async (to, from, next) => {
//首次加载语言文件
await language.firstLoadI18nLocale();
next();
});
切换语言
import { language } from "@/language";
language.loadLocaleMessages("zh");
基本使用就是这样了,后续可以将支持的语言导出,作为在vue中for循环遍历的数组啥的。
类封装的是通用的逻辑,如果业务变化需要改动,尽量改动i18n.ts
中的逻辑。
vue2封装
vue2版大同小异,需要注意的是i18n的使用,以及注册挂载的问题。目录结构也是一样的。
language.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-30 13:59:14
* @LastEditTime: 2022-05-30 16:39:40
* @LastEditors: mulingyuer
* @Description:
* @FilePath: \casino-vue2.0\src\language\core\language.ts
* 怎么可能会有bug!!!
*/
import { IVueI18n } from "vue-i18n";
import { getItem, setItem } from "@/utils";
import Vue from "vue";
import { LanguageOptions } from "../types";
class Language {
private i18n: IVueI18n; //i18n实例
private defaultLocale = "en"; //默认语言
private SUPPORT_LOCALES: Array<string> = []; //支持的语言数组
private localKey = "language"; //本地持久化key
private loadLocale: Array<string> = []; //已加载的语言
constructor(i18n: IVueI18n, options: LanguageOptions) {
this.i18n = i18n;
if (options) {
this.initOptions(options);
}
}
private initOptions(options: LanguageOptions): void {
const { defaultLocale, supportLocales, localKey, loadLocale } = options;
if (typeof defaultLocale === "string" && defaultLocale.trim() !== "") {
this.defaultLocale = defaultLocale;
}
if (Array.isArray(supportLocales)) {
this.SUPPORT_LOCALES = supportLocales;
}
if (this.SUPPORT_LOCALES.length === 0) {
this.SUPPORT_LOCALES.push(this.defaultLocale);
}
if (typeof localKey === "string" && localKey.trim() !== "") {
this.localKey = localKey;
}
if (Array.isArray(loadLocale)) {
this.loadLocale = loadLocale;
}
}
/**
* @description: 设置语言
* @param {string} locale 语言
* @Date: 2022-05-16 16:48:21
* @Author: mulingyuer
*/
private setI18nLanguage(locale: string) {
this.i18n.locale = locale;
document.querySelector("html")?.setAttribute("lang", locale);
setItem(this.localKey, locale);
}
/**
* @description: 异步加载语言并使用
* @param {string} locale 语言
* @Date: 2022-05-16 15:25:55
* @Author: mulingyuer
*/
public async loadLocaleMessages(locale: string): Promise<void> {
try {
//是否已经加载了
if (this.loadLocale.includes(locale)) {
this.setI18nLanguage(locale);
return Vue.nextTick();
}
//不合法的语言
if (!this.SUPPORT_LOCALES.includes(locale)) {
//使用默认的语言
return this.loadLocaleMessages(this.defaultLocale);
}
//加载语言
const messages = await import(/* webpackChunkName: "locale-[request]" */ `../lang/${locale}.json`);
//设置i18n的messages
this.i18n.setLocaleMessage(locale, messages.default);
this.loadLocale.push(locale);
this.setI18nLanguage(locale);
return Vue.nextTick();
} catch (error) {
console.error(`获取语言文件失败:${locale}`, error);
return Vue.nextTick();
}
}
/**
* @description: 首次加载语言文件
* @Date: 2022-05-16 17:18:33
* @Author: mulingyuer
*/
public async firstLoadI18nLocale(specifyLang?: string) {
const htmlLang = this.getDocumentLanguage();
const localLang = getItem(this.localKey);
const systemLang = this.getStystemLanguage();
const lang = specifyLang || localLang || systemLang || htmlLang || this.defaultLocale;
await this.loadLocaleMessages(lang);
this.firstLoadI18nLocale = () => Promise.resolve();
}
/**
* @description: 获取系统语言
* @Date: 2022-05-18 19:39:48
* @Author: mulingyuer
*/
private getStystemLanguage() {
const lang = navigator.language;
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
/**
* @description: 获取文档语言
* @Date: 2022-05-19 10:54:51
* @Author: mulingyuer
*/
private getDocumentLanguage() {
const lang = document.documentElement.getAttribute("lang");
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
}
export default Language;
types.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 14:18:51
* @LastEditTime: 2022-05-30 16:06:38
* @LastEditors: mulingyuer
* @Description: 类型声明
* @FilePath: \casino-vue2.0\src\language\types.ts
* 怎么可能会有bug!!!
*/
//api中的info字段
export type Info = string | null | undefined;
//language类的options
export type LanguageOptions = {
defaultLocale?: string;
supportLocales: Array<string>;
localKey?: string;
loadLocale?: Array<string>;
};
customization.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-19 17:24:44
* @LastEditTime: 2022-05-30 16:44:18
* @LastEditors: mulingyuer
* @Description: 自定义转换方法
* @FilePath: \casino-vue2.0\src\language\core\customization.ts
* 怎么可能会有bug!!!
*/
import i18n from "./i18n";
const $t = i18n.t.bind(i18n);
/**
* @description: 多语言保底输出 tOriginalOutput
* @param message 文本
* @Date: 2022-05-19 19:36:23
* @Author: mulingyuer
*/
export function $toop(message: string, backMessage: any = "") {
const langMessage = $t(message);
//如果没有对应的文本,用最后一个.后面的内容作为翻译文本
if (langMessage === message) {
return backMessage;
}
return langMessage;
}
i18n.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 15:55:00
* @LastEditTime: 2022-05-30 16:44:13
* @LastEditors: mulingyuer
* @Description:i18n
* @FilePath: \casino-vue2.0\src\language\core\i18n.ts
* 怎么可能会有bug!!!
*/
import Vue from "vue";
import VueI18n from "vue-i18n";
import Language from "./language";
Vue.use(VueI18n);
//创建实例
const i18n = new VueI18n({
silentTranslationWarn: true,
// locale: defaultLocale,
// fallbackLocale: "en", //回退用的语言
// messages: {
// en: require("../lang/en.json"),
// },
});
const language = new Language(i18n, {
supportLocales: ["en", "zh"],
defaultLocale: "en",
});
export { language };
export default i18n;
因为vue3版和vue2的ts类型声明不一样,你会发现在实例化i18n
的时候,部分参数不同。
index.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 15:55:00
* @LastEditTime: 2022-05-30 16:44:35
* @LastEditors: mulingyuer
* @Description: 多语言
* @FilePath: \casino-vue2.0\src\language\index.ts
* 怎么可能会有bug!!!
*/
import i18n, { language } from "./core/i18n";
import { $toop } from "./core/customization";
//方便的获取i18n的方法
const $t = i18n.t.bind(i18n);
export { language, $toop, $t };
export const installI18n = {
install(app: any) {
//全局注册方法
app.prototype.$toop = $toop;
},
};
export default i18n;
细心点你会发现,在vue3版中,我们从i18n
实例上获取t
是直接拿到的函数,而在vue2版需要进行绑定this指向为i18n
实例自身。
应该是vue3版改动较大,支持这种用法,也可能是凑巧,如果以后不行了,可以尝试也进行一次bind。
vue2版使用
main.ts中注册
import Vue from "vue";
import App from "./App.vue";
import i18n, { installI18n } from "@/language";
Vue.use(installI18n);
new Vue({
i18n,
render: (h) => h(App),
}).$mount("#app");
路由守卫中使用
因为语言采用异步加载的方式引入,所以需要保证在页面首次进入时加载了一份语言包,如果在i18n.ts
初始化i18n实例时默认在messages中require引入了一份语言,其实就可以考虑不用再路由守卫中处理首次加载的问题,因为你一定会有一份语言包的。
import { language } from "@/language";
router.beforeEach(async (to, from, next) => {
//首次加载语言文件
await language.firstLoadI18nLocale();
next();
});
切换语言
import { language } from "@/language";
language.loadLocaleMessages("zh");
基本使用就是这样了,后续可以将支持的语言导出,作为在vue中for循环遍历的数组啥的。
类封装的是通用的逻辑,如果业务变化需要改动,尽量改动i18n.ts
中的逻辑。
自定义语言处理
在上面的代码封装中,我们封装了一个$toop
的函数,这个函数会挂载到vue的全局。
为此,我们可以直接在template
模板语法中使用。
<template>
<div>
{{ $toop("xxx.xxx.xx","没有对应的值,采用我这个回退值") }}
</div>
</template>
这个方法的考虑:
语言包文件为了更好的语义化和维护,肯定会有层级嵌套的,即便他只是一个json文件,他可能会是这种结构:
{
"a": {
"b": {
"c" : "value"
}
}
}
但是有时候你不能保证这个层级下一定会有你的值,比如这个值可能是一个动态的后端返的值,你的语言包只存在旧的数据,新的数据没有这个,那么$t
它会默认将你的key字符串原样返回,显然这并不能很好的回退,所以我自定义了$toop
,我会使用第二个参数作为回退的值。
更多的自定义可以自己去尝试。
也正是因为我需要全局注册自定义方法,所以index.ts
看上去好像有些复杂。
vue-i18n官网
vue2版:vue-i18n
vue3版:vue-i18n
一些注意事项
- vue-i18n的语言包val值只能是字符串,数字会被视为不存在对应的翻译,如:
{
"name": 1
}
这种会被视为不存在翻译,仔细一想也是,多语言本来就是翻译字符串的,这么做好像也没错,但是我们平时使用还是得注意下。
- 关闭警告
在new i18n实例的时候配置一下即可:
//创建实例
const i18n = createI18n({
// locale: getItem("language") || defaultLocale, //读取本地存储的语言
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
// fallbackLocale: "en", //回退用的语言
messages: {
// en: require("../lang/en.json"),
},
silentTranslationWarn: true, //是否显示警告
silentFallbackWarn: true, //是否显示回退警告
});
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

相关推荐
实现一个点击空白区域关闭显示的自定义指令
typescriptimport type { DirectiveBinding } from "vue"; export default { bind(el: HTMLElement, binding: DirectiveBinding<Function>) { //声明一个给document绑定的事件 function documentClick(event: Event) { const target = event.target as unknown as Node; if (el.contains(t...
使用vue2.7的一些踩坑事项
eslint校验的一些问题(暂时无解)在初始化项目时勾选了eslint校验之后,升级vue 2.7版本后,eslint-plugin-vue这个插件需要升级到9+版本,我目前使用的版本是:"eslint-plugin-vue": "^9.4.0"具体的一些可以参考官方提供的2.7升级指南:2.7日志官方居然把这个写在了变更日志里面,按道理最好官方的文档上也有一份说明的,但是目前没有。虽然eslint的依赖更新到新版后确实解决了一些项目启动报错的问题,但是有时候我们的SFC单文件组件开发的时候,template中的一些变量绑定还是会出现波浪线警告,这...
正确使用vue3的ts类型声明
前言使用了ts最头疼的是什么,除了类型声明应该没有第二家了,那么在vue3中如何正确的声明ts类型,代表着我们踏出了认识vue3的第一步,这非常重要,所以为此水个文章,分享给有需要的人。Volar 插件一开始我对于Volar并没有太大的需要,因为一直使用的Vetur,而且这个插件刚出来时并不完善,各种视频up讲的那个一键分屏功能其实也并不好用,虽然是个很有意思的东西,但是没有那种非要使用它的点,所以当时的我怀着这么一个疑问?为什么要用Volar ?现在我就通过两张图告诉你,它有多香!我们在template里面写代码,绑定变量最烦的是什么,就是我们写了个对象,但是忘了它的属性有哪些啊,使用...

关于给css自动添加浏览器前缀
前言为了兼容之前的旧浏览器版本,特别是安卓4.4这种低版本,transform是一定得增加浏览器前缀的,但是我在项目中遇到了设置无效的问题,极度蛋疼,下面是我的解决教程,当然没这个问题,看这篇文章,相信你对如果给项目增加浏览器前缀,会有很充分的认识。教程给css增加浏览器前缀,业界的做法就是使用postcss,目前webpack与vue cli他们需要安装的依赖略有不同:webpack:pnpm i postcss postcss-loader autoprefixer -D添加对应的rules{ test: /.s?css$/, use: [ ...省略...
vuex 动态注册和卸载模块
概述一般情况下,我们的vuex数据都是静态的,store在首次初始化后数据的格式就定好了,在日常使用中也确实应该这么做。但是,随着业务的发展,vuex可能会变得非常的大,或者在多页面打包的时候,每个页面都需要vuex,但是如果把每个页面的vuex都写在一起,你会发现,原来我a页面可能只需要30个vuex的数据监听,但是会多出来其他页面的数据,这显然不应该的。所以,我们需要一个能够动态加载模块的方法,每个页面动态加载自己的vuex数据使用。api了解vuex官方提供了几个api:registerModule动态注册模块apiunregisterModule卸载一个动态模块hasModule...
vue router 一个重定向页面的思路
当项目需要和其他项目进行沟通的时候,往往常见的做法就是,我在a网站点击一个按钮,在链接中携带query参数啥的,然后去访问b网站页面。此时可能会有两种情况:跳转的链接就是b网站的具体链接地址。跳转的链接是b网站的一个跳板,跳板会对参数做一些操作,然后进行重定向。两种做法都各有优势:第一种省事简单,但是如果以后b网站链接层级发生变化,那么就会导致链接不可能,到时候还需要进行修改,很痛苦。第二种的话,我们需要写一个跳板页面,但是通过一些参数要求,甚至自身加上一些逻辑处理,那么他的功能性会更强一些。那么,在vue中,做一个重定向页面,怎么做?思路思路1利用路由守卫,当链接上存在某个参数的时候,...
vue 过滤器 驼峰与短横线相互转换的方法
自己去注册吧,提供的是es6导出的方法//短横线转驼峰 export const dashToHump = function (value) { const textArr = value.split("-"); return textArr.map((item, index) => { if (index === 0) return item.toLowerCase(); return item.slice(0, 1).toUpperCase() + item.slice(1); }) .join("")...
使用vue.draggable拖拽组件遇到的一些问题
资源github:vue.draggable中文文档:vue.draggable中文文档api参考文档:sortablejsvue.draggable是基于sortablejs的vue封装,所有有些api官方并不会有过多的解释,可以去sortablejs查看下拖拽无法触发页面滚动当拖拽的内容大于页面宽高时,页面滚动就是一个必然的需求,但是vue.draggable默认情况并不能触发滚动。官方设置里有一个属性:scroll,如果为true时就能触发滚动,但是默认属性就是true;所以这个配置可以说是无效的。解决方案:cannot set scrollSensitivity19年的时候就有提...

关于 element table多选里面实现单选的偷懒做法
在elment的table多选中增加单选逻辑,这个也不能说产品的问题,单选和多选应该算是比较基础的应用,但是,element并没有对单选做支持。于是我有了一个偷懒思路!前提由于table的一键全选按钮无法进行细致化操作,无法控制点击时选中的数量,只能在选中后的回调里面处理,所以,我的做法是单选时隐藏这个按钮。 selectable方法可以返回布尔值来表示当前格子是否允许勾选或者取消,其实就是禁用的意思。<el-table-column type="selection" :selectable="onSelectable"></el...

关于element el-input 输入的值需要转number的问题
前言经常是一个input表单需要输入数字和小数,那么如果仅仅是要求数据格式,我们可以通过表单校验的方式对输入的值进行限制,具体的做法,我之前就写过文章:《element input表单验证数字类型方法大全》里面有关于数字类型校验的方式,基本涵盖了日常需求。但是,仅仅就只能这样吗???no,如果后端要求你输入的数字,提交给他的时候也必须是数字类型呢?要知道,即便我们做了表单校验,校验它输入的必须是数字格式,但是input本身就是一个string类型的值,即便你设置type="number"也无法改变它值的类型。有人说用.number;其实这个问题之前就聊过了,这个修饰符...
vue2
Google Chrome Windows 10对于vue2来货,这个没有啊, 请问有没有什么解决方法?
import { getItem, setItem } from "@/utils";
木灵鱼儿
FireFox Windows 10getItem, setItem是对于localStorage的封装,可以看是:localStorage.getItem,localStorage.setItem.............. 我不是说了吗?[疑惑]