完善平台角色菜单树与授权登录导航
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 功能:校验非 root 账号登录落点、历史地址恢复与零菜单降级行为。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript';
|
||||
|
||||
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(currentDirectory, '..');
|
||||
const source = fs.readFileSync(
|
||||
path.join(root, 'src/router/authorized-landing.ts'),
|
||||
'utf8',
|
||||
);
|
||||
const compiled = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.ESNext,
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
}).outputText;
|
||||
const landing = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`
|
||||
);
|
||||
|
||||
const menuTree = [
|
||||
{
|
||||
name: 'finance',
|
||||
meta: { menuCode: 'finance' },
|
||||
children: [
|
||||
{
|
||||
name: 'finance-hidden',
|
||||
meta: { menuCode: 'finance', hideInMenu: true },
|
||||
},
|
||||
{
|
||||
name: 'finance-payments',
|
||||
meta: { menuCode: 'fin_payment' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(landing.findFirstAuthorizedMenuRoute(menuTree), {
|
||||
name: 'finance-payments',
|
||||
});
|
||||
assert.equal(landing.findFirstAuthorizedMenuRoute([]), null);
|
||||
|
||||
function fakeRouter(routeByTarget) {
|
||||
return {
|
||||
resolve(target) {
|
||||
return routeByTarget(String(target.name ?? target.path ?? ''));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const router = fakeRouter((target) => {
|
||||
if (target === 'finance') {
|
||||
return {
|
||||
name: 'finance',
|
||||
meta: { menuCode: 'finance' },
|
||||
matched: [{ redirect: '/finance/payments', children: [{}] }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: target,
|
||||
meta: {
|
||||
menuCode:
|
||||
target === 'finance-payments' ? 'fin_payment' : 'dashboard_overview',
|
||||
},
|
||||
matched: [{ children: [] }],
|
||||
};
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
landing.resolveAuthorizedLanding(
|
||||
router,
|
||||
menuTree,
|
||||
'finance_operator',
|
||||
['finance', 'fin_payment'],
|
||||
{ name: 'finance-payments' },
|
||||
),
|
||||
{ name: 'finance-payments' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
landing.resolveAuthorizedLanding(
|
||||
router,
|
||||
menuTree,
|
||||
'finance_operator',
|
||||
['finance', 'fin_payment'],
|
||||
{ name: 'dashboard-overview' },
|
||||
),
|
||||
{ name: 'finance-payments' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
landing.resolveAuthorizedLanding(
|
||||
router,
|
||||
menuTree,
|
||||
'finance_operator',
|
||||
['finance', 'fin_payment'],
|
||||
{ name: 'finance' },
|
||||
),
|
||||
{ name: 'finance-payments' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
landing.resolveAuthorizedLanding(router, [], 'empty_role', [], undefined),
|
||||
{ name: 'noPermission' },
|
||||
);
|
||||
|
||||
for (const [relativePath, expected] of [
|
||||
['src/router/constants.ts', "{ name: 'noPermission', children: [] }"],
|
||||
['src/router/routes/base.ts', "path: '/no-permission'"],
|
||||
[
|
||||
'src/views/login/components/login-form.vue',
|
||||
'resolveAuthorizedLanding(',
|
||||
],
|
||||
[
|
||||
'src/views/no-permission/index.vue',
|
||||
'尚未分配菜单权限',
|
||||
],
|
||||
]) {
|
||||
const content = fs.readFileSync(path.join(root, relativePath), 'utf8');
|
||||
assert.ok(content.includes(expected), `${relativePath} 缺少契约:${expected}`);
|
||||
}
|
||||
|
||||
process.stdout.write('平台账号授权登录落点检查通过\n');
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 功能:静态检查角色菜单树、叶子提交和自身导航读取契约。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const read = (path) => readFileSync(new URL(path, import.meta.url), 'utf8');
|
||||
const resources = read('../src/api/resources.ts');
|
||||
const form = read('../src/views/resource/ResourceFieldForm.vue');
|
||||
const tree = read('../src/views/resource/MenuPermissionTree.vue');
|
||||
const dialog = read('../src/views/resource/ResourceActionDialog.vue');
|
||||
const payload = read('../src/api/resource-form.ts');
|
||||
|
||||
function requireMarker(source, marker, message) {
|
||||
if (!source.includes(marker)) throw new Error(`角色菜单树检查失败:${message}`);
|
||||
}
|
||||
|
||||
requireMarker(resources, "type: 'menu-tree'", '分配菜单未使用专用树字段');
|
||||
requireMarker(form, 'MenuPermissionTree', '通用表单未挂载菜单树');
|
||||
requireMarker(tree, ':indeterminate="groupIndeterminate(group)"', '父菜单缺少半选状态');
|
||||
requireMarker(tree, 'toggleGroup(group, checked === true)', '父菜单缺少全选或全清行为');
|
||||
requireMarker(tree, 'group.name }} / {{ child.name', '子菜单未显示完整路径');
|
||||
requireMarker(payload, "field.type === 'menu-tree'", '空叶子数组不能稳定提交');
|
||||
requireMarker(dialog, '确认清空菜单权限', '清空全部菜单前缺少明确警告');
|
||||
|
||||
console.log('角色菜单树静态检查通过');
|
||||
Reference in New Issue
Block a user