feat: plugin-layout的页签信息支持国际化

This commit is contained in:
harrywan 2024-09-04 15:27:44 +08:00
parent 6d64a0997a
commit 0a78b72fdf
13 changed files with 127 additions and 65 deletions

View File

@ -31,5 +31,8 @@
"source": "./fixtures/output/**/*.*", "source": "./fixtures/output/**/*.*",
"target": "./fixtures/input/<base>" "target": "./fixtures/input/<base>"
} }
],
"cSpell.words": [
"unref"
] ]
} }

View File

@ -30,6 +30,13 @@ export default (api) => {
const absRuntimeFilePath = join(namespace, 'runtime.js'); const absRuntimeFilePath = join(namespace, 'runtime.js');
api.register({
key: 'addExtraLocales',
fn: () => [
join(api.paths.absTmpPath, namespace, 'locales'),
],
});
api.onGenerateFiles(async () => { api.onGenerateFiles(async () => {
// .fes配置 // .fes配置
const userConfig = { const userConfig = {

View File

@ -1,4 +1,4 @@
const getMetaByName = (config, name) => { function getMetaByName(config, name) {
let res = {}; let res = {};
if (Array.isArray(config)) { if (Array.isArray(config)) {
for (let i = 0; i < config.length; i++) { for (let i = 0; i < config.length; i++) {
@ -17,9 +17,9 @@ const getMetaByName = (config, name) => {
} }
} }
return res; return res;
}; }
const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => { function fillMenuByRoute(menuConfig, routeConfig, dep = 0) {
dep += 1; dep += 1;
if (dep > 3) { if (dep > 3) {
console.warn('[plugin-layout]: 菜单层级最好不要超出三层!'); console.warn('[plugin-layout]: 菜单层级最好不要超出三层!');
@ -44,6 +44,6 @@ const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
}); });
} }
return arr; return arr;
}; }
export default fillMenuByRoute; export default fillMenuByRoute;

View File

@ -1,6 +1,6 @@
import { plugin } from '@@/core/coreExports'; import { plugin } from '@@/core/coreExports';
export const transTitle = (name) => { export function transTitle(name) {
if (!/^\$\S+$/.test(name)) { if (!/^\$\S+$/.test(name)) {
return name; return name;
} }
@ -10,10 +10,10 @@ export const transTitle = (name) => {
return t(name.slice(1)); return t(name.slice(1));
} }
return name; return name;
}; }
export const transform = (menus) => export function transform(menus) {
menus.map((menu) => { return menus.map((menu) => {
const copy = { const copy = {
...menu, ...menu,
label: transTitle(menu.label), label: transTitle(menu.label),
@ -23,3 +23,4 @@ export const transform = (menus) =>
} }
return copy; return copy;
}); });
}

View File

@ -2,7 +2,7 @@ const isStr = function (str) {
return typeof str === 'string'; return typeof str === 'string';
}; };
export const isValid = (elm) => { export function isValid(elm) {
if (elm.nodeType === 1) { if (elm.nodeType === 1) {
if (elm.nodeName.toLowerCase() === 'script') { if (elm.nodeName.toLowerCase() === 'script') {
return false; return false;
@ -22,9 +22,9 @@ export const isValid = (elm) => {
} }
} }
return true; return true;
}; }
export const validateContent = (svgContent) => { export function validateContent(svgContent) {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = svgContent; div.innerHTML = svgContent;
@ -46,4 +46,4 @@ export const validateContent = (svgContent) => {
} }
} }
return ''; return '';
}; }

View File

@ -1,8 +1,9 @@
export const flatNodes = (nodes = []) => export function flatNodes(nodes = []) {
nodes.reduce((res, node) => { return nodes.reduce((res, node) => {
res.push(node); res.push(node);
if (node.children) { if (node.children) {
res = res.concat(flatNodes(node.children)); res = res.concat(flatNodes(node.children));
} }
return res; return res;
}, []); }, []);
}

View File

@ -0,0 +1,6 @@
export default {
pluginLayout: {
closeOtherPage: 'Close Other Page',
reloadPage: 'Reload Page',
},
};

View File

@ -0,0 +1,6 @@
export default {
pluginLayout: {
closeOtherPage: '关闭其他页签',
reloadPage: '刷新当前页签',
},
};

View File

@ -7,7 +7,7 @@
type="card" type="card"
class="layout-content-tabs" class="layout-content-tabs"
@close="handleCloseTab" @close="handleCloseTab"
@update:modelValue="switchPage" @update:model-value="switchPage"
> >
<FTabPane v-for="page in pageList" :key="page.path" :value="page.path" :closable="pageList.length > 1"> <FTabPane v-for="page in pageList" :key="page.path" :value="page.path" :closable="pageList.length > 1">
<template #tab> <template #tab>
@ -30,7 +30,7 @@
import { computed, ref, unref } from 'vue'; import { computed, ref, unref } from 'vue';
import { FDropdown, FTabPane, FTabs } from '@fesjs/fes-design'; import { FDropdown, FTabPane, FTabs } from '@fesjs/fes-design';
import { MoreOutlined, ReloadOutlined } from '@fesjs/fes-design/icon'; import { MoreOutlined, ReloadOutlined } from '@fesjs/fes-design/icon';
import { useRoute, useRouter } from '@@/core/coreExports'; import { plugin, useRoute, useRouter } from '@@/core/coreExports';
import { transTitle } from '../helpers/pluginLocale'; import { transTitle } from '../helpers/pluginLocale';
import { deleteTitle, getTitle } from '../useTitle'; import { deleteTitle, getTitle } from '../useTitle';
import { useLayout } from '../useLayout'; import { useLayout } from '../useLayout';
@ -71,26 +71,45 @@ export default {
}; };
const pageList = ref([createPage(router.currentRoute.value)]); const pageList = ref([createPage(router.currentRoute.value)]);
const actions = [
{ const actions = computed(() => {
value: 'closeOtherPage', const sharedLocale = plugin.getShared('locale');
label: '关闭其他页签', if (sharedLocale) {
}, const { t } = sharedLocale.locale;
{ return [
value: 'reloadPage', {
label: '刷新当前页签', value: 'closeOtherPage',
}, label: t('pluginLayout.closeOtherPage'),
]; },
{
value: 'reloadPage',
label: t('pluginLayout.reloadPage'),
},
];
}
return [
{
value: 'closeOtherPage',
label: '关闭其他页签',
},
{
value: 'reloadPage',
label: '刷新当前页签',
},
];
});
const findPage = path => pageList.value.find(item => unref(item.path) === unref(path)); const findPage = path => pageList.value.find(item => unref(item.path) === unref(path));
router.beforeEach((to) => { router.beforeEach((to) => {
const page = findPage(to.path); const page = findPage(to.path);
if (!page) if (!page) {
pageList.value = [...pageList.value, createPage(to)]; pageList.value = [...pageList.value, createPage(to)];
}
else else {
page.route = to; page.route = to;
}
return true; return true;
}); });
@ -113,11 +132,13 @@ export default {
const index = list.indexOf(selectedPage); const index = list.indexOf(selectedPage);
if (route.path === selectedPage.path) { if (route.path === selectedPage.path) {
if (list.length > 1) { if (list.length > 1) {
if (list.length - 1 === index) if (list.length - 1 === index) {
await switchPage(list[index - 1].path); await switchPage(list[index - 1].path);
}
else else {
await switchPage(list[index + 1].path); await switchPage(list[index + 1].path);
}
} }
} }
list.splice(index, 1); list.splice(index, 1);
@ -129,8 +150,9 @@ export default {
const reloadPage = (path) => { const reloadPage = (path) => {
const selectedPage = findPage(path || unref(route.path)); const selectedPage = findPage(path || unref(route.path));
if (selectedPage) if (selectedPage) {
selectedPage.key = getKey(); selectedPage.key = getKey();
}
}; };
const closeOtherPage = (path) => { const closeOtherPage = (path) => {
const selectedPage = findPage(path || unref(route.path)); const selectedPage = findPage(path || unref(route.path));
@ -139,8 +161,9 @@ export default {
}; };
const getPageKey = (_route) => { const getPageKey = (_route) => {
const selectedPage = findPage(_route.path); const selectedPage = findPage(_route.path);
if (selectedPage) if (selectedPage) {
return selectedPage.key; return selectedPage.key;
}
return ''; return '';
}; };

View File

@ -1,5 +1,5 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'node:fs';
import { join } from 'path'; import { join } from 'node:path';
import { name } from '../package.json'; import { name } from '../package.json';
const namespace = 'plugin-locale'; const namespace = 'plugin-locale';
@ -32,10 +32,17 @@ export default (api) => {
return join(api.paths.absSrcPath, api.config.singular ? 'locale' : 'locales'); return join(api.paths.absSrcPath, api.config.singular ? 'locale' : 'locales');
} }
api.register({
key: 'addExtraLocales',
fn: () => [
getLocaleFileBasePath(),
],
});
// 监听 locale 文件改变,重新生成文件 // 监听 locale 文件改变,重新生成文件
api.addTmpGenerateWatcherPaths(getLocaleFileBasePath); api.addTmpGenerateWatcherPaths(getLocaleFileBasePath);
api.onGenerateFiles(() => { api.onGenerateFiles(async () => {
// .fes配置 // .fes配置
const userConfig = { const userConfig = {
locale: 'zh-CN', // default locale locale: 'zh-CN', // default locale
@ -45,9 +52,13 @@ export default (api) => {
...api.config.locale, ...api.config.locale,
}; };
const localeConfigFileBasePath = getLocaleFileBasePath(); const additionalLocales = await api.applyPlugins({
key: 'addExtraLocales',
type: api.ApplyPluginsType.add,
initialValue: [],
});
const { files, locales } = getLocales(localeConfigFileBasePath); const { files, locales } = getLocales(additionalLocales);
const { baseNavigator, ...otherConfig } = userConfig; const { baseNavigator, ...otherConfig } = userConfig;
@ -55,7 +66,7 @@ export default (api) => {
path: join(namespace, 'locales.js'), path: join(namespace, 'locales.js'),
content: Mustache.render(readFileSync(join(__dirname, 'runtime/locales.js.tpl'), 'utf-8'), { content: Mustache.render(readFileSync(join(__dirname, 'runtime/locales.js.tpl'), 'utf-8'), {
REPLACE_IMPORTS: files, REPLACE_IMPORTS: files,
REPLACE_LOCALES: locales.map((item) => ({ REPLACE_LOCALES: locales.map(item => ({
locale: item.locale, locale: item.locale,
importNames: item.importNames.join(', '), importNames: item.importNames.join(', '),
})), })),

View File

@ -1,6 +1,6 @@
import { plugin } from '@@/core/coreExports'; import { plugin } from '@@/core/coreExports';
// eslint-disable-next-line import/extensions
import { useI18n, locale, install } from './core'; import { install, locale, useI18n } from './core';
import SelectLang from './views/SelectLang.vue'; import SelectLang from './views/SelectLang.vue';
// 共享出去 // 共享出去

View File

@ -1,11 +1,11 @@
<template> <template>
<FTooltip v-model="isOpened" popperClass="lang-popper" mode="popover"> <FTooltip v-model="isOpened" popper-class="lang-popper" mode="popover">
<div class="lang-icon"> <div class="lang-icon">
<LanguageOutlined /> <LanguageOutlined />
</div> </div>
<template #content> <template #content>
<FScrollbar height="274" class="lang-container"> <FScrollbar height="274" class="lang-container">
<div v-for="item in configs" :key="item.lang" :class="['lang-option', item.lang === locale && 'is-selected']" @click="handleSelect(item)"> <div v-for="item in configs" :key="item.lang" class="lang-option" :class="[item.lang === locale && 'is-selected']" @click="handleSelect(item)">
<span>{{ item.icon }}</span> <span>{{ item.icon }}</span>
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
</div> </div>
@ -15,12 +15,12 @@
</template> </template>
<script> <script>
import { FTooltip, FScrollbar } from '@fesjs/fes-design'; import { FScrollbar, FTooltip } from '@fesjs/fes-design';
import { LanguageOutlined } from '@fesjs/fes-design/icon'; import { LanguageOutlined } from '@fesjs/fes-design/icon';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import langUConfigMap from '../langUConfigMap'; import langUConfigMap from '../langUConfigMap';
// eslint-disable-next-line import/extensions
import { locale as _locale } from '../core'; import { locale as _locale } from '../core';
export default { export default {
@ -55,11 +55,13 @@ export default {
}, },
}; };
</script> </script>
<style> <style>
.fes-tooltip.fes-tooltip-popover.lang-popper { .fes-tooltip.fes-tooltip-popover.lang-popper {
padding: 0; padding: 0;
} }
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
.lang-icon { .lang-icon {
display: flex; display: flex;

View File

@ -1,4 +1,4 @@
import { join, basename } from 'path'; import { basename, join } from 'node:path';
import { glob, winPath } from '@fesjs/utils'; import { glob, winPath } from '@fesjs/utils';
const ignore = /\.(d\.ts|\.test\.(js|ts))$/; const ignore = /\.(d\.ts|\.test\.(js|ts))$/;
@ -15,30 +15,32 @@ const getRouteName = function (path) {
.replace(/\[...([a-zA-Z]*)\]/, 'FUZZYMATCH-$1'); .replace(/\[...([a-zA-Z]*)\]/, 'FUZZYMATCH-$1');
}; };
export function getLocales(cwd) { export function getLocales(cwdArray) {
const map = {}; const map = {};
const files = []; const files = [];
glob.sync('**/*.js', { cwdArray.forEach((cwd) => {
cwd, glob.sync('**/*.js', {
}) cwd,
.filter((file) => !ignore.test(file)) })
.forEach((fileName) => { .filter(file => !ignore.test(file))
const locale = basename(fileName, '.js'); .forEach((fileName) => {
const importName = getRouteName(fileName).replace('.js', ''); const locale = basename(fileName, '.js');
const result = { const importName = getRouteName(fileName).replace('.js', '');
importName, const result = {
// import语法的路径必须处理win importName,
path: winPath(join(cwd, fileName)), // import语法的路径必须处理win
}; path: winPath(join(cwd, fileName)),
files.push(result); };
if (!map[locale]) { files.push(result);
map[locale] = []; if (!map[locale]) {
} map[locale] = [];
map[locale].push(importName); }
}); map[locale].push(importName);
});
});
return { return {
locales: Object.keys(map).map((key) => ({ locale: key, importNames: map[key] })), locales: Object.keys(map).map(key => ({ locale: key, importNames: map[key] })),
files, files,
}; };
} }