mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-10-15 12:02:18 +08:00
feat(plugin-layout): 支持运行时配置menus
This commit is contained in:
parent
b5184a4e7d
commit
3ddc355738
@ -32,6 +32,9 @@ export default (api) => {
|
|||||||
|
|
||||||
const HAS_LOCALE = api.hasPlugins(['@fesjs/plugin-locale']);
|
const HAS_LOCALE = api.hasPlugins(['@fesjs/plugin-locale']);
|
||||||
|
|
||||||
|
// 路由信息
|
||||||
|
const routes = await api.getRoutes();
|
||||||
|
|
||||||
// .fes配置
|
// .fes配置
|
||||||
const userConfig = {
|
const userConfig = {
|
||||||
title: name,
|
title: name,
|
||||||
@ -39,14 +42,13 @@ export default (api) => {
|
|||||||
...(api.config.layout || {})
|
...(api.config.layout || {})
|
||||||
};
|
};
|
||||||
|
|
||||||
// 路由信息
|
|
||||||
const routes = await api.getRoutes();
|
|
||||||
// 把路由的meta合并到menu配置中
|
// 把路由的meta合并到menu配置中
|
||||||
userConfig.menus = helper.fillMenuByRoute(userConfig.menus, routes);
|
const menus = helper.fillMenuByRoute(userConfig.menus, routes);
|
||||||
|
delete userConfig.menus;
|
||||||
|
|
||||||
const icons = helper.getIconsFromMenu(userConfig.menus);
|
const iconNames = helper.getIconNamesFromMenu(menus);
|
||||||
|
|
||||||
const iconsString = icons.map(
|
const iconsString = iconNames.map(
|
||||||
iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'`
|
iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'`
|
||||||
);
|
);
|
||||||
api.writeTmpFile({
|
api.writeTmpFile({
|
||||||
@ -54,7 +56,7 @@ export default (api) => {
|
|||||||
content: `
|
content: `
|
||||||
${iconsString.join(';\n')}
|
${iconsString.join(';\n')}
|
||||||
export default {
|
export default {
|
||||||
${icons.join(',\n')}
|
${iconNames.join(',\n')}
|
||||||
}`
|
}`
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,6 +66,7 @@ export default (api) => {
|
|||||||
readFileSync(join(__dirname, 'runtime/index.tpl'), 'utf-8'),
|
readFileSync(join(__dirname, 'runtime/index.tpl'), 'utf-8'),
|
||||||
{
|
{
|
||||||
REPLACE_USER_CONFIG: JSON.stringify(userConfig),
|
REPLACE_USER_CONFIG: JSON.stringify(userConfig),
|
||||||
|
MENUS: JSON.stringify(menus),
|
||||||
HAS_LOCALE
|
HAS_LOCALE
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
|
const getMetaByName = (config, name) => {
|
||||||
const matchName = (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++) {
|
||||||
@ -10,7 +9,7 @@ const matchName = (config, name) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
res = matchName(item.children, name);
|
res = getMetaByName(item.children, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,27 +26,27 @@ export const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
|
|||||||
menuConfig.forEach((menu) => {
|
menuConfig.forEach((menu) => {
|
||||||
const pageConfig = {};
|
const pageConfig = {};
|
||||||
if (menu.name) {
|
if (menu.name) {
|
||||||
Object.assign(pageConfig, matchName(routeConfig, menu.name));
|
Object.assign(
|
||||||
|
pageConfig,
|
||||||
|
getMetaByName(routeConfig, menu.name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// menu的配置优先级高,当menu存在配置时,忽略页面的配置
|
// menu的配置优先级高,当menu存在配置时,忽略页面的配置
|
||||||
Object.keys(pageConfig).forEach((prop) => {
|
Object.keys(pageConfig).forEach((prop) => {
|
||||||
if (menu[prop] === undefined || menu[prop] === null || menu[prop] === '') {
|
if (
|
||||||
|
menu[prop] === undefined
|
||||||
|
|| menu[prop] === null
|
||||||
|
|| menu[prop] === ''
|
||||||
|
) {
|
||||||
menu[prop] = pageConfig[prop];
|
menu[prop] = pageConfig[prop];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 处理icon
|
|
||||||
if (menu.icon) {
|
|
||||||
const icon = menu.icon;
|
|
||||||
const urlReg = /^((https?|ftp|file):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
|
||||||
if (typeof icon === 'string' && !((urlReg.test(icon) || icon.includes('.svg')))) {
|
|
||||||
menu.icon = {
|
|
||||||
type: 'icon',
|
|
||||||
name: icon
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (menu.children && menu.children.length > 0) {
|
if (menu.children && menu.children.length > 0) {
|
||||||
menu.children = fillMenuByRoute(menu.children, routeConfig, dep);
|
menu.children = fillMenuByRoute(
|
||||||
|
menu.children,
|
||||||
|
routeConfig,
|
||||||
|
dep
|
||||||
|
);
|
||||||
}
|
}
|
||||||
arr.push(menu);
|
arr.push(menu);
|
||||||
});
|
});
|
||||||
@ -55,7 +54,7 @@ export const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
|
|||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getIconsFromMenu(data) {
|
export function getIconNamesFromMenu(data) {
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -63,12 +62,19 @@ export function getIconsFromMenu(data) {
|
|||||||
data.forEach((item = { path: '/' }) => {
|
data.forEach((item = { path: '/' }) => {
|
||||||
if (item.icon) {
|
if (item.icon) {
|
||||||
const { icon } = item;
|
const { icon } = item;
|
||||||
if (icon.type === 'icon') {
|
// 处理icon
|
||||||
icons.push(icon.name);
|
if (icon) {
|
||||||
|
const urlReg = /^((https?|ftp|file):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
||||||
|
if (
|
||||||
|
typeof icon === 'string'
|
||||||
|
&& !(urlReg.test(icon) || icon.includes('.svg'))
|
||||||
|
) {
|
||||||
|
icons.push(icon);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
icons = icons.concat(getIconsFromMenu(item.children));
|
icons = icons.concat(getIconNamesFromMenu(item.children));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,16 +1,48 @@
|
|||||||
import { reactive, defineComponent } from "vue";
|
import { ref, defineComponent, inject } from "vue";
|
||||||
import { plugin, ApplyPluginsType } from "@@/core/coreExports";
|
import { plugin, ApplyPluginsType } from "@@/core/coreExports";
|
||||||
import BaseLayout from "./views/BaseLayout.vue";
|
import BaseLayout from "./views/BaseLayout.vue";
|
||||||
|
|
||||||
const Layout = defineComponent({
|
const Layout = defineComponent({
|
||||||
name: 'Layout',
|
name: 'Layout',
|
||||||
setup(){
|
setup(){
|
||||||
const userConfig = reactive({{{REPLACE_USER_CONFIG}}});
|
const userConfig = {{{REPLACE_USER_CONFIG}}};
|
||||||
|
const menusRef = ref({{{MENUS}}})
|
||||||
const runtimeConfig = plugin.applyPlugins({
|
const runtimeConfig = plugin.applyPlugins({
|
||||||
key: "layout",
|
key: "layout",
|
||||||
type: ApplyPluginsType.modify,
|
type: ApplyPluginsType.modify,
|
||||||
initialValue: {},
|
initialValue: {
|
||||||
|
initialState: inject('initialState')
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
// 如果运行时配置了menus,则需要处理
|
||||||
|
if (
|
||||||
|
runtimeConfig.menus &&
|
||||||
|
typeof runtimeConfig.menus.request === 'function'
|
||||||
|
) {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const newMenusData = await runtimeConfig.menus.request(
|
||||||
|
menusRef.value
|
||||||
|
);
|
||||||
|
return newMenusData;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[plugin-layout]: runtime config menus.request run error:', e);
|
||||||
|
}
|
||||||
|
return menusRef.value;
|
||||||
|
};
|
||||||
|
if (runtimeConfig.menus.watch) {
|
||||||
|
watch(
|
||||||
|
() => runtimeConfig.menus.watch,
|
||||||
|
async () => {
|
||||||
|
const newMenusData = await fetchData();
|
||||||
|
menusRef.value = newMenusData;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fetchData().then((newMenusData) => {
|
||||||
|
menusRef.value = newMenusData;
|
||||||
|
});
|
||||||
|
}
|
||||||
const localeShared = plugin.getShared("locale");
|
const localeShared = plugin.getShared("locale");
|
||||||
return () => {
|
return () => {
|
||||||
const slots = {
|
const slots = {
|
||||||
@ -29,7 +61,7 @@ const Layout = defineComponent({
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return <BaseLayout locale={ localeShared ? true : false } {...userConfig} v-slots={slots}></BaseLayout>;
|
return <BaseLayout locale={ localeShared ? true : false } {...userConfig} menus={menusRef.value} v-slots={slots}></BaseLayout>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,34 +1,47 @@
|
|||||||
<script>
|
<script>
|
||||||
import { ref, onBeforeMount } from 'vue';
|
import { ref, onBeforeMount, isVNode } from 'vue';
|
||||||
// eslint-disable-next-line import/extensions
|
// eslint-disable-next-line import/extensions
|
||||||
import Icons from '../icons';
|
import Icons from '../icons';
|
||||||
import { validateContent } from '../helpers/svg';
|
import { validateContent } from '../helpers/svg';
|
||||||
|
|
||||||
|
const urlReg = /^((https?|ftp|file):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
||||||
|
const isUrlResource = name => urlReg.test(name) || name.includes('.svg');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
icon: [String, Object]
|
icon: [String, Object]
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const AIcon = ref(null);
|
const AIconComponent = ref(null);
|
||||||
const AText = ref(null);
|
const AText = ref(null);
|
||||||
|
const AVNode = ref(null);
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (props.icon && props.icon.type === 'icon') {
|
if (typeof props.icon === 'string') {
|
||||||
AIcon.value = Icons[props.icon.name];
|
if (isUrlResource(props.icon)) {
|
||||||
|
fetch(props.icon).then((rsp) => {
|
||||||
|
if (rsp.ok) {
|
||||||
|
return rsp.text().then((svgContent) => {
|
||||||
|
AText.value = validateContent(svgContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AIconComponent.value = Icons[props.icon];
|
||||||
|
}
|
||||||
|
} else if (isVNode(props.icon)) {
|
||||||
|
AVNode.value = props.icon;
|
||||||
} else {
|
} else {
|
||||||
fetch(props.icon).then((rsp) => {
|
console.log(props.icon);
|
||||||
if (rsp.ok) {
|
|
||||||
return rsp.text().then((svgContent) => {
|
|
||||||
AText.value = validateContent(svgContent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (AIcon.value) {
|
if (AVNode.value) {
|
||||||
return <AIcon.value />;
|
return AVNode.value;
|
||||||
|
}
|
||||||
|
if (AIconComponent.value) {
|
||||||
|
return <AIconComponent.value />;
|
||||||
}
|
}
|
||||||
if (AText.value) {
|
if (AText.value) {
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +26,8 @@ const renderClient = (opts = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const app = createApp(rootContainer);
|
const app = createApp(rootContainer);
|
||||||
app.provide("initialState", initialState);
|
// initialState是响应式的,后期可以更改
|
||||||
|
app.provide("initialState", reactive(initialState));
|
||||||
|
|
||||||
plugin.applyPlugins({
|
plugin.applyPlugins({
|
||||||
key: 'onAppCreated',
|
key: 'onAppCreated',
|
||||||
|
@ -24,6 +24,17 @@ export const beforeRender = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const layout = {
|
export const layout = (initialValue) => {
|
||||||
customHeader: <UserCenter />
|
console.log('layout runtime');
|
||||||
|
return {
|
||||||
|
...initialValue,
|
||||||
|
customHeader: <UserCenter />,
|
||||||
|
menus: {
|
||||||
|
request: async (defaultMenuData) => {
|
||||||
|
console.log(defaultMenuData);
|
||||||
|
console.log(initialValue.initialState);
|
||||||
|
return defaultMenuData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user