task-21(会话/菜单/权限): 定义 AdminUser/AdminMenuNode 类型契约并对齐后端字段
admin.ts 增加 AdminRole 域与 roleKind/isSuperAdminRole/isAdminRole/roleLabel/ isSuperUser,AdminUser 补齐 isAdmin/creatorUsername/createdAt 等真实字段; 契约覆盖“分组可无 route、页面必有 route”。
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
Reference in New Issue
Block a user