mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-06 03:59:53 +08:00
feat: 优化plugin-access和plugin-layout
1. access插件提供找不到页面时的处理器 2. 文件路径转路由路径优化 3. 提供按需加载插件配置dynamicImport
This commit is contained in:
parent
7460db344f
commit
14c00a8ffc
@ -73,7 +73,6 @@ export default {
|
|||||||
locale.setLocale({ lang: 'en-US' });
|
locale.setLocale({ lang: 'en-US' });
|
||||||
locale.addLocale({ lang: 'ja-JP', messages: { test: 'テスト' } });
|
locale.addLocale({ lang: 'ja-JP', messages: { test: 'テスト' } });
|
||||||
console.log(locale.getAllLocales());
|
console.log(locale.getAllLocales());
|
||||||
access.addAccess('/onepiece1');
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
accessId.value = '11';
|
accessId.value = '11';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { reactive, computed, inject } from "vue";
|
import { reactive, unref, computed, inject } from "vue";
|
||||||
import createDirective from "./createDirective";
|
import createDirective from "./createDirective";
|
||||||
import createComponent from "./createComponent";
|
import createComponent from "./createComponent";
|
||||||
|
|
||||||
@ -56,12 +56,9 @@ const setAccess = (accessIds) => {
|
|||||||
state.currentAccessIds = accessIds;
|
state.currentAccessIds = accessIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAccess = (accessId) => {
|
const getAccess = () => {
|
||||||
if (typeof accessId !== 'string') {
|
return state.currentAccessIds.slice(0)
|
||||||
throw new Error("[plugin-access]: argument to the addAccess() must be string");
|
}
|
||||||
}
|
|
||||||
state.currentAccessIds.push(accessId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _syncSetRoleId = (promise) => {
|
const _syncSetRoleId = (promise) => {
|
||||||
rolePromiseList.push(promise);
|
rolePromiseList.push(promise);
|
||||||
@ -116,12 +113,12 @@ const match = (path, accessIds) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasLoading = () => {
|
const isDataReady = () => {
|
||||||
return rolePromiseList.length || accessPromiseList.length;
|
return rolePromiseList.length || accessPromiseList.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAccess = async (path) => {
|
const hasAccess = async (path) => {
|
||||||
if (!hasLoading()) {
|
if (!isDataReady()) {
|
||||||
return match(path, getAllowAccessIds());
|
return match(path, getAllowAccessIds());
|
||||||
}
|
}
|
||||||
await Promise.all(rolePromiseList.concat(accessPromiseList));
|
await Promise.all(rolePromiseList.concat(accessPromiseList));
|
||||||
@ -132,7 +129,7 @@ export const install = (app) => {
|
|||||||
const allowPageIds = computed(getAllowAccessIds);
|
const allowPageIds = computed(getAllowAccessIds);
|
||||||
const useAccess = (path) => {
|
const useAccess = (path) => {
|
||||||
const result = computed(() => {
|
const result = computed(() => {
|
||||||
return match(path, allowPageIds.value);
|
return match(unref(path), allowPageIds.value);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@ -143,10 +140,10 @@ export const install = (app) => {
|
|||||||
|
|
||||||
export const access = {
|
export const access = {
|
||||||
hasAccess,
|
hasAccess,
|
||||||
hasLoading,
|
isDataReady,
|
||||||
setRole,
|
setRole,
|
||||||
setAccess,
|
setAccess,
|
||||||
addAccess
|
getAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccess = (path) => {
|
export const useAccess = (path) => {
|
||||||
|
@ -3,6 +3,18 @@ import { access, install } from './core';
|
|||||||
|
|
||||||
export function onRouterCreated({ router }) {
|
export function onRouterCreated({ router }) {
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const runtimeConfig = plugin.applyPlugins({
|
||||||
|
key: 'access',
|
||||||
|
type: ApplyPluginsType.modify,
|
||||||
|
initialValue: {}
|
||||||
|
});
|
||||||
|
if (to.matched.length === 0) {
|
||||||
|
if (runtimeConfig.noFoundHandler && typeof runtimeConfig.noFoundHandler === 'function') {
|
||||||
|
return runtimeConfig.noFoundHandler({
|
||||||
|
router, to, from, next
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
let path;
|
let path;
|
||||||
if (to.matched.length === 1) {
|
if (to.matched.length === 1) {
|
||||||
path = to.matched[0].path;
|
path = to.matched[0].path;
|
||||||
@ -11,21 +23,14 @@ export function onRouterCreated({ router }) {
|
|||||||
}
|
}
|
||||||
const canRoute = await access.hasAccess(path);
|
const canRoute = await access.hasAccess(path);
|
||||||
if (canRoute) {
|
if (canRoute) {
|
||||||
next();
|
return next();
|
||||||
} else {
|
|
||||||
const runtimeConfig = plugin.applyPlugins({
|
|
||||||
key: 'access',
|
|
||||||
type: ApplyPluginsType.modify,
|
|
||||||
initialValue: {}
|
|
||||||
});
|
|
||||||
if (runtimeConfig.noAccessHandler && typeof runtimeConfig.noAccessHandler === 'function') {
|
|
||||||
runtimeConfig.noAccessHandler({
|
|
||||||
router, to, from, next
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (runtimeConfig.unAccessHandler && typeof runtimeConfig.unAccessHandler === 'function') {
|
||||||
|
return runtimeConfig.unAccessHandler({
|
||||||
|
router, to, from, next
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,29 +7,29 @@ if (!useAccess) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAccess = (item) => {
|
export const hasAccessByMenuItem = (item) => {
|
||||||
let res;
|
let res;
|
||||||
if (item.path && (!item.children || item.children.length === 0)) {
|
if (item.path && (!item.children || item.children.length === 0)) {
|
||||||
res = useAccess(item.path);
|
res = useAccess(item.path);
|
||||||
} else if (item.children && item.children.length > 0) {
|
} else if (item.children && item.children.length > 0) {
|
||||||
res = computed(() => item.children.some(child => hasAccess(child)));
|
res = computed(() => item.children.some(child => hasAccessByMenuItem(child)));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAcessTag = (arr) => {
|
const _addAccessTag = (arr) => {
|
||||||
if (Array.isArray(arr)) {
|
if (Array.isArray(arr)) {
|
||||||
arr.forEach((item) => {
|
arr.forEach((item) => {
|
||||||
item.access = hasAccess(item);
|
item.access = hasAccessByMenuItem(item);
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
addAcessTag(item.children);
|
_addAccessTag(item.children);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function (menus) {
|
export const addAccessTag = (menus) => {
|
||||||
const originData = unref(menus);
|
const originData = unref(menus);
|
||||||
addAcessTag(originData);
|
_addAccessTag(originData);
|
||||||
return originData;
|
return originData;
|
||||||
}
|
};
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-menu
|
<a-menu
|
||||||
:selectedKeys="selectedKeys"
|
:selectedKeys="selectedKeys"
|
||||||
@click="onMenuClick"
|
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
mode="inline"
|
mode="inline"
|
||||||
|
@click="onMenuClick"
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in menus" :key="index">
|
<template v-for="(item, index) in fixedMenus" :key="index">
|
||||||
<template v-if="item.access">
|
<template v-if="item.access">
|
||||||
<a-sub-menu v-if="item.children" :title="item.title">
|
<a-sub-menu v-if="item.children" :title="item.title">
|
||||||
<template
|
<template
|
||||||
@ -52,7 +52,7 @@ import 'ant-design-vue/lib/menu/style';
|
|||||||
import {
|
import {
|
||||||
UserOutlined
|
UserOutlined
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import addAccessTag from '../helpers/addAccessTag';
|
import { addAccessTag } from '../helpers/pluginAccess';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -93,7 +93,7 @@ export default {
|
|||||||
const selectedKeys = computed(() => [route.path]);
|
const selectedKeys = computed(() => [route.path]);
|
||||||
return {
|
return {
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
menus: fixedMenus,
|
fixedMenus,
|
||||||
onMenuClick
|
onMenuClick
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-tabs
|
<a-tabs
|
||||||
:activeKey="route.path"
|
:activeKey="route.path"
|
||||||
@tabClick="switchPage"
|
|
||||||
class="layout-content-tabs"
|
class="layout-content-tabs"
|
||||||
hide-add
|
hide-add
|
||||||
type="editable-card"
|
type="editable-card"
|
||||||
|
@tabClick="switchPage"
|
||||||
>
|
>
|
||||||
<a-tab-pane v-for="page in pageList" :key="page.path" closable>
|
<a-tab-pane v-for="page in pageList" :key="page.path" closable>
|
||||||
<template #tab>
|
<template #tab>
|
||||||
{{page.name}}
|
{{page.name}}
|
||||||
<ReloadOutlined
|
<ReloadOutlined
|
||||||
v-show="route.path === page.path"
|
v-show="route.path === page.path"
|
||||||
@click="reloadPage(page.path)"
|
|
||||||
class="layout-tabs-close-icon"
|
class="layout-tabs-close-icon"
|
||||||
|
@click="reloadPage(page.path)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</a-tabs>
|
</a-tabs>
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :key="getPageKey(route)" :is="Component" />
|
<component :is="Component" :key="getPageKey(route)" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,6 +42,7 @@ export default function () {
|
|||||||
require.resolve('./plugins/features/nodeModulesTransform'),
|
require.resolve('./plugins/features/nodeModulesTransform'),
|
||||||
require.resolve('./plugins/features/vueLoader'),
|
require.resolve('./plugins/features/vueLoader'),
|
||||||
require.resolve('./plugins/features/mock'),
|
require.resolve('./plugins/features/mock'),
|
||||||
|
require.resolve('./plugins/features/dynamicImport'),
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
require.resolve('./plugins/misc/route'),
|
require.resolve('./plugins/misc/route'),
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
export default (api) => {
|
||||||
|
api.describe({
|
||||||
|
key: 'dynamicImport',
|
||||||
|
config: {
|
||||||
|
schema(joi) {
|
||||||
|
return joi.boolean();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
};
|
@ -57,14 +57,15 @@ const getRoutePath = function (parentRoutePath, fileName) {
|
|||||||
if (fileName === 'index') {
|
if (fileName === 'index') {
|
||||||
fileName = '';
|
fileName = '';
|
||||||
}
|
}
|
||||||
let routePath = posix.join(parentRoutePath, fileName);
|
|
||||||
// /@id.vue -> /:id
|
// /@id.vue -> /:id
|
||||||
routePath = routePath.replace(/@/g, ':');
|
if (fileName.startsWith('@')) {
|
||||||
// /*.vue -> *
|
fileName = fileName.replace(/@/, ':');
|
||||||
if (routePath === '/*') {
|
|
||||||
routePath = '*';
|
|
||||||
}
|
}
|
||||||
return routePath;
|
// /*.vue -> :pathMatch(.*)
|
||||||
|
if (fileName.includes('*')) {
|
||||||
|
fileName = fileName.replace('*', ':pathMatch(.*)');
|
||||||
|
}
|
||||||
|
return posix.join(parentRoutePath, fileName);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO 约定 layout 目录作为布局文件夹,
|
// TODO 约定 layout 目录作为布局文件夹,
|
||||||
@ -140,7 +141,7 @@ const genRoutes = function (parentRoutes, path, parentRoutePath, config) {
|
|||||||
|
|
||||||
* @param {*} routes
|
* @param {*} routes
|
||||||
*/
|
*/
|
||||||
const fix = function (routes) {
|
const rank = function (routes) {
|
||||||
routes.forEach((item) => {
|
routes.forEach((item) => {
|
||||||
const path = item.path;
|
const path = item.path;
|
||||||
let arr = path.split('/');
|
let arr = path.split('/');
|
||||||
@ -150,9 +151,9 @@ const fix = function (routes) {
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
arr.forEach((sonPath) => {
|
arr.forEach((sonPath) => {
|
||||||
count += 4;
|
count += 4;
|
||||||
if (sonPath.indexOf(':') !== -1) {
|
if (sonPath.indexOf(':') !== -1 && sonPath.indexOf(':pathMatch(.*)') === -1) {
|
||||||
count += 2;
|
count += 2;
|
||||||
} else if (sonPath.indexOf('*') !== -1) {
|
} else if (sonPath.indexOf(':pathMatch(.*)') !== -1) {
|
||||||
count -= 1;
|
count -= 1;
|
||||||
} else if (sonPath === '') {
|
} else if (sonPath === '') {
|
||||||
count += 1;
|
count += 1;
|
||||||
@ -162,7 +163,7 @@ const fix = function (routes) {
|
|||||||
});
|
});
|
||||||
item.count = count;
|
item.count = count;
|
||||||
if (item.children && item.children.length) {
|
if (item.children && item.children.length) {
|
||||||
fix(item.children);
|
rank(item.children);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
routes = routes.sort((a, b) => b.count - a.count);
|
routes = routes.sort((a, b) => b.count - a.count);
|
||||||
@ -175,7 +176,7 @@ const getRoutes = function ({ config, absPagesPath }) {
|
|||||||
|
|
||||||
const routes = [];
|
const routes = [];
|
||||||
genRoutes(routes, absPagesPath, '/', config);
|
genRoutes(routes, absPagesPath, '/', config);
|
||||||
fix(routes);
|
rank(routes);
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ export default {
|
|||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
access: {
|
access: {
|
||||||
roles: {
|
roles: {
|
||||||
admin: ["/", "/onepiece", '/store']
|
admin: ["/"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
@ -52,5 +52,6 @@ export default {
|
|||||||
},
|
},
|
||||||
vuex: {
|
vuex: {
|
||||||
strict: true
|
strict: true
|
||||||
}
|
},
|
||||||
|
dynamicImport: true
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
import { access } from '@webank/fes';
|
import { access as accessApi } from '@webank/fes';
|
||||||
import PageLoading from '@/components/PageLoading';
|
import PageLoading from '@/components/PageLoading';
|
||||||
import UserCenter from '@/components/UserCenter';
|
import UserCenter from '@/components/UserCenter';
|
||||||
|
|
||||||
export const beforeRender = {
|
export const beforeRender = {
|
||||||
loading: <PageLoading />,
|
loading: <PageLoading />,
|
||||||
action() {
|
action() {
|
||||||
const { setRole } = access;
|
const { setRole } = accessApi;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setRole('admin');
|
setRole('admin');
|
||||||
@ -22,3 +22,24 @@ export const beforeRender = {
|
|||||||
export const layout = {
|
export const layout = {
|
||||||
customHeader: <UserCenter />
|
customHeader: <UserCenter />
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const access = {
|
||||||
|
unAccessHandler({ to, next }) {
|
||||||
|
const accesssIds = accessApi.getAccess();
|
||||||
|
if (to.path === '/404') {
|
||||||
|
accessApi.setAccess(accesssIds.concat(['/404']));
|
||||||
|
return next('/404');
|
||||||
|
}
|
||||||
|
if (!accesssIds.includes('/403')) {
|
||||||
|
accessApi.setAccess(accesssIds.concat(['/403']));
|
||||||
|
}
|
||||||
|
next('/403');
|
||||||
|
},
|
||||||
|
noFoundHandler({ next }) {
|
||||||
|
const accesssIds = accessApi.getAccess();
|
||||||
|
if (!accesssIds.includes('/404')) {
|
||||||
|
accessApi.setAccess(accesssIds.concat(['/404']));
|
||||||
|
}
|
||||||
|
next('/404');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
25
packages/fes-template/src/pages/403.vue
Normal file
25
packages/fes-template/src/pages/403.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<a-result status="403" title="403" sub-title="Sorry, you are not authorized to access this page.">
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary">上一页</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</template>
|
||||||
|
<config>
|
||||||
|
{
|
||||||
|
"layout": false
|
||||||
|
}
|
||||||
|
</config>
|
||||||
|
<script>
|
||||||
|
import Result from 'ant-design-vue/lib/result';
|
||||||
|
import 'ant-design-vue/lib/result/style';
|
||||||
|
import Button from 'ant-design-vue/lib/button';
|
||||||
|
import 'ant-design-vue/lib/button/style';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
[Result.name]: Result,
|
||||||
|
[Button.name]: Button
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
25
packages/fes-template/src/pages/404.vue
Normal file
25
packages/fes-template/src/pages/404.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<a-result status="404" title="404" sub-title="Sorry, the page you visited does not exist.">
|
||||||
|
<template #extra>
|
||||||
|
<a-button type="primary">上一页</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</template>
|
||||||
|
<config>
|
||||||
|
{
|
||||||
|
"layout": false
|
||||||
|
}
|
||||||
|
</config>
|
||||||
|
<script>
|
||||||
|
import Result from 'ant-design-vue/lib/result';
|
||||||
|
import 'ant-design-vue/lib/result/style';
|
||||||
|
import Button from 'ant-design-vue/lib/button';
|
||||||
|
import 'ant-design-vue/lib/button/style';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
[Result.name]: Result,
|
||||||
|
[Button.name]: Button
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -69,12 +69,10 @@ export default {
|
|||||||
locale.setLocale({ lang: 'en-US' });
|
locale.setLocale({ lang: 'en-US' });
|
||||||
locale.addLocale({ lang: 'ja-JP', messages: { test: 'テスト' } });
|
locale.addLocale({ lang: 'ja-JP', messages: { test: 'テスト' } });
|
||||||
console.log(locale.getAllLocales());
|
console.log(locale.getAllLocales());
|
||||||
access.addAccess('/onepiece1');
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
accessId.value = '11';
|
accessId.value = '11';
|
||||||
}, 4000);
|
}, 4000);
|
||||||
// router.push('/onepiece');
|
|
||||||
|
|
||||||
console.log('测试 mock!!');
|
console.log('测试 mock!!');
|
||||||
request('/v2/file').then((data) => {
|
request('/v2/file').then((data) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user