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 HAS_LOCALE = api.hasPlugins(['@fesjs/plugin-locale']);
// 路由信息
const routes = await api.getRoutes();
// .fes配置 // .fes配置
const userConfig = { const userConfig = {
title: name, title: name,
@ -42,11 +39,7 @@ export default (api) => {
...(api.config.layout || {}) ...(api.config.layout || {})
}; };
// 把路由的meta合并到menu配置中 const iconNames = helper.getIconNamesFromMenu(userConfig.menus);
const menus = helper.fillMenuByRoute(userConfig.menus, routes);
delete userConfig.menus;
const iconNames = helper.getIconNamesFromMenu(menus);
const iconsString = iconNames.map( const iconsString = iconNames.map(
iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'` iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'`
@ -66,7 +59,6 @@ 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
} }
) )

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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