Files
hfb_sys/frontend/src/features/admin/views/AdminMgrUsersView.vue
T
yml f410be3797 代码格式化: prettier --write 统一代码风格
39 个文件按 .prettierrc.json (printWidth 100 / no semi / singleQuote) 统一格式, 纯空白与换行调整, 无逻辑变更。为接入代码门禁做准备, 让 npm run check (format:check + lint:strict + typecheck) 干净通过。
2026-06-14 16:24:01 +08:00

211 lines
6.2 KiB
Vue
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.
<script setup lang="ts">
import { readError } from '@/shared/utils/error'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
import {
fetchAdminMgrUsers,
deleteAdminMgrUser,
resetAdminPassword,
type AdminMgrUser,
} from '@/features/admin/api/adminMgr'
import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable'
import { formatDateTime } from '@/shared/utils/time'
import AdminUserDialog from '../components/AdminUserDialog.vue'
import AssignRolesDialog from '../components/AssignRolesDialog.vue'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const showDialog = ref(false)
const editingAdmin = ref<AdminMgrUser | null>(null)
const showRolesDialog = ref(false)
const rolesAdmin = ref<AdminMgrUser | null>(null)
const showPasswordDialog = ref(false)
const passwordAdmin = ref<AdminMgrUser | null>(null)
const passwordForm = ref({ new_password: '' })
const passwordSubmitting = ref(false)
const {
loading,
data: admins,
total,
currentPage,
currentPageSize,
load: loadAdmins,
} = useAdminPaginatedTable<AdminMgrUser>({
fetchFn: fetchAdminMgrUsers,
})
function openCreate() {
editingAdmin.value = null
showDialog.value = true
}
function openEdit(row: AdminMgrUser) {
editingAdmin.value = row
showDialog.value = true
}
function openRoles(row: AdminMgrUser) {
rolesAdmin.value = row
showRolesDialog.value = true
}
function openPassword(row: AdminMgrUser) {
passwordAdmin.value = row
passwordForm.value = { new_password: '' }
showPasswordDialog.value = true
}
async function handleDelete(row: AdminMgrUser) {
try {
await ElMessageBox.confirm(
`确定要删除管理员「${row.username}」吗?此操作不可撤销。`,
'删除确认',
{
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
}
)
await deleteAdminMgrUser(row.id)
ElMessage.success('管理员已删除')
await loadAdmins()
} catch {
// 用户取消
}
}
async function handleChangePassword() {
if (!passwordAdmin.value) return
if (!passwordForm.value.new_password) {
ElMessage.warning('请填写新密码')
return
}
passwordSubmitting.value = true
try {
await resetAdminPassword(passwordAdmin.value.id, passwordForm.value)
ElMessage.success('密码已重置,目标账号需要重新登录')
showPasswordDialog.value = false
} catch (error) {
ElMessage.error(readError(error, '重置失败'))
} finally {
passwordSubmitting.value = false
}
}
const statusLabel: Record<string, string> = {
active: '启用',
disabled: '禁用',
}
</script>
<template>
<section class="page">
<div class="page-header-row">
<div class="page-header">
<p class="eyebrow">Admin Users</p>
<h1>管理员管理</h1>
<p>管理后台管理员账号,分配角色和权限。</p>
</div>
<div class="toolbar-actions">
<el-button @click="loadAdmins">刷新</el-button>
<el-button type="primary" @click="openCreate">新建管理员</el-button>
</div>
</div>
<el-table v-loading="loading" class="table-panel" :data="admins">
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="username" label="用户名" min-width="120" />
<el-table-column prop="nickname" label="昵称" min-width="120" />
<el-table-column label="角色" min-width="200">
<template #default="{ row }">
<el-tag
v-for="role in row.roles"
:key="role.id"
size="small"
style="margin-right: 4px; margin-bottom: 2px"
>
{{ role.name }}
</el-tag>
<span v-if="!row.roles?.length" style="color: #8f9bba">未分配</span>
</template>
</el-table-column>
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
{{ statusLabel[row.status] || row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="最后登录" min-width="170">
<template #default="{ row }">{{
row.last_login_at ? formatDateTime(row.last_login_at) : '-'
}}</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="primary" @click="openRoles(row)">角色</el-button>
<el-button size="small" @click="openPassword(row)">密码</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="loadAdmins"
/>
<!-- 新建/编辑对话框 -->
<AdminUserDialog v-model="showDialog" :admin="editingAdmin" @saved="loadAdmins" />
<!-- 角色分配对话框 -->
<AssignRolesDialog v-model="showRolesDialog" :admin="rolesAdmin" @saved="loadAdmins" />
<!-- 重置密码对话框 -->
<el-dialog
:model-value="showPasswordDialog"
:title="`重置密码 - ${passwordAdmin?.username || ''}`"
width="460px"
@update:model-value="showPasswordDialog = $event"
>
<div class="dialog-body">
<el-form-item label="新密码" class="full-control">
<el-input
v-model="passwordForm.new_password"
type="password"
show-password
placeholder="至少 8 位,包含字母和数字"
/>
</el-form-item>
</div>
<template #footer>
<el-button @click="showPasswordDialog = false">取消</el-button>
<el-button type="primary" :loading="passwordSubmitting" @click="handleChangePassword"
>确认重置</el-button
>
</template>
</el-dialog>
</section>
</template>
<style scoped>
.toolbar-actions {
display: flex;
gap: 8px;
}
.dialog-body {
display: grid;
gap: 16px;
}
.full-control {
width: 100%;
}
</style>