接上一篇Vue-Fetch-Async-Page可行性研究(续4)-CSDN博客
上篇引入了vue-i18n、material icon和roboto字体并适配了组件内国际化,遗留了Vuetify组件国际化及其配置抽取到JS请求返回、路由表抽取到Json请求返回、主题切换功能、App.js的抽取等改进工作。
MenuRoutes.json
// MenuRoutes.json
[
{"menu":"Go to VTable","path": "/VTable", "name": "VTable", "fetchPath": "./VTablePage.js"},
{"menu":"Go to VBtn","path": "/VBtn", "name": "VBtn", "fetchPath": "./VBtnPage.js"},
{"menu":"Go to Echarts","path": "/echarts", "name": "echarts", "fetchPath": "./EchartsPage.js"}
]
en_US.js
// /locale/en_US.js 经过分析后发现Vuetify只对en作了国际化配置,没有中文简体,分析加载的数据结构后将结构复制到en_US.js保存
export default {
badge: 'Badge',
open: 'Open',
close: 'Close',
dismiss: 'Dismiss',
confirmEdit: {
ok: 'OK',
cancel: 'Cancel',
},
dataIterator: {
noResultsText: 'No matching records found',
loadingText: 'Loading items...',
},
dataTable: {
itemsPerPageText: 'Rows per page:',
ariaLabel: {
sortDescending: 'Sorted descending.',
sortAscending: 'Sorted ascending.',
sortNone: 'Not sorted.',
activateNone: 'Activate to remove sorting.',
activateDescending: 'Activate to sort descending.',
activateAscending: 'Activate to sort ascending.',
},
sortBy: 'Sort by',
},
dataFooter: {
itemsPerPageText: 'Items per page:',
itemsPerPageAll: 'All',
nextPage: 'Next page',
prevPage: 'Previous page',
firstPage: 'First page',
lastPage: 'Last page',
pageText: '{0}-{1} of {2}',
},
...
};
zh_CN.js
// /locale/zh_CN.js 参照英文作中文的修改,以验证国际化切换后Vuetify组件是否同步切换
export default {
badge: "徽章",
open: "打开",
close: "关闭",
dismiss: "关闭",
confirmEdit: {
ok: "确定",
cancel: "取消"
},
dataIterator: {
noResultsText: "没有匹配的记录",
loadingText: "加载中..."
},
dataTable: {
itemsPerPageText: "每页条数:",
ariaLabel: {
sortDescending: "倒序",
sortAscending: "正序",
sortNone: "未排序",
activateNone: "激活以取消排序",
activateDescending: "激活以倒序",
activateAscending: "激活以正序"
},
sortBy: "排序"
},
dataFooter: {
itemsPerPageText: "每页条数:",
itemsPerPageAll: "全部",
nextPage: "下一页",
prevPage: "上一页",
firstPage: "第一页",
lastPage: "最后一页",
pageText: "{0}-{1} 共 {2}"
},
...
}
VTablePage.js
// VTablePage.js 借用Vuetify v-data-table组件验证Vuetify组件国际化切换,注意看分页部分
export default {
template:
`
<v-data-table :items="items"></v-data-table>
`,
setup() {
const items = [
{
name: 'African Elephant',
species: 'Loxodonta africana',
diet: 'Herbivore',
habitat: 'Savanna, Forests',
},
];
return { items };
}
};
App.js
// App.js
const { createApp, ref, reactive, computed, watch, watchEffect, h } = Vue;
const { createVuetify } = Vuetify
const CTranslateMenu = { // 国际化切换改用Vuetify下拉面板组件实现更好些
template:
`
<v-menu
v-model="menu"
location="bottom end"
>
<template v-slot:activator="{ props }">
<v-btn icon="mdi-translate" v-bind="props"></v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
:value="item.value"
color="primary"
:active="locale === item.value"
@click="onToggleLocale(item.value)"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
`,
setup() {
const menu = ref(false);
const i18n = VueI18n.useI18n();
const locale = ref(i18n.locale.value);
const onToggleLocale = (value) => {
i18n.locale.value = value;
locale.value = value;
}
const items = [
{ title: 'English', value: 'en_US' },
{ title: '简体中文', value: 'zh_CN' },
]
return { items, menu, locale, onToggleLocale };
},
};
const initApp = (MenuRoutes, i18nMessages) => {
const AppConfig = {
name: 'App',
components: {
'c-translate-menu': CTranslateMenu
},
template:
`<v-locale-provider :locale="locale">
<v-app :theme="theme">
<v-app-bar color="teal-darken-4" image="assets/jpg/1080.jpg">
<template v-slot:image>
<v-img
gradient="to top right, rgba(19,84,122,.8), rgba(128,208,199,.8)"
></v-img>
</template>
<template v-slot:prepend>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
</template>
<v-app-bar-title>WEB实验室</v-app-bar-title>
<v-spacer></v-spacer>
<v-btn
:icon="theme === 'light' ? 'mdi-weather-sunny' : 'mdi-weather-night'"
@click="onToggleTheme"
></v-btn>
<c-translate-menu></c-translate-menu>
</v-app-bar>
<v-navigation-drawer v-model="drawer" app temporary>
<v-list>
<v-list-item v-for="item of MenuRoutes" :key="item.meta.menu" @click="goto(item)">{{item.meta.menu}}</v-list-item>
</v-list>
</v-navigation-drawer>
<v-main class="position-relative">
<router-view></router-view>
</v-main>
</v-app>
</v-locale-provider>`,
setup() {
const drawer = ref(false);
const router = VueRouter.useRouter();
const MenuRoutes = router.options.routes;
const goto = (route) => {
router.push({ name: route.name, params: route.params, query: route.query });
}
const theme = ref('light');
function onToggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
const locale = computed(() => VueI18n.useI18n().locale.value);
return { drawer, router, goto,MenuRoutes, theme, onToggleTheme, locale };
}
};
const app = createApp(AppConfig);
const fetchAsyncComponent = async (path) => {
let res = await import(path);
return res.default;
}
const routes = [
...MenuRoutes.map(d => ({ ...d, meta: { menu: d.menu }, component: async () => fetchAsyncComponent(d.fetchPath) }))
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
});
app.use(router);
const vuetify = createVuetify({
locale: {
locale: 'zh_CN',
fallback: 'en_US',
messages: i18nMessages,
},
});
app.use(vuetify);
const i18n = VueI18n.createI18n({
legacy: false,
locale: 'zh_CN',
fallbackLocale: 'en_US',
messages: {
en_US: {
hello: 'hello world'
},
zh_CN: {
hello: '你好世界'
}
}
});
app.use(i18n);
const vm = app.mount('#app');
}
const loadMenuRoutes = async () => {
let res = await axios.get('MenuRoutes.json');
return res.data;
}
const fetchLocale = async (locale) => await import(`./locale/${locale}.js`);
Promise.all([ loadMenuRoutes(), fetchLocale('zh_CN'), fetchLocale('en_US') ])
.then(([ MenuRoutes, zhMsg, enMsg ]) => {
initApp(MenuRoutes, { zh_CN: zhMsg.default, en_US: enMsg.default });
}).catch(err => {
console.log('err: ', err);
});
框架页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例</title>
<script src="https://cdn.staticfile.net/vue/3.2.31/vue.global.min.js"></script>
<script src="https://cdn.staticfile.net/vue-router/4.2.4/vue-router.global.min.js"></script>
<script src="https://unpkg.com/vue-i18n@11.0.0/dist/vue-i18n.global.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify@3.8.2/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.8.2/dist/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet" Cache-Control="max-age=36000" />
</head>
<body>
<div id="app"></div>
<script src="App.js"></script>
</body>
</html>
参考Vuetify官方文档通过v-locale-provider包裹根元素并传入locale参数来对所有Vuetify组件支持国际化;但Vuetify默认只支持了en语言,我经过调试取得en的messages,然后适配出简体中文并统一到en_us.js和zh_CN.js文件中(json文件编辑有不少限制,改用js更方便些);抽出Vuetify 组件下拉面板实现的国际化切换组件CTranslateMenu;参考Vuetify官方文档主题切换通过给v-app传入theme参数来实现;因为创建根元素组件需要等路由、国际化配置请求均返回才能创建,故使用Promise.all等依赖都返回后进行创建,因为不能用await所以用then替代。菜单列表采用了导航栏图标点击左出抽屉显示的改进。至此,一个基于Vue3的子页面按需请求加载框架,已基本达到可开发阶段。至于框架页面和子页面混合切换使用中的无障碍联动,通过国际化切换已经可以验证框架页面可以联动子页面了,子页面联动框架页面可考虑在框架页面加面包屑显示子页面菜单,就留给有兴趣的朋友来补充验证了。如果有朋友想参考源代码或服务,下面附上github代码仓和借由它运行的服务(github访问比较慢,可考虑配置host指定可用链路地址,我本机用的是20.200.245.247 github.com 20.205.243.166 github.com仅供参考)
github代码仓:https://github.com/xiangruihua-private/Vue3-Fetch-Async-Page-Example
github服务:https://xiangruihua-private.github.io/Vue3-Fetch-Async-Page-Example/#/