laobinghu 4df5c13976 refactor: simplify dark mode with VueUse + Element Plus
Remove custom CSS variable overrides, use Element Plus built-in dark mode with VueUse useDark
2026-03-22 16:22:35 +08:00

353 lines
9.1 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.

<template>
<div class="users-container">
<el-card>
<template #header>
<div class="card-header">
<span>用户管理</span>
<el-button type="primary" @click="showAddDialog = true">添加用户</el-button>
</div>
</template>
<el-form :inline="true" class="filter-form">
<el-form-item label="状态">
<el-select v-model="filters.status" placeholder="全部" clearable @change="loadUsers">
<el-option label="正常" value="active" />
<el-option label="停用" value="inactive" />
<el-option label="封禁" value="banned" />
</el-select>
</el-form-item>
</el-form>
<el-table :data="users" border stripe v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="realName" label="姓名" />
<el-table-column prop="roleName" label="角色" width="120">
<template #default="{ row }">
<el-tag :type="row.roleName === 'admin' ? 'danger' : 'info'">
{{ row.roleName }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="lastLogin" label="最后登录" width="180">
<template #default="{ row }">
{{ row.lastLogin ? new Date(row.lastLogin).toLocaleString('zh-CN') : '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="editUser(row)">编辑</el-button>
<el-button
type="danger"
size="small"
@click="deleteUser(row)"
:disabled="row.roleName === 'admin'"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="showAddDialog" title="添加用户" width="500px">
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
<div class="form-tip">密码至少6位不能为纯数字</div>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱(可选)" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名(可选)" />
</el-form-item>
<el-form-item label="角色" prop="roleId">
<el-select v-model="form.roleId" placeholder="请选择角色">
<el-option
v-for="role in roles"
:key="role.id"
:label="role.description || role.name"
:value="role.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'default'
})
const users = ref<any[]>([])
const roles = ref<any[]>([])
const loading = ref(false)
const showAddDialog = ref(false)
const filters = ref({ status: '' })
const form = ref({
id: null as number | null,
username: '',
password: '',
email: '',
realName: '',
roleId: null as number | null
})
const formRef = ref()
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名2-20个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
],
roleId: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
}
const getStatusType = (status: string) => {
const types: Record<string, string> = {
active: 'success',
inactive: 'warning',
banned: 'danger'
}
return types[status] || 'info'
}
const getStatusText = (status: string) => {
const texts: Record<string, string> = {
active: '正常',
inactive: '停用',
banned: '封禁'
}
return texts[status] || status
}
const loadUsers = async () => {
const token = localStorage.getItem('token')
if (!token) {
ElMessage.warning('请先登录')
navigateTo('/')
return
}
loading.value = true
try {
const params = new URLSearchParams()
if (filters.value.status) params.append('status', filters.value.status)
const res = await $fetch(`/api/admin/users?${params}`, {
headers: { Authorization: `Bearer ${token}` }
})
users.value = res.data
} catch (error: any) {
ElMessage.error(error.data?.message || '加载用户失败')
} finally {
loading.value = false
}
}
const loadRoles = async () => {
const token = localStorage.getItem('token')
if (!token) return
try {
const res = await $fetch('/api/admin/roles', {
headers: { Authorization: `Bearer ${token}` }
})
roles.value = res.data.roles
} catch (error) {
console.error('Failed to load roles:', error)
}
}
const editUser = (user: any) => {
form.value = {
id: user.id,
username: user.username,
password: '',
email: user.email || '',
realName: user.realName || '',
roleId: user.roleId
}
showAddDialog.value = true
}
const deleteUser = async (user: any) => {
const token = localStorage.getItem('token')
if (!token) return
try {
await ElMessageBox.confirm(`确定删除用户 "${user.username}" 吗?`, '提示', {
type: 'warning'
})
await $fetch(`/api/admin/users/${user.id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` }
})
ElMessage.success('删除成功')
loadUsers()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.data?.message || '删除失败')
}
}
}
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
const token = localStorage.getItem('token')
if (!token) return
try {
if (form.value.id) {
await $fetch(`/api/admin/users/${form.value.id}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${token}` },
body: {
email: form.value.email,
realName: form.value.realName,
roleId: form.value.roleId
}
})
ElMessage.success('更新成功')
} else {
await $fetch('/api/admin/users', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form.value
})
ElMessage.success('添加成功')
}
showAddDialog.value = false
resetForm()
loadUsers()
} catch (error: any) {
ElMessage.error(error.data?.message || '操作失败')
}
})
}
const resetForm = () => {
form.value = {
id: null,
username: '',
password: '',
email: '',
realName: '',
roleId: null
}
formRef.value?.resetFields()
}
onMounted(() => {
loadUsers()
loadRoles()
})
</script>
<style scoped>
.users-container {
max-width: 1200px;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header span {
color: var(--el-text-color-primary);
font-size: 18px;
font-weight: 600;
}
.filter-form {
margin-bottom: 20px;
}
.form-tip {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 4px;
}
/* 确保表格和表单在暗色模式下文字清晰 */
:deep(.el-card) {
background-color: var(--header-bg);
color: var(--el-text-color-primary);
}
:deep(.el-card .el-card__header) {
border-bottom-color: var(--el-border-color);
}
:deep(.el-form-item__label) {
color: var(--el-text-color-primary) !important;
}
:deep(.el-table) {
color: var(--el-text-color-primary);
}
:deep(.el-table th) {
background-color: var(--header-bg);
color: var(--el-text-color-primary);
}
:deep(.el-table tr) {
background-color: var(--header-bg);
}
:deep(.el-table td) {
color: var(--el-text-color-primary);
}
@media (max-width: 900px) {
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.filter-form :deep(.el-form-item) {
margin-right: 0;
width: 100%;
}
.filter-form :deep(.el-select) {
width: 100%;
}
}
</style>