445 lines
11 KiB
Vue
445 lines
11 KiB
Vue
<template>
|
|
<div class="user-list-container">
|
|
<div class="header-actions">
|
|
<el-input
|
|
v-model="searchKeyword"
|
|
placeholder="请输入用户名搜索"
|
|
clearable
|
|
class="search-input"
|
|
@clear="loadUserList"
|
|
@keyup.enter="handleSearch"
|
|
>
|
|
<template #append>
|
|
<el-button @click="handleSearch">
|
|
<el-icon><Search /></el-icon>
|
|
</el-button>
|
|
</template>
|
|
</el-input>
|
|
|
|
<el-button type="primary" @click="openUserDialog()">
|
|
<el-icon><Plus /></el-icon>新增用户
|
|
</el-button>
|
|
</div>
|
|
|
|
<el-table
|
|
v-loading="loading"
|
|
:data="userList"
|
|
border
|
|
style="width: 100%"
|
|
row-key="id"
|
|
>
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
<el-table-column prop="username" label="用户名" />
|
|
<el-table-column prop="nickname" label="昵称" />
|
|
<el-table-column prop="phone" label="手机号" />
|
|
<el-table-column prop="email" label="邮箱" />
|
|
<el-table-column prop="createTime" label="创建时间" :formatter="formatDate" />
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button type="primary" link @click="openUserDialog(row)">
|
|
编辑
|
|
</el-button>
|
|
<el-button type="danger" link @click="handleDelete(row)">
|
|
删除
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<div class="pagination-container">
|
|
<el-pagination
|
|
v-model:current-page="currentPage"
|
|
v-model:page-size="pageSize"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
:total="total"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 用户表单对话框 -->
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
:title="isEdit ? '编辑用户' : '新增用户'"
|
|
width="500px"
|
|
@closed="resetForm"
|
|
>
|
|
<el-form
|
|
ref="formRef"
|
|
:model="form"
|
|
:rules="rules"
|
|
label-width="100px"
|
|
class="user-form"
|
|
>
|
|
<el-form-item label="用户名" prop="username">
|
|
<el-input v-model="form.username" placeholder="请输入用户名" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="密码" prop="password" v-if="!isEdit">
|
|
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="确认密码" prop="confirmPassword" v-if="!isEdit">
|
|
<el-input v-model="form.confirmPassword" type="password" placeholder="请确认密码" show-password />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="昵称" prop="nickname">
|
|
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="手机号" prop="phone">
|
|
<el-input v-model="form.phone" placeholder="请输入手机号" />
|
|
</el-form-item>
|
|
|
|
<el-form-item label="邮箱" prop="email">
|
|
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
|
</el-form-item>
|
|
<el-form-item label="角色" prop="roleId">
|
|
<el-select v-model="form.roleId" placeholder="请选择角色">
|
|
<el-option
|
|
v-for="role in roleList"
|
|
:key="role.id"
|
|
:label="role.roleName"
|
|
:value="role.id"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { Search, Plus } from '@element-plus/icons-vue'
|
|
import { userApi } from '@/api'
|
|
import { getRoleList } from '@/api/role'
|
|
|
|
// 表格数据
|
|
const userList = ref([])
|
|
const loading = ref(false)
|
|
const searchKeyword = ref('')
|
|
|
|
// 分页参数
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(10)
|
|
const total = ref(0)
|
|
|
|
// 对话框相关
|
|
const dialogVisible = ref(false)
|
|
const isEdit = ref(false)
|
|
const submitLoading = ref(false)
|
|
const roleList = ref([])
|
|
// 表单数据
|
|
const form = reactive({
|
|
id: null,
|
|
username: '',
|
|
password: '',
|
|
confirmPassword: '',
|
|
nickname: '',
|
|
phone: '',
|
|
email: '',
|
|
roleId: null // 角色ID
|
|
})
|
|
|
|
// 表单校验规则
|
|
const validatePass = (rule, value, callback) => {
|
|
if (!isEdit.value && !value) {
|
|
callback(new Error('请输入密码'))
|
|
} else {
|
|
if (form.confirmPassword !== '') {
|
|
formRef.value?.validateField('confirmPassword')
|
|
}
|
|
callback()
|
|
}
|
|
}
|
|
|
|
const validateConfirmPass = (rule, value, callback) => {
|
|
if (!isEdit.value && !value) {
|
|
callback(new Error('请再次输入密码'))
|
|
} else if (value !== form.password) {
|
|
callback(new Error('两次输入密码不一致'))
|
|
} else {
|
|
callback()
|
|
}
|
|
}
|
|
|
|
const rules = reactive({
|
|
username: [
|
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
|
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
|
],
|
|
password: [
|
|
{ validator: validatePass, trigger: 'blur' }
|
|
],
|
|
confirmPassword: [
|
|
{ validator: validateConfirmPass, trigger: 'blur' }
|
|
],
|
|
phone: [
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
|
],
|
|
email: [
|
|
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
|
],
|
|
roleId: [
|
|
{ required: true, message: '请选择角色', trigger: 'change' }
|
|
]
|
|
})
|
|
|
|
// 表单引用
|
|
const formRef = ref(null)
|
|
|
|
// 加载用户列表
|
|
const loadUserList = async () => {
|
|
try {
|
|
loading.value = true
|
|
const res = await userApi.getUserList()
|
|
|
|
if (res.code === 200) {
|
|
userList.value = res.data || []
|
|
total.value = res.data?.length || 0
|
|
|
|
// 如果有搜索关键词,进行前端过滤
|
|
if (searchKeyword.value) {
|
|
userList.value = userList.value.filter(user =>
|
|
user.username?.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
|
user.nickname?.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
|
)
|
|
}
|
|
} else {
|
|
ElMessage.error(res.message || '获取用户列表失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('获取用户列表出错:', error)
|
|
ElMessage.error(error.message || '获取用户列表失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 搜索
|
|
const handleSearch = () => {
|
|
currentPage.value = 1
|
|
loadUserList()
|
|
}
|
|
|
|
// 加载角色列表
|
|
const loadRoleList = async () => {
|
|
try {
|
|
const res = await getRoleList()
|
|
console.log(res)
|
|
if (res.code === 200) {
|
|
roleList.value = res.data || []
|
|
} else {
|
|
ElMessage.error(res.message || '获取角色列表失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('获取角色列表出错:', error)
|
|
ElMessage.error(error.message || '获取角色列表失败')
|
|
}
|
|
}
|
|
|
|
// 打开用户对话框
|
|
const openUserDialog = (row) => {
|
|
resetForm()
|
|
|
|
if (row) {
|
|
// 编辑模式
|
|
isEdit.value = true
|
|
getUserDetail(row.id)
|
|
} else {
|
|
// 新增模式
|
|
isEdit.value = false
|
|
}
|
|
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
// 获取用户详情
|
|
const getUserDetail = async (id) => {
|
|
try {
|
|
submitLoading.value = true
|
|
const res = await userApi.getUserById(id)
|
|
|
|
if (res.code === 200 && res.data) {
|
|
// 填充表单数据
|
|
Object.keys(form).forEach(key => {
|
|
if (key !== 'password' && key !== 'confirmPassword' && res.data[key] !== undefined) {
|
|
form[key] = res.data[key]
|
|
}
|
|
})
|
|
} else {
|
|
ElMessage.error(res.message || '获取用户信息失败')
|
|
dialogVisible.value = false
|
|
}
|
|
} catch (error) {
|
|
console.error('获取用户信息出错:', error)
|
|
ElMessage.error(error.message || '获取用户信息失败')
|
|
dialogVisible.value = false
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 提交表单
|
|
const submitForm = async () => {
|
|
if (!formRef.value) return
|
|
|
|
await formRef.value.validate(async (valid) => {
|
|
if (valid) {
|
|
try {
|
|
submitLoading.value = true
|
|
|
|
// 移除确认密码字段
|
|
const submitData = { ...form }
|
|
delete submitData.confirmPassword
|
|
|
|
// 如果是编辑模式且没有设置密码,则移除密码字段
|
|
if (isEdit.value && !submitData.password) {
|
|
delete submitData.password
|
|
}
|
|
|
|
let res
|
|
if (isEdit.value) {
|
|
console.log(submitData)
|
|
res = await userApi.updateUser(submitData)
|
|
} else {
|
|
res = await userApi.register(submitData)
|
|
}
|
|
|
|
if (res.code === 200) {
|
|
ElMessage.success(`${isEdit.value ? '更新' : '添加'}成功`)
|
|
dialogVisible.value = false
|
|
loadUserList()
|
|
} else {
|
|
ElMessage.error(res.message || `${isEdit.value ? '更新' : '添加'}失败`)
|
|
}
|
|
} catch (error) {
|
|
console.error(`${isEdit.value ? '更新' : '添加'}用户出错:`, error)
|
|
ElMessage.error(error.message || `${isEdit.value ? '更新' : '添加'}失败`)
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// 重置表单
|
|
const resetForm = () => {
|
|
if (formRef.value) {
|
|
formRef.value.resetFields()
|
|
}
|
|
|
|
// 重置表单数据
|
|
Object.keys(form).forEach(key => {
|
|
form[key] = key === 'id' ? null : ''
|
|
})
|
|
}
|
|
|
|
// 删除用户
|
|
const handleDelete = (row) => {
|
|
ElMessageBox.confirm(
|
|
`确定要删除用户 "${row.username || row.nickname || row.id}" 吗?`,
|
|
'删除确认',
|
|
{
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}
|
|
).then(async () => {
|
|
try {
|
|
const res = await userApi.deleteUser(row.id)
|
|
if (res.code === 200) {
|
|
ElMessage.success('删除成功')
|
|
loadUserList()
|
|
} else {
|
|
ElMessage.error(res.message || '删除失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('删除用户出错:', error)
|
|
ElMessage.error(error.message || '删除失败')
|
|
}
|
|
}).catch(() => {
|
|
// 取消删除
|
|
})
|
|
}
|
|
|
|
// 分页大小变化
|
|
const handleSizeChange = (val) => {
|
|
pageSize.value = val
|
|
loadUserList()
|
|
}
|
|
|
|
// 页码变化
|
|
const handleCurrentChange = (val) => {
|
|
currentPage.value = val
|
|
loadUserList()
|
|
}
|
|
|
|
// 格式化日期
|
|
const formatDate = (row, column) => {
|
|
const dateValue = row[column.property]
|
|
if (!dateValue) return '-'
|
|
|
|
try {
|
|
const date = new Date(dateValue)
|
|
return date.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
})
|
|
} catch (e) {
|
|
return dateValue
|
|
}
|
|
}
|
|
|
|
// 组件挂载时加载数据
|
|
onMounted(() => {
|
|
loadUserList()
|
|
loadRoleList() //
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.user-list-container {
|
|
padding: 20px;
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.search-input {
|
|
width: 300px;
|
|
}
|
|
|
|
.pagination-container {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.user-form {
|
|
margin: 0 20px;
|
|
}
|
|
|
|
:deep(.el-dialog__body) {
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
}
|
|
</style>
|