Files
crawler-plugin/admin-frontend-vue/src/pages/account/user-menu-auth.ts
T
huangzd1997 898fae4cb3 align(用户菜单授权): 创建/编辑用户菜单权限补齐前端客户端(app)菜单并按类型分区落库
用户管理菜单权限此前只含后台 admin(单类型):前端授权树/回显仅拉 menuType=admin,
后端 replaceDirectPermissions 也固定用 ADMIN 作用域写。现改为同时覆盖 后台(admin)+前端客户端(app):
- 前端 user-menu-auth 节点携带 type,api 并行拉取 admin/app 两类可授权树与已授权 id 合并去重,树内两类根并存(账号与权限/视频/前端工具/运营工具/后勤工具等)
- 后端 PermissionMenuService 新增 resolveMenuTypes(按 id 解析类型);AdminUserService create/update 把勾选 id 拆 admin/app 分区整树替换(internal 不纳入)
- 前端 1556 测试全绿 + build 通过;后端单测(AdminUserServiceTest/PermissionMenuServiceTest/Controller/PermissionMenuResolveTypeTest) EXIT 0
- 本地 E2E:创建用户写入 admin=7+app=3 → 按类型回读 admin[7]/app[3],删除正常
2026-09-07 02:43:36 +08:00

108 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 用户菜单授权数据解析模型(任务 49):permission-menus 扁平项 → 嵌套树 + 用户已授权 id。 */
import { unwrap } from '../../api/envelope.ts'
/** 后台 Vue 控制台菜单类型(与 /api/admin/current-user/menus 一致)。 */
export const ADMIN_MENU_TYPE = 'admin'
/** 前端客户端菜单类型(桌面端功能模块,如视频/前端工具/运营工具/后勤工具)。 */
export const APP_MENU_TYPE = 'app'
/** 授权用可点选菜单节点(携带后端 numeric column id,用于提交授权)。 */
export interface MenuOptionNode {
id: number
/** column_key,稳定权限标识;缺省用 id 字符串兜底。 */
key: string
name: string
sort: number
parentId: number | null
/** 所属菜单类型(admin 后台 / app 前端客户端),提交时按类型分区落库。 */
type: string
children?: MenuOptionNode[]
}
function numberOrNull(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : null
}
/** 把单条 PermissionMenuItemVo(snake_case) 归一为授权节点;缺 id 视为无效。 */
export function parsePermissionMenuItem(raw: unknown, type = ''): MenuOptionNode | null {
if (!raw || typeof raw !== 'object') return null
const record = raw as Record<string, unknown>
const id = numberOrNull(record.id)
if (id === null) return null
const parentId = numberOrNull(record.parent_id)
const sortRaw = numberOrNull(record.sort_order)
const columnKey = typeof record.column_key === 'string' ? record.column_key.trim() : ''
return {
id,
key: columnKey || String(id),
name: typeof record.name === 'string' && record.name.trim() ? record.name.trim() : '未命名菜单',
sort: sortRaw === null ? 0 : sortRaw,
parentId: parentId === null ? null : parentId,
type,
}
}
/** 归一化 permission-menus 响应(信封或裸数组)为扁平授权节点列表。 */
export function parseMenuOptionList(payload: unknown, type = ''): MenuOptionNode[] {
const data = unwrap<unknown>(payload)
if (!Array.isArray(data)) return []
return data
.map((raw) => parsePermissionMenuItem(raw, type))
.filter((node): node is MenuOptionNode => node !== null)
}
/** 按 parent_id 组装嵌套树;同级按 (sort,key) 升序,父缺失的节点按根处理。 */
export function buildMenuOptionTree(items: MenuOptionNode[]): MenuOptionNode[] {
const nodes = new Map<number, MenuOptionNode>()
for (const item of items) {
nodes.set(item.id, { ...item, children: [] })
}
const roots: MenuOptionNode[] = []
for (const item of items) {
const node = nodes.get(item.id)
if (!node) continue
const parent = item.parentId === null ? undefined : nodes.get(item.parentId)
if (parent) {
parent.children!.push(node)
} else {
roots.push(node)
}
}
const order = (a: MenuOptionNode, b: MenuOptionNode) =>
a.sort - b.sort || a.key.localeCompare(b.key)
const sortRecursive = (nodes: MenuOptionNode[]) => {
nodes.sort(order)
for (const node of nodes) node.children?.length && sortRecursive(node.children)
}
sortRecursive(roots)
for (const item of items) {
const node = nodes.get(item.id)
if (node && !node.children?.length) delete node.children
}
return roots
}
/** 解包用户已授权 columnIdsdata.columnIds)→ 去重升序正整数。 */
export function parseUserGrantedColumnIds(payload: unknown): number[] {
const data = unwrap<unknown>(payload)
const record = data && typeof data === 'object' ? (data as Record<string, unknown>) : null
if (!record || !Array.isArray(record.columnIds)) return []
return normalizeMenuIds(record.columnIds)
}
/** 任意输入 id 集合归一为去重升序正整数(镜像后端 normalizeColumnIds)。 */
export function normalizeMenuIds(raw: unknown): number[] {
if (!Array.isArray(raw)) return []
const ids = new Set<number>()
for (const value of raw) {
const id = numberOrNull(value)
if (id !== null && id >= 1) ids.add(id)
}
return [...ids].sort((a, b) => a - b)
}
/** 提交授权请求体(任务 50):{columnIds} 与 Java UserColumnPermissionUpdateRequest 对齐。 */
export function buildUserColumnPermissionPayload(raw: unknown): { columnIds: number[] } {
return { columnIds: normalizeMenuIds(raw) }
}