feat(user): 集成 NovaDialog 组件到用户管理页面

- 替换所有原生 n-modal 为自定义 NovaDialog 组件
- 统一用户表单、角色分配、重置密码、头像查看和导入功能的弹框交互
- 使用 novaOpen/novaClose 方法控制所有弹框显示状态
- 保持原有功能和用户体验不变
- 与角色管理页面保持一致的弹框风格
- 提升整体页面的一致性和可维护性
This commit is contained in:
Leo 2025-07-06 18:25:02 +08:00
parent 7577077c26
commit ebffbd78bc

View File

@ -2,6 +2,7 @@
import { h, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import type { DataTableColumns, FormInst } from 'naive-ui'
import { NButton, NCheckbox, NDropdown, NIcon, NPopconfirm, NProgress, NSpace, NSwitch, NTag, NUpload, NUploadDragger } from 'naive-ui'
import NovaDialog from '@/components/common/NovaDialog.vue'
import {
addUser,
batchDeleteUsers,
@ -29,7 +30,6 @@ import { coiMsgBox, coiMsgError, coiMsgInfo, coiMsgSuccess, coiMsgWarning } from
//
const loading = ref(false)
const tableData = ref<UserVo[]>([])
const showModal = ref(false)
const modalTitle = ref('新增用户')
const formRef = ref<FormInst | null>(null)
const searchFormRef = ref<FormInst | null>(null)
@ -38,8 +38,14 @@ const currentUser = ref<UserVo | null>(null)
const selectedRows = ref<UserVo[]>([])
const roleOptions = ref<RoleVo[]>([])
//
const userDialogRef = ref()
const roleDialogRef = ref()
const resetPwdDialogRef = ref()
const avatarDialogRef = ref()
const importDialogRef = ref()
//
const showRoleModal = ref(false)
const roleModalTitle = ref('分配角色')
const currentAssignUser = ref<UserVo | null>(null)
const availableRoles = ref<{ label: string, value: number }[]>([])
@ -47,7 +53,6 @@ const selectedRoleIds = ref<number[]>([])
const roleLoading = ref(false)
//
const showResetPwdModal = ref(false)
const resetPwdFormRef = ref<FormInst | null>(null)
const currentResetUser = ref<UserVo | null>(null)
const resetPwdForm = ref({
@ -56,12 +61,10 @@ const resetPwdForm = ref({
})
//
const showAvatarModal = ref(false)
const currentAvatar = ref('')
const currentAvatarUser = ref<UserVo | null>(null)
//
const showImportModal = ref(false)
const importLoading = ref(false)
const selectedFile = ref<File | null>(null)
const updateSupport = ref(false)
@ -488,7 +491,7 @@ function handleAdd() {
remark: '',
roleIds: [],
}
showModal.value = true
userDialogRef.value?.novaOpen()
}
//
@ -509,7 +512,7 @@ function handleEdit(user: UserVo) {
remark: user.remark || '',
roleIds: user.roleIds || [],
}
showModal.value = true
userDialogRef.value?.novaOpen()
}
//
@ -595,7 +598,7 @@ function handleResetPassword(user: UserVo) {
newPassword: '',
confirmPassword: '',
}
showResetPwdModal.value = true
resetPwdDialogRef.value?.novaOpen()
}
//
@ -619,7 +622,7 @@ async function handleConfirmResetPassword() {
const { isSuccess } = await resetUserPassword(currentResetUser.value.userId, resetPwdForm.value.newPassword)
if (isSuccess) {
coiMsgSuccess(`用户「${currentResetUser.value.userName}」密码重置成功`)
showResetPwdModal.value = false
resetPwdDialogRef.value?.novaClose()
}
else {
coiMsgError('重置密码失败,请稍后重试')
@ -633,7 +636,7 @@ async function handleConfirmResetPassword() {
//
function handleCancelResetPassword() {
showResetPwdModal.value = false
resetPwdDialogRef.value?.novaClose()
resetPwdForm.value = {
newPassword: '',
confirmPassword: '',
@ -679,12 +682,12 @@ function handleViewAvatar(user: UserVo) {
currentAvatar.value = blobUrl
createdBlobUrls.value.push(blobUrl)
}
showAvatarModal.value = true
avatarDialogRef.value?.novaOpen()
}
//
function handleCloseAvatar() {
showAvatarModal.value = false
avatarDialogRef.value?.novaClose()
// URL
if (currentAvatar.value.startsWith('blob:')) {
URL.revokeObjectURL(currentAvatar.value)
@ -826,7 +829,7 @@ function handleImport() {
selectedFile.value = null
updateSupport.value = false
uploadProgress.value = 0
showImportModal.value = true
importDialogRef.value?.novaOpen()
}
//
@ -891,7 +894,7 @@ async function handleConfirmImport() {
)
//
showImportModal.value = false
importDialogRef.value?.novaClose()
await getUserList()
}
else {
@ -916,7 +919,7 @@ async function handleConfirmImport() {
//
function handleCancelImport() {
showImportModal.value = false
importDialogRef.value?.novaClose()
selectedFile.value = null
updateSupport.value = false
uploadProgress.value = 0
@ -954,7 +957,7 @@ async function handleAssignRole(user: UserVo) {
// 使ID
selectedRoleIds.value = userRoleIds
showRoleModal.value = true
roleDialogRef.value?.novaOpen()
}
else {
coiMsgInfo('当前系统没有可分配的角色')
@ -992,7 +995,7 @@ async function handleConfirmAssignRole() {
if (response.isSuccess) {
coiMsgSuccess('角色分配成功')
showRoleModal.value = false
roleDialogRef.value?.novaClose()
//
if (currentAssignUser.value) {
@ -1016,7 +1019,7 @@ async function handleConfirmAssignRole() {
//
function handleCancelAssignRole() {
showRoleModal.value = false
roleDialogRef.value?.novaClose()
currentAssignUser.value = null
selectedRoleIds.value = []
availableRoles.value = []
@ -1059,7 +1062,7 @@ async function handleSubmit() {
if (isSuccess) {
coiMsgSuccess(isEdit.value ? '用户信息更新成功' : '用户创建成功')
showModal.value = false
userDialogRef.value?.novaClose()
await getUserList()
}
else {
@ -1074,7 +1077,7 @@ async function handleSubmit() {
//
function handleCancel() {
showModal.value = false
userDialogRef.value?.novaClose()
}
//
@ -1339,15 +1342,19 @@ onBeforeUnmount(() => {
</div>
</div>
<!-- 用户表单模态-->
<n-modal
v-model:show="showModal"
<!-- 用户表单-->
<NovaDialog
ref="userDialogRef"
:title="modalTitle"
preset="card"
:style="{ width: '800px' }"
:mask-closable="false"
class="user-modal"
:width="800"
height="auto"
confirm-text="确定"
cancel-text="取消"
@nova-confirm="handleSubmit"
@nova-cancel="handleCancel"
>
<template #content>
<div class="px-3 py-2">
<n-form
ref="formRef"
:model="formData"
@ -1470,29 +1477,23 @@ onBeforeUnmount(() => {
/>
</n-form-item>
</n-form>
<template #footer>
<div class="flex justify-end gap-3">
<NButton @click="handleCancel">
取消
</NButton>
<NButton type="primary" @click="handleSubmit">
确定
</NButton>
</div>
</template>
</n-modal>
</NovaDialog>
<!-- 角色分配模态-->
<n-modal
v-model:show="showRoleModal"
<!-- 角色分配弹框 -->
<NovaDialog
ref="roleDialogRef"
:title="roleModalTitle"
preset="card"
:style="{ width: '600px' }"
:mask-closable="false"
class="role-assign-modal"
:width="600"
height="auto"
confirm-text="确定"
cancel-text="取消"
@nova-confirm="handleConfirmAssignRole"
@nova-cancel="handleCancelAssignRole"
>
<div class="space-y-4">
<template #content>
<div class="p-3 space-y-4">
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-blue-400 to-purple-500 flex items-center justify-center text-white text-sm font-bold">
@ -1537,37 +1538,22 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-3">
<NButton
:disabled="roleLoading"
@click="handleCancelAssignRole"
>
取消
</NButton>
<NButton
type="primary"
:loading="roleLoading"
:disabled="availableRoles.length === 0"
@click="handleConfirmAssignRole"
>
确定
</NButton>
</div>
</template>
</n-modal>
</NovaDialog>
<!-- 重置密码模态-->
<n-modal
v-model:show="showResetPwdModal"
<!-- 重置密码弹框 -->
<NovaDialog
ref="resetPwdDialogRef"
title="重置用户密码"
preset="card"
:style="{ width: '500px' }"
:mask-closable="false"
class="reset-pwd-modal"
:width="500"
height="auto"
confirm-text="确认重置"
cancel-text="取消"
@nova-confirm="handleConfirmResetPassword"
@nova-cancel="handleCancelResetPassword"
>
<div class="space-y-4">
<template #content>
<div class="p-3 space-y-4">
<div class="bg-gray-50 p-4 rounded-lg">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-orange-400 to-red-500 flex items-center justify-center text-white text-sm font-bold">
@ -1629,27 +1615,17 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-3">
<NButton @click="handleCancelResetPassword">
取消
</NButton>
<NButton type="primary" @click="handleConfirmResetPassword">
确认重置
</NButton>
</div>
</template>
</n-modal>
</NovaDialog>
<!-- 头像查看模态-->
<n-modal
v-model:show="showAvatarModal"
preset="card"
:style="{ width: '600px' }"
:mask-closable="true"
class="avatar-modal"
@close="handleCloseAvatar"
<!-- 头像查看弹框 -->
<NovaDialog
ref="avatarDialogRef"
:width="600"
height="auto"
cancel-text="关闭"
:show-confirm="false"
@nova-cancel="handleCloseAvatar"
>
<template #header>
<div class="flex items-center gap-3">
@ -1663,7 +1639,8 @@ onBeforeUnmount(() => {
</div>
</template>
<div class="flex flex-col items-center space-y-4">
<template #content>
<div class="flex flex-col items-center space-y-4 p-3">
<div class="relative">
<img
:src="currentAvatar"
@ -1689,26 +1666,22 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end">
<NButton @click="handleCloseAvatar">
关闭
</NButton>
</div>
</template>
</n-modal>
</NovaDialog>
<!-- 导入用户模态-->
<n-modal
v-model:show="showImportModal"
<!-- 导入用户弹框 -->
<NovaDialog
ref="importDialogRef"
title="导入用户数据"
preset="card"
:style="{ width: '600px' }"
:mask-closable="false"
class="import-modal"
:width="600"
height="auto"
confirm-text="开始导入"
cancel-text="取消"
@nova-confirm="handleConfirmImport"
@nova-cancel="handleCancelImport"
>
<div class="space-y-6">
<template #content>
<div class="p-3 space-y-6">
<!-- 文件上传区域 -->
<div>
<h4 class="text-sm font-medium text-gray-700 mb-3">
@ -1782,10 +1755,9 @@ onBeforeUnmount(() => {
<li> 用户状态默认为"启用"</li>
</ul>
</div>
</div>
<template #footer>
<div class="flex justify-between">
<!-- 下载模板按钮 -->
<div class="flex justify-start">
<NButton
:disabled="importLoading"
@click="handleDownloadTemplate"
@ -1795,23 +1767,10 @@ onBeforeUnmount(() => {
</template>
下载模板
</NButton>
<div class="flex gap-3">
<NButton :disabled="importLoading" @click="handleCancelImport">
取消
</NButton>
<NButton
type="primary"
:loading="importLoading"
:disabled="!selectedFile"
@click="handleConfirmImport"
>
开始导入
</NButton>
</div>
</div>
</template>
</n-modal>
</NovaDialog>
</div>
</template>