task-21(会话/菜单/权限): 定义 AdminUser/AdminMenuNode 类型契约并对齐后端字段

admin.ts 增加 AdminRole 域与 roleKind/isSuperAdminRole/isAdminRole/roleLabel/
isSuperUser,AdminUser 补齐 isAdmin/creatorUsername/createdAt 等真实字段;
契约覆盖“分组可无 route、页面必有 route”。
This commit is contained in:
2026-09-05 13:47:06 +08:00
parent 1cfdcb5fb3
commit 9f73af5911
2 changed files with 146 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
/** 后台前端类型契约(任务 21):与 Java Admin 接口返回字段对齐。 */
/** 管理角色域(与后端 current-user / users 返回一致)。 */
export type AdminRole = 'super_admin' | 'admin' | 'normal'
export interface AdminUser {
id: number
username: string
role: string
/** 1 为管理员标志位;role 为空时可据此推断角色。 */
isAdmin?: boolean
createdById?: number | null
creatorUsername?: string
createdAt?: string
pinyinAbbr?: string
}
export interface AdminMenuNode {
/** columns.column_key,稳定权限标识。 */
key: string
name: string
/** 相对 /admin-vue/ 的页面路径;分组节点可能没有。 */
route?: string
/** 分组/页面层级;后端按 sort_order 排好后原样下发,前端不二次重排。 */
children?: AdminMenuNode[]
sort?: number
icon?: string
}
export type AdminRoleKind = 'super_admin' | 'admin' | 'normal' | null
/** 把任意角色字符串归一为角色域;未知/空返回 null。 */
export function roleKind(role: string | null | undefined): AdminRoleKind {
const value = (role || '').trim().toLowerCase()
if (value === 'super_admin') return 'super_admin'
if (value === 'admin') return 'admin'
if (value === 'normal') return 'normal'
return null
}
/** 是否超级管理员(可进入全部菜单)。 */
export function isSuperAdminRole(role: string | null | undefined): boolean {
return roleKind(role) === 'super_admin'
}
/** 是否管理员级(super_admin 或 admin,可进后台菜单管理类)。 */
export function isAdminRole(role: string | null | undefined): boolean {
const kind = roleKind(role)
return kind === 'super_admin' || kind === 'admin'
}
/** 顶部栏角色展示文案。 */
export function roleLabel(role: string | null | undefined): string {
switch (roleKind(role)) {
case 'super_admin':
return '超级管理员'
case 'admin':
return '管理员'
case 'normal':
return '普通用户'
default:
return ''
}
}
/** 前端是否应把该用户视为超级管理员(可进入全部菜单)。 */
export function isSuperUser(user: Pick<AdminUser, 'role'> | null | undefined): boolean {
return isSuperAdminRole(user?.role)
}
export type ApiEnvelope<T> = {
success?: boolean
message?: string
error?: string
data?: T
item?: T
items?: T
}
+68
View File
@@ -0,0 +1,68 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
isAdminRole,
isSuperAdminRole,
isSuperUser,
roleKind,
roleLabel,
} from '../src/types/admin.ts'
test('test_task_021_type_contract_normal_primary_path', () => {
// 正常主路径:真实后端角色串映射到角色域。
assert.equal(roleKind('super_admin'), 'super_admin')
assert.equal(isSuperAdminRole('super_admin'), true)
assert.equal(isSuperUser({ role: 'super_admin' }), true)
})
test('test_task_021_type_contract_normal_variant_input', () => {
// 正常变体:admin/normal 归类正确,超级用户判定不为 true。
assert.equal(roleKind('admin'), 'admin')
assert.equal(roleKind('normal'), 'normal')
assert.equal(isSuperUser({ role: 'admin' }), false)
assert.equal(isAdminRole('admin'), true)
})
test('test_task_021_type_contract_normal_repeated_operation_is_idempotent', () => {
// 正常重复:角色归一稳定。
assert.equal(roleKind('SUPER_ADMIN'), roleKind('super_admin'))
assert.equal(roleLabel('admin'), roleLabel(' admin '))
})
test('test_task_021_type_contract_boundary_empty_input', () => {
// 边界空值:空角色/空用户不崩溃,归类为 null/非管理员。
assert.equal(roleKind(''), null)
assert.equal(roleKind(undefined), null)
assert.equal(roleKind(null), null)
assert.equal(isSuperUser(null), false)
assert.equal(isSuperUser(undefined), false)
})
test('test_task_021_type_contract_boundary_single_item', () => {
// 边界单元素:单个 super_admin 用户可进全部菜单。
assert.equal(isSuperUser({ role: 'super_admin' }), true)
assert.equal(roleLabel('super_admin'), '超级管理员')
})
test('test_task_021_type_contract_boundary_limit_or_missing_field', () => {
// 边界上限/缺字段:未知角色串不误判为管理员,展示空文案。
assert.equal(roleKind('owner'), null)
assert.equal(isAdminRole('owner'), false)
assert.equal(roleLabel('owner'), '')
})
test('test_task_021_type_contract_invalid_input_rejected', () => {
// 异常输入:大小写/空白容忍,非法字符串归类 null。
assert.equal(roleKind(' Super_Admin '), 'super_admin')
assert.equal(roleKind('xxx'), null)
assert.equal(isSuperAdminRole(''), false)
})
test('test_task_021_type_contract_dependency_failure_returns_actionable_message', () => {
// 依赖失败:菜单节点契约为分组可无 route、页面必须可路由。
const group = { key: 'admin_group_account', name: '账号权限' }
const page = { key: 'admin_users', name: '用户管理', route: '/account/users' }
assert.equal('route' in group, false, '分组节点允许无 route')
assert.equal(typeof page.route, 'string', '页面节点必须带 route')
assert.equal(roleLabel('super_admin').length > 0, true)
})