feat(plugin-layout): 支持运行时配置menus

This commit is contained in:
wanchun 2022-04-11 12:31:41 +08:00
parent b5184a4e7d
commit 3ddc355738
6 changed files with 113 additions and 47 deletions

View File

@ -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
}
)

View File

@ -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));
}
});

View File

@ -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>;
};
}
})

View File

@ -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 (

View File

@ -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',

View File

@ -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;
}
}
};
};