From 61fe9ef1435e1d7d00ce20cfc91678a00b5e0bdd Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Mon, 22 Jun 2026 14:31:41 +0800 Subject: [PATCH] =?UTF-8?q?style(projects):=20=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.prod | 2 +- src/typings/components.d.ts | 3 - .../_builtin/login/modules/pwd-login.vue | 4 +- .../role/modules/role-resource-panel.vue | 19 +---- .../system/role/modules/role-resource-tree.ts | 85 +------------------ tests/role-resource-tree.test.ts | 59 ------------- 6 files changed, 10 insertions(+), 162 deletions(-) diff --git a/.env.prod b/.env.prod index b813430..2a6578b 100644 --- a/.env.prod +++ b/.env.prod @@ -1,5 +1,5 @@ # 生产环境的后端服务地址 -VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default +VITE_SERVICE_BASE_URL= # 生产环境下的其他后端服务地址 VITE_OTHER_SERVICE_BASE_URL= `{ diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index c07329c..e4e09ae 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -119,11 +119,9 @@ declare module 'vue' { IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'] IconMdiCheck: typeof import('~icons/mdi/check')['default'] - IconMdiCheckCircleOutline: typeof import('~icons/mdi/check-circle-outline')['default'] IconMdiChevronDoubleDown: typeof import('~icons/mdi/chevron-double-down')['default'] IconMdiChevronDoubleUp: typeof import('~icons/mdi/chevron-double-up')['default'] IconMdiCloseCircle: typeof import('~icons/mdi/close-circle')['default'] - IconMdiCloseCircleOutline: typeof import('~icons/mdi/close-circle-outline')['default'] IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default'] IconMdiDownload: typeof import('~icons/mdi/download')['default'] IconMdiDrag: typeof import('~icons/mdi/drag')['default'] @@ -135,7 +133,6 @@ declare module 'vue' { IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiLinkVariant: typeof import('~icons/mdi/link-variant')['default'] - IconMdiPackageDown: typeof import('~icons/mdi/package-down')['default'] IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline')['default'] IconMdiPlus: typeof import('~icons/mdi/plus')['default'] IconMdiRefresh: typeof import('~icons/mdi/refresh')['default'] diff --git a/src/views/_builtin/login/modules/pwd-login.vue b/src/views/_builtin/login/modules/pwd-login.vue index 0cfa7fe..a646837 100644 --- a/src/views/_builtin/login/modules/pwd-login.vue +++ b/src/views/_builtin/login/modules/pwd-login.vue @@ -15,8 +15,8 @@ interface FormModel { } const model = ref({ - userName: 'admin', - password: 'admin123' + userName: '', + password: '' }); const rules = computed>(() => { diff --git a/src/views/system/role/modules/role-resource-panel.vue b/src/views/system/role/modules/role-resource-panel.vue index 5d596a0..750e0cd 100644 --- a/src/views/system/role/modules/role-resource-panel.vue +++ b/src/views/system/role/modules/role-resource-panel.vue @@ -4,7 +4,7 @@ import type { TreeInstance } from 'element-plus'; import { menuTypeRecord } from '@/constants/business'; import { fetchAssignRoleMenus, fetchGetRoleMenuIds } from '@/service/api'; import { $t } from '@/locales'; -import { normalizeRoleMenuCheckedIds, resolveRoleMenuSubmitIds } from './role-resource-tree'; +import { normalizeRoleMenuCheckedIds } from './role-resource-tree'; defineOptions({ name: 'RoleResourcePanel' }); @@ -28,7 +28,6 @@ const submitting = ref(false); const filterKeyword = ref(''); const checkedKeys = ref([]); const baselineMenuIds = ref([]); -const dirtyMenuIds = ref>(new Set()); const disabled = computed(() => !props.role || props.role.status === 1); const checkedCount = computed(() => checkedKeys.value.length); @@ -107,7 +106,6 @@ function collectExpandableNodeIds(nodes: Api.SystemManage.MenuSimple[]) { async function loadRoleMenus() { if (!props.role) { baselineMenuIds.value = []; - dirtyMenuIds.value = new Set(); await applyCheckedKeys([]); treeRef.value?.filter(filterKeyword.value); return; @@ -121,7 +119,6 @@ async function loadRoleMenus() { if (error) { baselineMenuIds.value = []; - dirtyMenuIds.value = new Set(); await applyCheckedKeys([]); treeRef.value?.filter(filterKeyword.value); return; @@ -133,13 +130,11 @@ async function loadRoleMenus() { }); baselineMenuIds.value = normalizedMenuIds; - dirtyMenuIds.value = new Set(); await applyCheckedKeys(normalizedMenuIds); treeRef.value?.filter(filterKeyword.value); } -function handleCheck(data: Api.SystemManage.MenuSimple) { - dirtyMenuIds.value = new Set([...dirtyMenuIds.value, data.id]); +function handleCheck() { syncCheckedKeys(); } @@ -148,13 +143,8 @@ async function handleSave() { return; } - const checkedMenuIds = (treeRef.value?.getCheckedKeys(false) as string[]) ?? []; - const menuIds = resolveRoleMenuSubmitIds({ - menuTree: props.menuTree, - baselineIds: baselineMenuIds.value, - dirtyIds: [...dirtyMenuIds.value], - checkedIds: checkedMenuIds - }); + // 直接提交树上完整勾选的节点(getCheckedKeys(false) 自动排除半选父节点),保证“所见即所得”。 + const menuIds = (treeRef.value?.getCheckedKeys(false) as string[]) ?? []; submitting.value = true; @@ -170,7 +160,6 @@ async function handleSave() { } baselineMenuIds.value = [...menuIds]; - dirtyMenuIds.value = new Set(); syncCheckedKeys(); window.$message?.success($t('common.modifySuccess')); diff --git a/src/views/system/role/modules/role-resource-tree.ts b/src/views/system/role/modules/role-resource-tree.ts index 7199fcc..2fd5707 100644 --- a/src/views/system/role/modules/role-resource-tree.ts +++ b/src/views/system/role/modules/role-resource-tree.ts @@ -3,13 +3,6 @@ export type RoleResourceTreeNode = { children?: RoleResourceTreeNode[] | null; }; -type ResolveRoleMenuSubmitIdsInput = { - menuTree: RoleResourceTreeNode[]; - baselineIds: string[]; - dirtyIds: string[]; - checkedIds: string[]; -}; - type NormalizeRoleMenuCheckedIdsInput = { menuTree: RoleResourceTreeNode[]; checkedIds: string[]; @@ -17,7 +10,6 @@ type NormalizeRoleMenuCheckedIdsInput = { type TreeIndex = { orderedIds: string[]; - parentById: Map; subtreeIdsById: Map; }; @@ -54,104 +46,33 @@ export function normalizeRoleMenuCheckedIds(input: NormalizeRoleMenuCheckedIdsIn return sortIdsByTreeOrder(treeIndex.orderedIds, normalizedIdSet); } -export function resolveRoleMenuSubmitIds(input: ResolveRoleMenuSubmitIdsInput) { - const baselineIds = normalizeIds(input.baselineIds); - - if (!input.dirtyIds.length) { - return baselineIds; - } - - const treeIndex = buildTreeIndex(input.menuTree); - const affectedIds = collectAffectedIds(treeIndex, baselineIds, normalizeIds(input.dirtyIds)); - - if (!affectedIds.size) { - return baselineIds; - } - - const nextIdSet = new Set(); - - baselineIds.forEach(id => { - if (!affectedIds.has(id)) { - nextIdSet.add(id); - } - }); - - // 半选父节点只用于树态展示,提交它会把整棵子树误当成完整授权。 - normalizeIds(input.checkedIds).forEach(id => { - if (affectedIds.has(id)) { - nextIdSet.add(id); - } - }); - - return sortIdsByTreeOrder(treeIndex.orderedIds, nextIdSet); -} - function normalizeIds(ids: string[]) { return [...new Set(ids.map(id => String(id).trim()).filter(Boolean))]; } function buildTreeIndex(nodes: RoleResourceTreeNode[]) { const orderedIds: string[] = []; - const parentById = new Map(); const subtreeIdsById = new Map(); - const walk = (items: RoleResourceTreeNode[], parentId: string | null) => { + const walk = (items: RoleResourceTreeNode[]) => { items.forEach(item => { orderedIds.push(item.id); - parentById.set(item.id, parentId); - const childIds = item.children?.length ? walk(item.children, item.id) : []; + const childIds = item.children?.length ? walk(item.children) : []; subtreeIdsById.set(item.id, [item.id, ...childIds]); }); return items.flatMap(item => subtreeIdsById.get(item.id) ?? [item.id]); }; - walk(nodes, null); + walk(nodes); return { orderedIds, - parentById, subtreeIdsById } satisfies TreeIndex; } -function collectAffectedIds(treeIndex: TreeIndex, baselineIds: string[], dirtyIds: string[]) { - const affectedIds = new Set(); - const baselineIdSet = new Set(baselineIds); - - dirtyIds.forEach(dirtyId => { - const subtreeIds = treeIndex.subtreeIdsById.get(dirtyId) ?? [dirtyId]; - subtreeIds.forEach(id => affectedIds.add(id)); - - const ancestors = collectAncestorIds(treeIndex.parentById, dirtyId); - - ancestors.forEach(ancestorId => { - if (!baselineIdSet.has(ancestorId)) { - return; - } - - const ancestorSubtreeIds = treeIndex.subtreeIdsById.get(ancestorId) ?? [ancestorId]; - ancestorSubtreeIds.forEach(id => affectedIds.add(id)); - }); - }); - - return affectedIds; -} - -function collectAncestorIds(parentById: Map, nodeId: string) { - const ancestorIds: string[] = []; - - let currentId: string | null | undefined = nodeId; - - while (currentId) { - ancestorIds.push(currentId); - currentId = parentById.get(currentId) ?? null; - } - - return ancestorIds; -} - function sortIdsByTreeOrder(orderedIds: string[], idSet: Set) { const sortedIds: string[] = []; diff --git a/tests/role-resource-tree.test.ts b/tests/role-resource-tree.test.ts index 17091b2..fbeb022 100644 --- a/tests/role-resource-tree.test.ts +++ b/tests/role-resource-tree.test.ts @@ -32,65 +32,6 @@ const menuTree: MenuNode[] = [ const normalizeRoleMenuCheckedIds = (roleResourceTree as { normalizeRoleMenuCheckedIds?: NormalizeRoleMenuCheckedIds }) .normalizeRoleMenuCheckedIds; -const { resolveRoleMenuSubmitIds } = roleResourceTree; - -describe('resolveRoleMenuSubmitIds', () => { - it('keeps original ids when there is no user interaction', () => { - const result = resolveRoleMenuSubmitIds({ - menuTree, - baselineIds: ['weekly', 'monthly'], - dirtyIds: [], - checkedIds: ['personal', 'weekly', 'monthly'] - }); - - assert.deepEqual(result, ['weekly', 'monthly']); - }); - - it('preserves untouched branches when another branch changes', () => { - const result = resolveRoleMenuSubmitIds({ - menuTree, - baselineIds: ['weekly', 'monthly'], - dirtyIds: ['stateMachine'], - checkedIds: ['personal', 'weekly', 'monthly', 'stateMachine'] - }); - - assert.deepEqual(result, ['weekly', 'monthly', 'stateMachine']); - }); - - it('recomputes the whole dirty branch instead of expanding unrelated baseline ids', () => { - const result = resolveRoleMenuSubmitIds({ - menuTree, - baselineIds: ['personal'], - dirtyIds: ['weekly'], - checkedIds: ['personal', 'weekly', 'weeklyDetail'] - }); - - assert.deepEqual(result, ['personal', 'weekly', 'weeklyDetail']); - }); - - it('does not expand untouched sibling branches under the same ancestor', () => { - const result = resolveRoleMenuSubmitIds({ - menuTree, - baselineIds: ['monthly'], - dirtyIds: ['weeklyDetail'], - checkedIds: ['personal', 'weekly', 'weeklyDetail', 'monthly', 'monthlyDetail'] - }); - - assert.deepEqual(result, ['weeklyDetail', 'monthly']); - }); - - it('does not submit half-checked parent ids when a fully authorized branch becomes partial', () => { - const result = resolveRoleMenuSubmitIds({ - menuTree, - baselineIds: ['personal'], - dirtyIds: ['monthlyDetail'], - checkedIds: ['weekly', 'weeklyDetail'] - }); - - assert.deepEqual(result, ['weekly', 'weeklyDetail']); - }); -}); - describe('normalizeRoleMenuCheckedIds', () => { it('removes partially covered parent ids before tree rendering', () => { assert.equal(typeof normalizeRoleMenuCheckedIds, 'function');