task-51(账号/权限页面): 实现用户删除确认和刷新
新增 user-delete-model.ts 纯模型:deleteConfirmMessage/deleteBlockReason 镜像 Java
deleteUser 约束(不能删自己/超管)、isDeletableUser。users.ts 增 deleteUser DELETE
/api/admin/user/{uid} 并由 unwrap 抛后端 message;UsersPage 删除改走 adapter、二次确认后
刷新列表(超管行直接告警拦截),不再内联 http.delete、不入 session store。
8 用例全过,371 单测 + build 绿。
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { http } from './http'
|
||||
import { unwrap } from './envelope'
|
||||
import { parseUserPage } from './users-model'
|
||||
import {
|
||||
normalizeUserPageParams,
|
||||
@@ -13,3 +14,9 @@ export async function fetchUserList(params: Partial<UserListParams> = {}): Promi
|
||||
const { data } = await http.get('/api/admin/users', { params: toUserListQuery(normalized) })
|
||||
return parseUserPage(data)
|
||||
}
|
||||
|
||||
/** 删除用户:DELETE /api/admin/user/{uid},success=false 由 unwrap 抛后端 message。 */
|
||||
export async function deleteUser(uid: number): Promise<void> {
|
||||
const { data } = await http.delete<unknown>(`/api/admin/user/${uid}`)
|
||||
unwrap<unknown>(data)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { http } from '@/api/http'
|
||||
import type { AdminUser } from '@/types/admin'
|
||||
import { fetchUserList } from '@/api/users'
|
||||
import { deleteUser, fetchUserList } from '@/api/users'
|
||||
import { createUserFilterState, toUserListParams } from './users-filter'
|
||||
import { totalPageCount } from './user-pagination'
|
||||
import { deleteBlockReason } from './user-delete-model'
|
||||
|
||||
const loading = ref(false)
|
||||
const rows = ref<AdminUser[]>([])
|
||||
@@ -40,9 +40,14 @@ function search() {
|
||||
}
|
||||
|
||||
async function removeUser(row: AdminUser) {
|
||||
const reason = deleteBlockReason(row)
|
||||
if (reason) {
|
||||
ElMessage.warning(reason)
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除用户“${row.username}”吗?`, '删除确认', { type: 'warning' })
|
||||
await http.delete(`/api/admin/user/${row.id}`)
|
||||
await deleteUser(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadUsers()
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/** 用户删除确认模型(任务 51):删除确认文案 + 不可删原因,镜像 Java deleteUser 约束。 */
|
||||
import type { AdminUser } from '../../types/admin'
|
||||
|
||||
/** 删除二次确认文案(删除后不可恢复)。 */
|
||||
export function deleteConfirmMessage(user: AdminUser | null | undefined): string {
|
||||
const username = user?.username || ''
|
||||
return `确定删除用户“${username}”吗?删除后不可恢复。`
|
||||
}
|
||||
|
||||
/** 返回不可删除原因;可删除时返回 undefined。 */
|
||||
export function deleteBlockReason(
|
||||
user: AdminUser | null | undefined,
|
||||
currentUserId?: number | null,
|
||||
): string | undefined {
|
||||
if (!user) return '用户信息缺失,无法删除'
|
||||
if (currentUserId != null && user.id === currentUserId) return '不能删除当前登录账号'
|
||||
if ((user.role || '').trim().toLowerCase() === 'super_admin') return '不能删除超级管理员'
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** 该用户当前是否允许删除(非缺失/非自己/非超管)。 */
|
||||
export function isDeletableUser(user: AdminUser | null | undefined, currentUserId?: number | null): boolean {
|
||||
return deleteBlockReason(user, currentUserId) === undefined
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
import { deleteBlockReason, deleteConfirmMessage, isDeletableUser } from '../src/pages/account/user-delete-model.ts'
|
||||
import type { AdminUser } from '../src/types/admin.ts'
|
||||
|
||||
test('test_task_051_user_delete_normal_primary_path', () => {
|
||||
// 正常主路径:非超管且非当前登录账号可删除,确认文案带用户名。
|
||||
const row: AdminUser = { id: 3, username: '运营一', role: 'normal' }
|
||||
assert.equal(isDeletableUser(row, 1), true)
|
||||
assert.equal(deleteBlockReason(row, 1), undefined)
|
||||
assert.match(deleteConfirmMessage(row), /运营一/)
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_normal_variant_input', () => {
|
||||
// 正常变体:admin 角色用户亦可删除(仅超管/自己受限)。
|
||||
assert.equal(isDeletableUser({ id: 4, username: 'mgr', role: 'admin' }, 1), true)
|
||||
assert.equal(deleteConfirmMessage({ id: 4, username: 'mgr', role: 'admin' }), '确定删除用户“mgr”吗?删除后不可恢复。')
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_normal_repeated_operation_is_idempotent', () => {
|
||||
// 正常重复:纯函数结果稳定、不改输入。
|
||||
const row: AdminUser = { id: 5, username: 'u', role: 'normal' }
|
||||
assert.equal(deleteBlockReason(row, 1), deleteBlockReason(row, 1))
|
||||
assert.equal(row.role, 'normal')
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_boundary_empty_input', () => {
|
||||
// 边界空值:缺用户行给可操作原因;空用户名不崩溃。
|
||||
assert.match(deleteBlockReason(null, 1) || '', /用户/)
|
||||
assert.equal(isDeletableUser(null, 1), false)
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_boundary_single_item', () => {
|
||||
// 边界单元素:单条普通用户可删除。
|
||||
assert.equal(isDeletableUser({ id: 7, username: 'a', role: 'normal' }, 2), true)
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_boundary_limit_or_missing_field', () => {
|
||||
// 边界上限/缺当前 id:未带当前 id 时仅超管受限。
|
||||
assert.equal(isDeletableUser({ id: 8, username: 'b', role: 'normal' }, undefined), true)
|
||||
assert.match(deleteBlockReason({ id: 9, username: 'root', role: 'super_admin' }, 1) || '', /超级管理员/)
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_invalid_input_rejected', () => {
|
||||
// 异常输入:不能删自己、不能删超管。
|
||||
assert.match(deleteBlockReason({ id: 1, username: 'me', role: 'normal' }, 1) || '', /当前登录/)
|
||||
assert.equal(isDeletableUser({ id: 1, username: 'me', role: 'normal' }, 1), false)
|
||||
assert.equal(isDeletableUser({ id: 9, username: 'root', role: 'super_admin' }, 2), false)
|
||||
})
|
||||
|
||||
test('test_task_051_user_delete_dependency_failure_returns_actionable_message', () => {
|
||||
// 依赖失败/删除走 adapter:页面不再内联 http.delete;users.ts 负责 DELETE + 解包。
|
||||
const mod = readSource('src/pages/account/user-delete-model.ts')
|
||||
assert.equal(/axios|http\./.test(mod), false, '删除模型保持纯逻辑')
|
||||
const page = readSource('src/pages/account/UsersPage.vue')
|
||||
assert.match(page, /deleteUser/)
|
||||
assert.equal(/http\.delete/.test(page), false, '页面不再内联删除请求')
|
||||
const users = readSource('src/api/users.ts')
|
||||
assert.match(users, /deleteUser/)
|
||||
assert.match(users, /http\.delete/)
|
||||
assert.match(users, /api\/admin\/user/)
|
||||
})
|
||||
Reference in New Issue
Block a user