mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-06-30 02:35:08 +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 routes = await api.getRoutes();
|
||||
|
||||
// .fes配置
|
||||
const userConfig = {
|
||||
title: name,
|
||||
@ -39,14 +42,13 @@ export default (api) => {
|
||||
...(api.config.layout || {})
|
||||
};
|
||||
|
||||
// 路由信息
|
||||
const routes = await api.getRoutes();
|
||||
// 把路由的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'`
|
||||
);
|
||||
api.writeTmpFile({
|
||||
@ -54,7 +56,7 @@ export default (api) => {
|
||||
content: `
|
||||
${iconsString.join(';\n')}
|
||||
export default {
|
||||
${icons.join(',\n')}
|
||||
${iconNames.join(',\n')}
|
||||
}`
|
||||
});
|
||||
|
||||
@ -64,6 +66,7 @@ export default (api) => {
|
||||
readFileSync(join(__dirname, 'runtime/index.tpl'), 'utf-8'),
|
||||
{
|
||||
REPLACE_USER_CONFIG: JSON.stringify(userConfig),
|
||||
MENUS: JSON.stringify(menus),
|
||||
HAS_LOCALE
|
||||
}
|
||||
)
|
||||
|
@ -1,5 +1,4 @@
|
||||
|
||||
const matchName = (config, name) => {
|
||||
const getMetaByName = (config, name) => {
|
||||
let res = {};
|
||||
if (Array.isArray(config)) {
|
||||
for (let i = 0; i < config.length; i++) {
|
||||
@ -10,7 +9,7 @@ const matchName = (config, name) => {
|
||||
break;
|
||||
}
|
||||
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) => {
|
||||
const pageConfig = {};
|
||||
if (menu.name) {
|
||||
Object.assign(pageConfig, matchName(routeConfig, menu.name));
|
||||
Object.assign(
|
||||
pageConfig,
|
||||
getMetaByName(routeConfig, menu.name)
|
||||
);
|
||||
}
|
||||
// menu的配置优先级高,当menu存在配置时,忽略页面的配置
|
||||
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];
|
||||
}
|
||||
});
|
||||
// 处理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) {
|
||||
menu.children = fillMenuByRoute(menu.children, routeConfig, dep);
|
||||
menu.children = fillMenuByRoute(
|
||||
menu.children,
|
||||
routeConfig,
|
||||
dep
|
||||
);
|
||||
}
|
||||
arr.push(menu);
|
||||
});
|
||||
@ -55,7 +54,7 @@ export const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
|
||||
return arr;
|
||||
};
|
||||
|
||||
export function getIconsFromMenu(data) {
|
||||
export function getIconNamesFromMenu(data) {
|
||||
if (!Array.isArray(data)) {
|
||||
return [];
|
||||
}
|
||||
@ -63,12 +62,19 @@ export function getIconsFromMenu(data) {
|
||||
data.forEach((item = { path: '/' }) => {
|
||||
if (item.icon) {
|
||||
const { icon } = item;
|
||||
if (icon.type === 'icon') {
|
||||
icons.push(icon.name);
|
||||
// 处理icon
|
||||
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) {
|
||||
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 BaseLayout from "./views/BaseLayout.vue";
|
||||
|
||||
const Layout = defineComponent({
|
||||
name: 'Layout',
|
||||
setup(){
|
||||
const userConfig = reactive({{{REPLACE_USER_CONFIG}}});
|
||||
const userConfig = {{{REPLACE_USER_CONFIG}}};
|
||||
const menusRef = ref({{{MENUS}}})
|
||||
const runtimeConfig = plugin.applyPlugins({
|
||||
key: "layout",
|
||||
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");
|
||||
return () => {
|
||||
const slots = {
|
||||
@ -29,7 +61,7 @@ const Layout = defineComponent({
|
||||
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>
|
||||
import { ref, onBeforeMount } from 'vue';
|
||||
import { ref, onBeforeMount, isVNode } from 'vue';
|
||||
// eslint-disable-next-line import/extensions
|
||||
import Icons from '../icons';
|
||||
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 {
|
||||
props: {
|
||||
icon: [String, Object]
|
||||
},
|
||||
setup(props) {
|
||||
const AIcon = ref(null);
|
||||
const AIconComponent = ref(null);
|
||||
const AText = ref(null);
|
||||
const AVNode = ref(null);
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.icon && props.icon.type === 'icon') {
|
||||
AIcon.value = Icons[props.icon.name];
|
||||
if (typeof props.icon === 'string') {
|
||||
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 {
|
||||
fetch(props.icon).then((rsp) => {
|
||||
if (rsp.ok) {
|
||||
return rsp.text().then((svgContent) => {
|
||||
AText.value = validateContent(svgContent);
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(props.icon);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (AIcon.value) {
|
||||
return <AIcon.value />;
|
||||
if (AVNode.value) {
|
||||
return AVNode.value;
|
||||
}
|
||||
if (AIconComponent.value) {
|
||||
return <AIconComponent.value />;
|
||||
}
|
||||
if (AText.value) {
|
||||
return (
|
||||
|
@ -26,7 +26,8 @@ const renderClient = (opts = {}) => {
|
||||
});
|
||||
|
||||
const app = createApp(rootContainer);
|
||||
app.provide("initialState", initialState);
|
||||
// initialState是响应式的,后期可以更改
|
||||
app.provide("initialState", reactive(initialState));
|
||||
|
||||
plugin.applyPlugins({
|
||||
key: 'onAppCreated',
|
||||
|
@ -24,6 +24,17 @@ export const beforeRender = {
|
||||
}
|
||||
};
|
||||
|
||||
export const layout = {
|
||||
customHeader: <UserCenter />
|
||||
export const layout = (initialValue) => {
|
||||
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