refactor(plugin-layout): 优化runtimeConfig,重新实现运行时menus配置方式,修复多页时setup执行两次的bug

This commit is contained in:
wanchun 2022-04-11 17:38:37 +08:00
parent b73c1f9027
commit 9b03236654
14 changed files with 156 additions and 176 deletions

View File

@ -32,9 +32,6 @@ export default (api) => {
const HAS_LOCALE = api.hasPlugins(['@fesjs/plugin-locale']);
// 路由信息
const routes = await api.getRoutes();
// .fes配置
const userConfig = {
title: name,
@ -42,11 +39,7 @@ export default (api) => {
...(api.config.layout || {})
};
// 把路由的meta合并到menu配置中
const menus = helper.fillMenuByRoute(userConfig.menus, routes);
delete userConfig.menus;
const iconNames = helper.getIconNamesFromMenu(menus);
const iconNames = helper.getIconNamesFromMenu(userConfig.menus);
const iconsString = iconNames.map(
iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'`
@ -66,7 +59,6 @@ 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,58 +1,3 @@
const getMetaByName = (config, name) => {
let res = {};
if (Array.isArray(config)) {
for (let i = 0; i < config.length; i++) {
const item = config[i];
if (item.meta && item.meta.name === name) {
res = item.meta;
res.path = item.path;
break;
}
if (item.children && item.children.length > 0) {
res = getMetaByName(item.children, name);
}
}
}
return res;
};
export const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
dep += 1;
if (dep > 3) {
console.warn('[plugin-layout]: 菜单层级最好不要超出三层!');
}
const arr = [];
if (Array.isArray(menuConfig) && Array.isArray(routeConfig)) {
menuConfig.forEach((menu) => {
const pageConfig = {};
if (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] === ''
) {
menu[prop] = pageConfig[prop];
}
});
if (menu.children && menu.children.length > 0) {
menu.children = fillMenuByRoute(
menu.children,
routeConfig,
dep
);
}
arr.push(menu);
});
}
return arr;
};
export function getIconNamesFromMenu(data) {
if (!Array.isArray(data)) {

View File

@ -0,0 +1,57 @@
const getMetaByName = (config, name) => {
let res = {};
if (Array.isArray(config)) {
for (let i = 0; i < config.length; i++) {
const item = config[i];
if (item.meta && item.meta.name === name) {
res = item.meta;
res.path = item.path;
break;
}
if (item.children && item.children.length > 0) {
res = getMetaByName(item.children, name);
}
}
}
return res;
};
const fillMenuByRoute = (menuConfig, routeConfig, dep = 0) => {
dep += 1;
if (dep > 3) {
console.warn('[plugin-layout]: 菜单层级最好不要超出三层!');
}
const arr = [];
if (Array.isArray(menuConfig) && Array.isArray(routeConfig)) {
menuConfig.forEach((menu) => {
const pageConfig = {};
if (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] === ''
) {
menu[prop] = pageConfig[prop];
}
});
if (menu.children && menu.children.length > 0) {
menu.children = fillMenuByRoute(
menu.children,
routeConfig,
dep
);
}
arr.push(menu);
});
}
return arr;
};
export default fillMenuByRoute;

View File

@ -0,0 +1,22 @@
import { plugin, ApplyPluginsType } from '@@/core/coreExports';
import { inject } from 'vue';
let runtimeConfig;
export default () => {
if (!runtimeConfig) {
runtimeConfig = plugin.applyPlugins({
key: 'layout',
type: ApplyPluginsType.modify,
initialValue: {
initialState: inject('initialState'),
sidebar: true,
header: true,
logo: true
}
});
}
return runtimeConfig;
};

View File

@ -1,49 +1,25 @@
import { ref, defineComponent, inject } from "vue";
import { plugin, ApplyPluginsType } from "@@/core/coreExports";
import BaseLayout from "./views/BaseLayout.vue";
import { ref, defineComponent, computed } from 'vue';
import { plugin, ApplyPluginsType, getRoutes } from '@@/core/coreExports';
import BaseLayout from './views/BaseLayout.vue';
import getRuntimeConfig from './helpers/getRuntimeConfig';
import fillMenu from './helpers/fillMenu';
const Layout = defineComponent({
name: 'Layout',
setup(){
setup() {
const userConfig = {{{REPLACE_USER_CONFIG}}};
const menusRef = ref({{{MENUS}}})
const runtimeConfig = plugin.applyPlugins({
key: "layout",
type: ApplyPluginsType.modify,
initialValue: {
initialState: inject('initialState')
},
});
const runtimeConfig = getRuntimeConfig();
let menusRef = ref(userConfig.menus);
// 如果运行时配置了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;
});
if (runtimeConfig.menus && typeof runtimeConfig.menus === 'function') {
menusRef = ref(runtimeConfig.menus(userConfig.menus));
}
const localeShared = plugin.getShared("locale");
// 把路由的meta合并到menu配置中
const filledMenuRef = computed(() => {
return fillMenu(menusRef.value, getRoutes());
});
const localeShared = plugin.getShared('locale');
return () => {
const slots = {
customHeader: () => {
@ -56,14 +32,23 @@ const Layout = defineComponent({
},
locale: () => {
if (localeShared) {
return <localeShared.SelectLang></localeShared.SelectLang>;
return (
<localeShared.SelectLang></localeShared.SelectLang>
);
}
return null;
},
}
};
return <BaseLayout locale={ localeShared ? true : false } {...userConfig} menus={menusRef.value} v-slots={slots}></BaseLayout>;
return (
<BaseLayout
locale={localeShared ? true : false}
{...userConfig}
menus={filledMenuRef.value}
v-slots={slots}
></BaseLayout>
);
};
}
})
});
export default Layout;

View File

@ -1,8 +1,8 @@
import { plugin, ApplyPluginsType } from '@@/core/coreExports';
// eslint-disable-next-line import/extensions
import { access as accessApi } from '../plugin-access/core';
import Exception404 from './views/404';
import Exception403 from './views/403';
import getRuntimeConfig from './helpers/getRuntimeConfig';
if (!accessApi) {
throw new Error(
@ -30,11 +30,7 @@ export const access = memo => ({
unAccessHandler({
router, to, from, next
}) {
const runtimeConfig = plugin.applyPlugins({
key: 'layout',
type: ApplyPluginsType.modify,
initialValue: {}
});
const runtimeConfig = getRuntimeConfig();
if (runtimeConfig.unAccessHandler && typeof runtimeConfig.unAccessHandler === 'function') {
return runtimeConfig.unAccessHandler({
router, to, from, next
@ -50,11 +46,7 @@ export const access = memo => ({
noFoundHandler({
router, to, from, next
}) {
const runtimeConfig = plugin.applyPlugins({
key: 'layout',
type: ApplyPluginsType.modify,
initialValue: {}
});
const runtimeConfig = getRuntimeConfig();
if (runtimeConfig.noFoundHandler && typeof runtimeConfig.noFoundHandler === 'function') {
return runtimeConfig.noFoundHandler({
router, to, from, next

View File

@ -146,13 +146,14 @@
<script>
import { ref, computed, onMounted } from 'vue';
import { useRoute, plugin, ApplyPluginsType } from '@@/core/coreExports';
import { useRoute } from '@@/core/coreExports';
import {
FLayout, FAside, FMain, FFooter, FHeader
} from '@fesjs/fes-design';
import Menu from './Menu';
import MultiTabProvider from './MultiTabProvider';
import defaultLogo from '../assets/logo.png';
import getRuntimeConfig from '../helpers/getRuntimeConfig';
export default {
components: {
@ -221,15 +222,7 @@ export default {
const collapsedRef = ref(false);
const route = useRoute();
const runtimeConfig = plugin.applyPlugins({
key: 'layout',
type: ApplyPluginsType.modify,
initialValue: {
sidebar: true,
header: true,
logo: true
}
});
const runtimeConfig = getRuntimeConfig();
const routeLayout = computed(() => {
let config;
// meta layout true

View File

@ -14,7 +14,6 @@ export default {
setup(props) {
const AIconComponent = ref(null);
const AText = ref(null);
const AVNode = ref(null);
onBeforeMount(() => {
if (typeof props.icon === 'string') {
@ -29,16 +28,12 @@ export default {
} else {
AIconComponent.value = Icons[props.icon];
}
} else if (isVNode(props.icon)) {
AVNode.value = props.icon;
} else {
console.log(props.icon);
}
});
return () => {
if (AVNode.value) {
return AVNode.value;
if (isVNode(props.icon)) {
return props.icon;
}
if (AIconComponent.value) {
return <AIconComponent.value />;

View File

@ -38,17 +38,12 @@
</template>
<router-view v-else v-slot="{ Component, route }">
<keep-alive :include="keepAlivePages">
<component
:is="getComponent(Component, route)"
:key="getPageKey(route)"
/>
<component :is="getComponent(Component, route)" />
</keep-alive>
</router-view>
</template>
<script>
import {
computed, onMounted, unref, ref
} from 'vue';
import { computed, unref, ref } from 'vue';
import { FTabs, FTabPane, FDropdown } from '@fesjs/fes-design';
import { ReloadOutlined, MoreOutlined } from '@fesjs/fes-design/icon';
import { useRouter, useRoute } from '@@/core/coreExports';
@ -68,20 +63,6 @@ export default {
multiTabs: Boolean
},
setup() {
const route = useRoute();
const router = useRouter();
const pageList = ref([]);
const actions = [
{
value: 'closeOtherPage',
label: '关闭其他'
},
{
value: 'reloadPage',
label: '刷新当前页'
}
];
const createPage = (_route) => {
const title = _route.meta.title;
return {
@ -93,11 +74,21 @@ export default {
};
};
const findPage = path => pageList.value.find(item => unref(item.path) === unref(path));
const route = useRoute();
const router = useRouter();
const pageList = ref([createPage(route)]);
const actions = [
{
value: 'closeOtherPage',
label: '关闭其他'
},
{
value: 'reloadPage',
label: '刷新当前页'
}
];
onMounted(() => {
pageList.value = [createPage(route)];
});
const findPage = path => pageList.value.find(item => unref(item.path) === unref(path));
router.beforeEach((to) => {
if (!findPage(to.path)) {

View File

@ -1,5 +1,5 @@
import { createRouter as createVueRouter, {{{ CREATE_HISTORY }}}, ApplyPluginsType } from '{{{ runtimePath }}}';
import { plugin } from '@@/core/coreExports';
import { plugin } from '@@/core/plugin';
export function getRoutes() {
const routes = {{{ routes }}};

View File

@ -4,6 +4,7 @@ import { access as accessApi, pinia } from '@fesjs/fes';
import PageLoading from '@/components/PageLoading';
import UserCenter from '@/components/UserCenter';
import { useStore } from '@/store/main';
import { ref } from 'vue';
export const beforeRender = {
loading: <PageLoading />,
@ -24,17 +25,16 @@ export const beforeRender = {
}
};
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;
}
}
};
};
export const layout = initialValue => ({
...initialValue,
customHeader: <UserCenter />,
menus: (defaultMenuData) => {
const menusRef = ref(defaultMenuData);
// watch(() => initialValue.initialState.userName, () => {
// menusRef.value = [{
// name: 'store'
// }];
// });
return menusRef;
}
});

View File

@ -24,6 +24,7 @@ export default {
MonacoEditor
},
setup() {
console.log('editor.vue');
const editorRef = ref();
const json = ref('');
const language = ref('json');

View File

@ -7,12 +7,18 @@
<script>
import { FButton } from '@fesjs/fes-design';
import { useModel } from '@fesjs/fes';
export default {
components: {
FButton
},
setup() {
const initialState = useModel('@@initialState');
setTimeout(() => {
initialState.userName = '1';
}, 1000);
console.log('index.vue');
return {
};
}

View File

@ -28,6 +28,7 @@ import { MUTATION_TYPES, GETTER_TYPES, ACTION_TYPES } from '@fesjs/fes';
export default {
setup() {
console.log('store.vue');
const store = useStore();
console.log('store==>', store);
const disabled = ref(false);