|
@@ -1,5 +1,6 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { getUserController } from '@/api/user-controller'
|
|
import { getUserController } from '@/api/user-controller'
|
|
|
|
|
+import { getImageController } from '@/api/image-controller'
|
|
|
import type { UserDto, UserVo } from '@/api/models'
|
|
import type { UserDto, UserVo } from '@/api/models'
|
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules, ElIcon } from 'element-plus'
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules, ElIcon } from 'element-plus'
|
|
|
import { Plus, RefreshRight, Search, Delete, Edit } from '@element-plus/icons-vue'
|
|
import { Plus, RefreshRight, Search, Delete, Edit } from '@element-plus/icons-vue'
|
|
@@ -25,7 +26,10 @@ const userForm = reactive<Partial<UserDto>>({
|
|
|
username: '',
|
|
username: '',
|
|
|
account: '',
|
|
account: '',
|
|
|
password: '',
|
|
password: '',
|
|
|
- role: ''
|
|
|
|
|
|
|
+ role: '',
|
|
|
|
|
+ email: '',
|
|
|
|
|
+ phone: '',
|
|
|
|
|
+ avatar: ''
|
|
|
})
|
|
})
|
|
|
const formRules: FormRules = {
|
|
const formRules: FormRules = {
|
|
|
username: [
|
|
username: [
|
|
@@ -43,6 +47,12 @@ const formRules: FormRules = {
|
|
|
role: [
|
|
role: [
|
|
|
{ required: true, message: '请选择角色', trigger: 'change' },
|
|
{ required: true, message: '请选择角色', trigger: 'change' },
|
|
|
{ min: 4, max: 32, message: '角色长度需在 4 到 32 个字符之间', trigger: 'change' }
|
|
{ min: 4, max: 32, message: '角色长度需在 4 到 32 个字符之间', trigger: 'change' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ email: [
|
|
|
|
|
+ { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ phone: [
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -68,6 +78,24 @@ const roleOptions = [
|
|
|
// 是否编辑模式
|
|
// 是否编辑模式
|
|
|
const isEdit = computed(() => !!userForm.id)
|
|
const isEdit = computed(() => !!userForm.id)
|
|
|
|
|
|
|
|
|
|
+const uploading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const handleAvatarChange = async (uploadFile: any) => {
|
|
|
|
|
+ uploading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getImageController().upload({ imageFile: uploadFile.raw as Blob })
|
|
|
|
|
+ if (res?.data) {
|
|
|
|
|
+ userForm.avatar = res.data
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('图片上传失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ ElMessage.error('图片上传失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 搜索
|
|
// 搜索
|
|
|
const handleSearch = () => {
|
|
const handleSearch = () => {
|
|
|
pagination.pageNum = 1
|
|
pagination.pageNum = 1
|
|
@@ -119,7 +147,7 @@ const handleSizeChange = (size: number) => {
|
|
|
// 打开新增
|
|
// 打开新增
|
|
|
const handleAdd = () => {
|
|
const handleAdd = () => {
|
|
|
dialogTitle.value = '新增用户'
|
|
dialogTitle.value = '新增用户'
|
|
|
- Object.assign(userForm, { id: undefined, username: '', account: '', password: '', role: '' })
|
|
|
|
|
|
|
+ Object.assign(userForm, { id: undefined, username: '', account: '', password: '', role: '', email: '', phone: '', avatar: '' })
|
|
|
dialogVisible.value = true
|
|
dialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -134,7 +162,10 @@ const handleEdit = async (row: UserVo) => {
|
|
|
username: res.data?.username,
|
|
username: res.data?.username,
|
|
|
account: res.data?.account,
|
|
account: res.data?.account,
|
|
|
password: '',
|
|
password: '',
|
|
|
- role: res.data?.role
|
|
|
|
|
|
|
+ role: res.data?.role,
|
|
|
|
|
+ email: res.data?.email ?? '',
|
|
|
|
|
+ phone: res.data?.phone ?? '',
|
|
|
|
|
+ avatar: res.data?.avatar ?? ''
|
|
|
})
|
|
})
|
|
|
dialogVisible.value = true
|
|
dialogVisible.value = true
|
|
|
}
|
|
}
|
|
@@ -151,7 +182,10 @@ const handleSave = async () => {
|
|
|
const baseData = {
|
|
const baseData = {
|
|
|
username: userForm.username!,
|
|
username: userForm.username!,
|
|
|
account: userForm.account!,
|
|
account: userForm.account!,
|
|
|
- role: userForm.role!
|
|
|
|
|
|
|
+ role: userForm.role!,
|
|
|
|
|
+ email: userForm.email || undefined,
|
|
|
|
|
+ phone: userForm.phone || undefined,
|
|
|
|
|
+ avatar: userForm.avatar || undefined
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (isEdit.value) {
|
|
if (isEdit.value) {
|
|
@@ -326,6 +360,8 @@ fetchData()
|
|
|
<el-table-column prop="id" label="ID" width="80" align="center" />
|
|
<el-table-column prop="id" label="ID" width="80" align="center" />
|
|
|
<el-table-column prop="username" label="用户名" min-width="120" align="center" />
|
|
<el-table-column prop="username" label="用户名" min-width="120" align="center" />
|
|
|
<el-table-column prop="account" label="账号" min-width="150" align="center" />
|
|
<el-table-column prop="account" label="账号" min-width="150" align="center" />
|
|
|
|
|
+ <el-table-column prop="email" label="邮箱" min-width="180" align="center" />
|
|
|
|
|
+ <el-table-column prop="phone" label="手机号" min-width="130" align="center" />
|
|
|
<el-table-column prop="role" label="角色" width="120" align="center">
|
|
<el-table-column prop="role" label="角色" width="120" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="row.role === 'admin' ? 'danger' : row.role === 'user' ? 'success' : 'info'">
|
|
<el-tag :type="row.role === 'admin' ? 'danger' : row.role === 'user' ? 'success' : 'info'">
|
|
@@ -345,8 +381,19 @@ fetchData()
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="avatar" label="头像" width="100" align="center">
|
|
<el-table-column prop="avatar" label="头像" width="100" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
- <el-avatar v-if="row.avatar" :src="row.avatar" :size="40" />
|
|
|
|
|
- <el-avatar v-else :size="40">{{ row.username?.charAt(0) || '?' }}</el-avatar>
|
|
|
|
|
|
|
+ <el-avatar
|
|
|
|
|
+ v-if="row.avatar"
|
|
|
|
|
+ :src="row.avatar"
|
|
|
|
|
+ :size="40"
|
|
|
|
|
+ class="clickable-avatar"
|
|
|
|
|
+ @click="handleEdit(row)"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-avatar
|
|
|
|
|
+ v-else
|
|
|
|
|
+ :size="40"
|
|
|
|
|
+ class="clickable-avatar"
|
|
|
|
|
+ @click="handleEdit(row)"
|
|
|
|
|
+ >{{ row.username?.charAt(0) || '?' }}</el-avatar>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
|
@@ -393,6 +440,28 @@ fetchData()
|
|
|
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
+ <el-form-item label="邮箱" prop="email">
|
|
|
|
|
+ <el-input v-model="userForm.email" placeholder="请输入邮箱" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="手机号" prop="phone">
|
|
|
|
|
+ <el-input v-model="userForm.phone" placeholder="请输入手机号" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="头像" prop="avatar">
|
|
|
|
|
+ <div class="avatar-upload">
|
|
|
|
|
+ <el-avatar v-if="userForm.avatar" :size="60" :src="userForm.avatar" shape="square" />
|
|
|
|
|
+ <el-avatar v-else :size="60" shape="square">?</el-avatar>
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ :show-file-list="false"
|
|
|
|
|
+ :on-change="handleAvatarChange"
|
|
|
|
|
+ :auto-upload="false"
|
|
|
|
|
+ accept="image/jpeg,image/png,image/gif,image/webp"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #trigger>
|
|
|
|
|
+ <el-button size="small" :loading="uploading">选择图片</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
@@ -430,4 +499,27 @@ fetchData()
|
|
|
color: #909399;
|
|
color: #909399;
|
|
|
line-height: 1.4;
|
|
line-height: 1.4;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.clickable-avatar {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: opacity 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+.clickable-avatar:hover {
|
|
|
|
|
+ opacity: 0.8;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.avatar-upload {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .table-card :deep(.el-table) {
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+ .table-card :deep(.el-table__body-wrapper) {
|
|
|
|
|
+ overflow-x: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|