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