Files
AIGC/demo/frontend/src/views/AdminUsers.vue
2025-10-21 16:50:33 +08:00

746 lines
18 KiB
Vue

<template>
<div class="admin-users">
<!-- 页面标题 -->
<div class="page-header">
<h2>
<el-icon><User /></el-icon>
用户管理 - 管理员
</h2>
</div>
<!-- 统计面板 -->
<el-row :gutter="20" class="stats-row">
<el-col :xs="12" :sm="6">
<el-card class="stat-card clickable" @click="handleStatClick('all')">
<div class="stat-content">
<div class="stat-number">{{ stats.totalUsers || 0 }}</div>
<div class="stat-label">总用户数</div>
</div>
<el-icon class="stat-icon" color="#409EFF"><User /></el-icon>
</el-card>
</el-col>
<el-col :xs="12" :sm="6">
<el-card class="stat-card clickable" @click="handleStatClick('admin')">
<div class="stat-content">
<div class="stat-number">{{ stats.adminUsers || 0 }}</div>
<div class="stat-label">管理员</div>
</div>
<el-icon class="stat-icon" color="#67C23A"><UserFilled /></el-icon>
</el-card>
</el-col>
<el-col :xs="12" :sm="6">
<el-card class="stat-card clickable" @click="handleStatClick('user')">
<div class="stat-content">
<div class="stat-number">{{ stats.normalUsers || 0 }}</div>
<div class="stat-label">普通用户</div>
</div>
<el-icon class="stat-icon" color="#E6A23C"><Avatar /></el-icon>
</el-card>
</el-col>
<el-col :xs="12" :sm="6">
<el-card class="stat-card clickable" @click="handleStatClick('today')">
<div class="stat-content">
<div class="stat-number">{{ stats.todayUsers || 0 }}</div>
<div class="stat-label">今日注册</div>
</div>
<el-icon class="stat-icon" color="#F56C6C"><Calendar /></el-icon>
</el-card>
</el-col>
</el-row>
<!-- 筛选和搜索 -->
<el-card class="filter-card">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8">
<el-select
v-model="filters.role"
placeholder="选择用户角色"
clearable
@change="handleFilterChange"
>
<el-option label="全部角色" value="" />
<el-option label="管理员" value="ROLE_ADMIN" />
<el-option label="普通用户" value="ROLE_USER" />
</el-select>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-input
v-model="filters.search"
placeholder="搜索用户名或邮箱"
clearable
@input="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-button @click="resetFilters">重置筛选</el-button>
</el-col>
</el-row>
</el-card>
<!-- 用户列表 -->
<el-card class="users-card">
<template #header>
<div class="card-header">
<span>用户列表</span>
<el-button type="primary" @click="showCreateUserDialog">
<el-icon><Plus /></el-icon>
添加用户
</el-button>
</div>
</template>
<el-table
:data="users"
v-loading="loading"
empty-text="暂无用户"
@sort-change="handleSortChange"
>
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
<el-table-column prop="username" label="用户名" width="150" sortable="custom">
<template #default="{ row }">
<div class="user-info">
<el-avatar :size="32">{{ row.username.charAt(0).toUpperCase() }}</el-avatar>
<div class="user-details">
<div class="username">{{ row.username }}</div>
<div class="user-id">ID: {{ row.id }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱" min-width="200" sortable="custom">
<template #default="{ row }">
<span class="email">{{ row.email }}</span>
</template>
</el-table-column>
<el-table-column prop="role" label="角色" width="120">
<template #default="{ row }">
<el-tag :type="getRoleType(row.role)">
{{ getRoleText(row.role) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="注册时间" width="160" sortable="custom">
<template #default="{ row }">
{{ formatDate(row.createdAt) }}
</template>
</el-table-column>
<el-table-column prop="lastLoginAt" label="最后登录" width="160">
<template #default="{ row }">
{{ row.lastLoginAt ? formatDate(row.lastLoginAt) : '从未登录' }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button-group>
<el-button size="small" @click="viewUserDetail(row)">
查看
</el-button>
<el-button size="small" type="primary" @click="editUser(row)">
编辑
</el-button>
<el-button
size="small"
type="danger"
@click="deleteUser(row)"
:disabled="row.role === 'ROLE_ADMIN'"
>
删除
</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.size"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 创建/编辑用户对话框 -->
<el-dialog
v-model="userDialogVisible"
:title="isEdit ? '编辑用户' : '添加用户'"
width="600px"
>
<el-form
ref="userFormRef"
:model="userForm"
:rules="userRules"
label-width="100px"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="userForm.username"
placeholder="请输入用户名"
:disabled="isEdit"
/>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="userForm.email"
type="email"
placeholder="请输入邮箱"
/>
</el-form-item>
<el-form-item label="密码" prop="password" v-if="!isEdit">
<el-input
v-model="userForm.password"
type="password"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<el-form-item label="角色" prop="role">
<el-radio-group v-model="userForm.role">
<el-radio value="ROLE_USER">普通用户</el-radio>
<el-radio value="ROLE_ADMIN">管理员</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="userDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitUser" :loading="submitLoading">
{{ isEdit ? '更新' : '创建' }}
</el-button>
</template>
</el-dialog>
<!-- 用户详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="用户详情"
width="600px"
>
<div v-if="currentUser">
<el-descriptions :column="2" border>
<el-descriptions-item label="用户ID">{{ currentUser.id }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ currentUser.username }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ currentUser.email }}</el-descriptions-item>
<el-descriptions-item label="角色">
<el-tag :type="getRoleType(currentUser.role)">
{{ getRoleText(currentUser.role) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="注册时间">{{ formatDate(currentUser.createdAt) }}</el-descriptions-item>
<el-descriptions-item label="最后登录" v-if="currentUser.lastLoginAt">
{{ formatDate(currentUser.lastLoginAt) }}
</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const users = ref([])
const submitLoading = ref(false)
// 统计数据
const stats = ref({
totalUsers: 0,
adminUsers: 0,
normalUsers: 0,
todayUsers: 0
})
// 筛选条件
const filters = reactive({
role: '',
search: '',
todayOnly: false
})
// 分页信息
const pagination = reactive({
page: 1,
size: 10,
total: 0
})
// 排序
const sortBy = ref('createdAt')
const sortDir = ref('desc')
// 用户对话框
const userDialogVisible = ref(false)
const isEdit = ref(false)
const userFormRef = ref()
const userForm = reactive({
username: '',
email: '',
password: '',
role: 'ROLE_USER'
})
const userRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
}
// 用户详情对话框
const detailDialogVisible = ref(false)
const currentUser = ref(null)
// 获取角色类型
const getRoleType = (role) => {
return role === 'ROLE_ADMIN' ? 'danger' : 'primary'
}
// 获取角色文本
const getRoleText = (role) => {
return role === 'ROLE_ADMIN' ? '管理员' : '普通用户'
}
// 格式化日期
const formatDate = (dateString) => {
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
// 获取用户列表
const fetchUsers = async () => {
try {
loading.value = true
// 模拟数据
const today = new Date().toISOString().split('T')[0]
const mockUsers = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
role: 'ROLE_ADMIN',
createdAt: '2024-01-01T10:00:00Z',
lastLoginAt: '2024-01-01T15:00:00Z'
},
{
id: 2,
username: 'user1',
email: 'user1@example.com',
role: 'ROLE_USER',
createdAt: '2024-01-01T11:00:00Z',
lastLoginAt: '2024-01-01T14:00:00Z'
},
{
id: 3,
username: 'user2',
email: 'user2@example.com',
role: 'ROLE_USER',
createdAt: '2024-01-01T12:00:00Z',
lastLoginAt: null
},
{
id: 4,
username: 'admin2',
email: 'admin2@example.com',
role: 'ROLE_ADMIN',
createdAt: '2024-01-01T13:00:00Z',
lastLoginAt: '2024-01-01T16:00:00Z'
},
{
id: 5,
username: 'user3',
email: 'user3@example.com',
role: 'ROLE_USER',
createdAt: '2024-01-01T14:00:00Z',
lastLoginAt: null
},
{
id: 6,
username: 'newuser1',
email: 'newuser1@example.com',
role: 'ROLE_USER',
createdAt: `${today}T10:00:00Z`,
lastLoginAt: null
},
{
id: 7,
username: 'newuser2',
email: 'newuser2@example.com',
role: 'ROLE_USER',
createdAt: `${today}T11:00:00Z`,
lastLoginAt: null
},
{
id: 8,
username: 'newadmin',
email: 'newadmin@example.com',
role: 'ROLE_ADMIN',
createdAt: `${today}T12:00:00Z`,
lastLoginAt: null
}
]
// 根据筛选条件过滤用户
let filteredUsers = mockUsers
// 按角色筛选
if (filters.role) {
filteredUsers = filteredUsers.filter(user => user.role === filters.role)
}
// 按搜索关键词筛选
if (filters.search) {
const searchLower = filters.search.toLowerCase()
filteredUsers = filteredUsers.filter(user =>
user.username.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower)
)
}
// 按今日注册筛选(模拟)
if (filters.todayOnly) {
const today = new Date().toISOString().split('T')[0]
filteredUsers = filteredUsers.filter(user =>
user.createdAt.startsWith(today)
)
}
users.value = filteredUsers
pagination.total = filteredUsers.length
// 更新统计数据
stats.value = {
totalUsers: mockUsers.length,
adminUsers: mockUsers.filter(user => user.role === 'ROLE_ADMIN').length,
normalUsers: mockUsers.filter(user => user.role === 'ROLE_USER').length,
todayUsers: mockUsers.filter(user => {
const today = new Date().toISOString().split('T')[0]
return user.createdAt.startsWith(today)
}).length
}
} catch (error) {
console.error('Fetch users error:', error)
ElMessage.error('获取用户列表失败')
} finally {
loading.value = false
}
}
// 筛选变化
const handleFilterChange = () => {
pagination.page = 1
fetchUsers()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
fetchUsers()
}
// 重置筛选
const resetFilters = () => {
filters.role = ''
filters.search = ''
pagination.page = 1
fetchUsers()
}
// 排序变化
const handleSortChange = ({ prop, order }) => {
if (prop) {
sortBy.value = prop
sortDir.value = order === 'ascending' ? 'asc' : 'desc'
fetchUsers()
}
}
// 分页大小变化
const handleSizeChange = (size) => {
pagination.size = size
pagination.page = 1
fetchUsers()
}
// 当前页变化
const handleCurrentChange = (page) => {
pagination.page = page
fetchUsers()
}
// 显示创建用户对话框
const showCreateUserDialog = () => {
isEdit.value = false
resetUserForm()
userDialogVisible.value = true
}
// 编辑用户
const editUser = (user) => {
isEdit.value = true
userForm.username = user.username
userForm.email = user.email
userForm.role = user.role
userForm.password = ''
userDialogVisible.value = true
}
// 重置用户表单
const resetUserForm = () => {
userForm.username = ''
userForm.email = ''
userForm.password = ''
userForm.role = 'ROLE_USER'
}
// 提交用户表单
const handleSubmitUser = async () => {
if (!userFormRef.value) return
try {
const valid = await userFormRef.value.validate()
if (!valid) return
submitLoading.value = true
// 模拟提交
await new Promise(resolve => setTimeout(resolve, 1000))
ElMessage.success(isEdit.value ? '用户更新成功' : '用户创建成功')
userDialogVisible.value = false
fetchUsers()
} catch (error) {
console.error('Submit user error:', error)
ElMessage.error(isEdit.value ? '用户更新失败' : '用户创建失败')
} finally {
submitLoading.value = false
}
}
// 查看用户详情
const viewUserDetail = (user) => {
currentUser.value = user
detailDialogVisible.value = true
}
// 删除用户
const deleteUser = async (user) => {
try {
await ElMessageBox.confirm(`确定要删除用户 "${user.username}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
ElMessage.success('用户删除成功')
fetchUsers()
} catch (error) {
// 用户取消
}
}
// 处理统计卡片点击事件
const handleStatClick = (type) => {
switch (type) {
case 'all':
// 显示所有用户
filters.role = ''
filters.search = ''
filters.todayOnly = false
ElMessage.info('显示所有用户')
break
case 'admin':
// 筛选管理员用户
filters.role = 'ROLE_ADMIN'
filters.search = ''
filters.todayOnly = false
ElMessage.info('筛选管理员用户')
break
case 'user':
// 筛选普通用户
filters.role = 'ROLE_USER'
filters.search = ''
filters.todayOnly = false
ElMessage.info('筛选普通用户')
break
case 'today':
// 筛选今日注册用户
filters.role = ''
filters.search = ''
filters.todayOnly = true
ElMessage.info('筛选今日注册用户')
break
}
// 重新获取用户列表
fetchUsers()
}
onMounted(() => {
fetchUsers()
})
</script>
<style scoped>
.admin-users {
max-width: 1400px;
margin: 0 auto;
}
.page-header {
margin-bottom: 20px;
}
.page-header h2 {
margin: 0;
color: #303133;
display: flex;
align-items: center;
gap: 8px;
}
.stats-row {
margin-bottom: 20px;
}
.stat-card {
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s ease;
}
.stat-card.clickable {
cursor: pointer;
}
.stat-card.clickable:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.stat-content {
flex: 1;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #303133;
margin-bottom: 4px;
}
.stat-label {
color: #909399;
font-size: 14px;
}
.stat-icon {
font-size: 2rem;
opacity: 0.8;
}
.filter-card {
margin-bottom: 20px;
}
.users-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.user-details {
display: flex;
flex-direction: column;
}
.username {
font-weight: 500;
color: #303133;
}
.user-id {
font-size: 12px;
color: #909399;
}
.email {
color: #606266;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
@media (max-width: 768px) {
.page-header {
text-align: center;
}
.stat-card {
margin-bottom: 16px;
}
.card-header {
flex-direction: column;
gap: 16px;
}
}
</style>