feat(views): 优化登录页面和用户管理页面
* 登录页面改进 - 更新API导入路径,适配新的模块化结构 - 优化登录逻辑和错误处理 * 用户管理页面优化 - 简化组件生命周期管理代码 - 移除冗余的isComponentMounted检查 - 保留必要的资源清理(定时器、Blob URLs) - 重新组织代码结构,template在最前面 * 提升页面性能和可维护性 - 减少不必要的复杂度 - 保持良好的资源管理实践 改善开发体验和运行时性能
This commit is contained in:
parent
104952336d
commit
e3fbe07497
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormInst } from 'naive-ui'
|
import type { FormInst } from 'naive-ui'
|
||||||
import { useAuthStore } from '@/store'
|
import { useAuthStore } from '@/store'
|
||||||
import { fetchCaptchaPng } from '@/service'
|
import { fetchCaptchaPng } from '@/service/api/auth'
|
||||||
import { local } from '@/utils'
|
import { local } from '@/utils'
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|||||||
@ -1,80 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, nextTick, 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 {
|
import {
|
||||||
addUser,
|
addUser,
|
||||||
assignUserRole,
|
|
||||||
batchDeleteUsers,
|
batchDeleteUsers,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
downloadExcelTemplate,
|
downloadExcelTemplate,
|
||||||
exportExcelData,
|
exportExcelData,
|
||||||
fetchNormalRoleForUser,
|
|
||||||
fetchRoleList,
|
|
||||||
fetchUserPage,
|
fetchUserPage,
|
||||||
importUserData,
|
importUserData,
|
||||||
resetUserPassword,
|
resetUserPassword,
|
||||||
updateUser,
|
updateUser,
|
||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
} from '@/service/api/system'
|
|
||||||
|
} from '@/service/api/system/user'
|
||||||
|
import type { UserSearchForm, UserVo } from '@/service/api/system/user'
|
||||||
|
|
||||||
|
import {
|
||||||
|
assignUserRole,
|
||||||
|
fetchNormalRoleForUser,
|
||||||
|
fetchRoleList,
|
||||||
|
|
||||||
|
} from '@/service/api/system/role'
|
||||||
|
import type { RoleVo } from '@/service/api/system/role'
|
||||||
import { coiMsgBox, coiMsgError, coiMsgInfo, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
import { coiMsgBox, coiMsgError, coiMsgInfo, coiMsgSuccess, coiMsgWarning } from '@/utils/coi'
|
||||||
|
|
||||||
// 用户数据类型定义
|
|
||||||
interface UserData {
|
|
||||||
userId: number
|
|
||||||
loginName: string
|
|
||||||
userName: string
|
|
||||||
userType: string
|
|
||||||
email?: string
|
|
||||||
phone?: string
|
|
||||||
sex?: string
|
|
||||||
avatar?: string
|
|
||||||
userStatus: string // 0-启用 1-停用
|
|
||||||
loginIp?: string
|
|
||||||
loginTime?: string
|
|
||||||
pwdUpdateTime?: string
|
|
||||||
remark?: string
|
|
||||||
createBy?: string
|
|
||||||
createTime?: string
|
|
||||||
updateBy?: string
|
|
||||||
updateTime?: string
|
|
||||||
roleIds?: number[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 角色数据类型
|
|
||||||
interface RoleData {
|
|
||||||
roleId: number
|
|
||||||
roleName: string
|
|
||||||
roleCode: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索表单数据
|
|
||||||
interface SearchForm {
|
|
||||||
loginName?: string
|
|
||||||
userName?: string
|
|
||||||
phone?: string
|
|
||||||
userStatus?: string
|
|
||||||
beginTime?: string
|
|
||||||
endTime?: string
|
|
||||||
timeRange?: [number, number] | null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableData = ref<UserData[]>([])
|
const tableData = ref<UserVo[]>([])
|
||||||
const showModal = ref(false)
|
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)
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
const currentUser = ref<UserData | null>(null)
|
const currentUser = ref<UserVo | null>(null)
|
||||||
const selectedRows = ref<UserData[]>([])
|
const selectedRows = ref<UserVo[]>([])
|
||||||
const roleOptions = ref<RoleData[]>([])
|
const roleOptions = ref<RoleVo[]>([])
|
||||||
|
|
||||||
// 角色分配相关
|
// 角色分配相关
|
||||||
const showRoleModal = ref(false)
|
const showRoleModal = ref(false)
|
||||||
const roleModalTitle = ref('分配角色')
|
const roleModalTitle = ref('分配角色')
|
||||||
const currentAssignUser = ref<UserData | null>(null)
|
const currentAssignUser = ref<UserVo | null>(null)
|
||||||
const availableRoles = ref<{ label: string, value: number }[]>([])
|
const availableRoles = ref<{ label: string, value: number }[]>([])
|
||||||
const selectedRoleIds = ref<number[]>([])
|
const selectedRoleIds = ref<number[]>([])
|
||||||
const roleLoading = ref(false)
|
const roleLoading = ref(false)
|
||||||
@ -82,7 +49,7 @@ const roleLoading = ref(false)
|
|||||||
// 重置密码相关
|
// 重置密码相关
|
||||||
const showResetPwdModal = ref(false)
|
const showResetPwdModal = ref(false)
|
||||||
const resetPwdFormRef = ref<FormInst | null>(null)
|
const resetPwdFormRef = ref<FormInst | null>(null)
|
||||||
const currentResetUser = ref<UserData | null>(null)
|
const currentResetUser = ref<UserVo | null>(null)
|
||||||
const resetPwdForm = ref({
|
const resetPwdForm = ref({
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
@ -91,7 +58,7 @@ const resetPwdForm = ref({
|
|||||||
// 头像查看相关
|
// 头像查看相关
|
||||||
const showAvatarModal = ref(false)
|
const showAvatarModal = ref(false)
|
||||||
const currentAvatar = ref('')
|
const currentAvatar = ref('')
|
||||||
const currentAvatarUser = ref<UserData | null>(null)
|
const currentAvatarUser = ref<UserVo | null>(null)
|
||||||
|
|
||||||
// 导入相关
|
// 导入相关
|
||||||
const showImportModal = ref(false)
|
const showImportModal = ref(false)
|
||||||
@ -100,6 +67,11 @@ const selectedFile = ref<File | null>(null)
|
|||||||
const updateSupport = ref(false)
|
const updateSupport = ref(false)
|
||||||
const uploadProgress = ref(0)
|
const uploadProgress = ref(0)
|
||||||
|
|
||||||
|
// 组件状态追踪
|
||||||
|
const isComponentMounted = ref(true)
|
||||||
|
const progressInterval = ref<NodeJS.Timeout | null>(null)
|
||||||
|
const createdBlobUrls = ref<string[]>([])
|
||||||
|
|
||||||
// 分页数据
|
// 分页数据
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
page: 1,
|
page: 1,
|
||||||
@ -110,7 +82,7 @@ const pagination = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 搜索表单数据
|
// 搜索表单数据
|
||||||
const searchForm = ref<SearchForm>({})
|
const searchForm = ref<UserSearchForm>({})
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
@ -176,7 +148,7 @@ const resetPwdRules = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns: DataTableColumns<UserData> = [
|
const columns: DataTableColumns<UserVo> = [
|
||||||
{
|
{
|
||||||
type: 'selection',
|
type: 'selection',
|
||||||
width: 50,
|
width: 50,
|
||||||
@ -379,6 +351,9 @@ const columns: DataTableColumns<UserData> = [
|
|||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
async function getUserList() {
|
async function getUserList() {
|
||||||
|
if (!isComponentMounted.value)
|
||||||
|
return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 构建请求参数,处理时间范围
|
// 构建请求参数,处理时间范围
|
||||||
@ -406,6 +381,10 @@ async function getUserList() {
|
|||||||
|
|
||||||
const { isSuccess, data } = await fetchUserPage(params)
|
const { isSuccess, data } = await fetchUserPage(params)
|
||||||
|
|
||||||
|
// 检查组件是否仍然挂载
|
||||||
|
if (!isComponentMounted.value)
|
||||||
|
return
|
||||||
|
|
||||||
if (isSuccess && data) {
|
if (isSuccess && data) {
|
||||||
tableData.value = data.records || []
|
tableData.value = data.records || []
|
||||||
pagination.value.itemCount = data.total || 0
|
pagination.value.itemCount = data.total || 0
|
||||||
@ -418,20 +397,25 @@ async function getUserList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
if (!isComponentMounted.value)
|
||||||
|
return
|
||||||
console.error('获取用户列表失败:', error)
|
console.error('获取用户列表失败:', error)
|
||||||
coiMsgError('获取用户列表失败,请检查网络连接')
|
coiMsgError('获取用户列表失败,请检查网络连接')
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
pagination.value.itemCount = 0
|
pagination.value.itemCount = 0
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
if (isComponentMounted.value) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取角色列表
|
// 获取角色列表
|
||||||
async function getRoleList() {
|
async function getRoleList() {
|
||||||
try {
|
try {
|
||||||
const { isSuccess, data } = await fetchRoleList()
|
const { isSuccess, data } = await fetchRoleList()
|
||||||
|
|
||||||
if (isSuccess && data) {
|
if (isSuccess && data) {
|
||||||
roleOptions.value = data
|
roleOptions.value = data
|
||||||
}
|
}
|
||||||
@ -520,7 +504,7 @@ function handleAdd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 编辑用户
|
// 编辑用户
|
||||||
function handleEdit(user: UserData) {
|
function handleEdit(user: UserVo) {
|
||||||
modalTitle.value = '编辑用户'
|
modalTitle.value = '编辑用户'
|
||||||
isEdit.value = true
|
isEdit.value = true
|
||||||
currentUser.value = user
|
currentUser.value = user
|
||||||
@ -594,7 +578,7 @@ async function handleBatchDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 切换用户状态
|
// 切换用户状态
|
||||||
async function handleToggleStatus(user: UserData) {
|
async function handleToggleStatus(user: UserVo) {
|
||||||
try {
|
try {
|
||||||
const newStatus = user.userStatus === '0' ? '1' : '0'
|
const newStatus = user.userStatus === '0' ? '1' : '0'
|
||||||
const statusText = newStatus === '0' ? '启用' : '停用'
|
const statusText = newStatus === '0' ? '启用' : '停用'
|
||||||
@ -617,8 +601,7 @@ async function handleToggleStatus(user: UserData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重置密码
|
// 重置密码
|
||||||
// 重置密码 - 打开弹框
|
function handleResetPassword(user: UserVo) {
|
||||||
function handleResetPassword(user: UserData) {
|
|
||||||
currentResetUser.value = user
|
currentResetUser.value = user
|
||||||
resetPwdForm.value = {
|
resetPwdForm.value = {
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
@ -670,7 +653,7 @@ function handleCancelResetPassword() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查看头像
|
// 查看头像
|
||||||
function handleViewAvatar(user: UserData) {
|
function handleViewAvatar(user: UserVo) {
|
||||||
currentAvatarUser.value = user
|
currentAvatarUser.value = user
|
||||||
// 如果用户有头像URL,使用头像URL,否则生成默认头像
|
// 如果用户有头像URL,使用头像URL,否则生成默认头像
|
||||||
if (user.avatar) {
|
if (user.avatar) {
|
||||||
@ -704,7 +687,9 @@ function handleViewAvatar(user: UserData) {
|
|||||||
</svg>
|
</svg>
|
||||||
`
|
`
|
||||||
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
||||||
currentAvatar.value = URL.createObjectURL(blob)
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
currentAvatar.value = blobUrl
|
||||||
|
createdBlobUrls.value.push(blobUrl)
|
||||||
}
|
}
|
||||||
showAvatarModal.value = true
|
showAvatarModal.value = true
|
||||||
}
|
}
|
||||||
@ -715,6 +700,11 @@ function handleCloseAvatar() {
|
|||||||
// 如果是生成的默认头像,释放URL
|
// 如果是生成的默认头像,释放URL
|
||||||
if (currentAvatar.value.startsWith('blob:')) {
|
if (currentAvatar.value.startsWith('blob:')) {
|
||||||
URL.revokeObjectURL(currentAvatar.value)
|
URL.revokeObjectURL(currentAvatar.value)
|
||||||
|
// 从追踪数组中移除
|
||||||
|
const index = createdBlobUrls.value.indexOf(currentAvatar.value)
|
||||||
|
if (index > -1) {
|
||||||
|
createdBlobUrls.value.splice(index, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
currentAvatar.value = ''
|
currentAvatar.value = ''
|
||||||
currentAvatarUser.value = null
|
currentAvatarUser.value = null
|
||||||
@ -740,7 +730,9 @@ function handleAvatarError(event: Event) {
|
|||||||
</svg>
|
</svg>
|
||||||
`
|
`
|
||||||
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
||||||
img.src = URL.createObjectURL(blob)
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
img.src = blobUrl
|
||||||
|
createdBlobUrls.value.push(blobUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,8 +875,14 @@ async function handleConfirmImport() {
|
|||||||
importLoading.value = true
|
importLoading.value = true
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0
|
||||||
|
|
||||||
|
// 清理之前的定时器
|
||||||
|
if (progressInterval.value) {
|
||||||
|
clearInterval(progressInterval.value)
|
||||||
|
progressInterval.value = null
|
||||||
|
}
|
||||||
|
|
||||||
// 模拟上传进度
|
// 模拟上传进度
|
||||||
const progressInterval = setInterval(() => {
|
progressInterval.value = setInterval(() => {
|
||||||
if (uploadProgress.value < 90) {
|
if (uploadProgress.value < 90) {
|
||||||
uploadProgress.value += 10
|
uploadProgress.value += 10
|
||||||
}
|
}
|
||||||
@ -892,7 +890,14 @@ async function handleConfirmImport() {
|
|||||||
|
|
||||||
const response = await importUserData(selectedFile.value, updateSupport.value)
|
const response = await importUserData(selectedFile.value, updateSupport.value)
|
||||||
|
|
||||||
clearInterval(progressInterval)
|
// 检查组件是否仍然挂载
|
||||||
|
if (!isComponentMounted.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (progressInterval.value) {
|
||||||
|
clearInterval(progressInterval.value)
|
||||||
|
progressInterval.value = null
|
||||||
|
}
|
||||||
uploadProgress.value = 100
|
uploadProgress.value = 100
|
||||||
|
|
||||||
if (response.isSuccess && response.data) {
|
if (response.isSuccess && response.data) {
|
||||||
@ -910,14 +915,24 @@ async function handleConfirmImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
if (isComponentMounted.value) {
|
||||||
console.error('导入失败:', error)
|
console.error('导入失败:', error)
|
||||||
coiMsgError('导入失败,请检查文件格式或联系管理员')
|
coiMsgError('导入失败,请检查文件格式或联系管理员')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
finally {
|
finally {
|
||||||
|
// 清理定时器
|
||||||
|
if (progressInterval.value) {
|
||||||
|
clearInterval(progressInterval.value)
|
||||||
|
progressInterval.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isComponentMounted.value) {
|
||||||
importLoading.value = false
|
importLoading.value = false
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 取消导入
|
// 取消导入
|
||||||
function handleCancelImport() {
|
function handleCancelImport() {
|
||||||
@ -928,7 +943,7 @@ function handleCancelImport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 分配角色
|
// 分配角色
|
||||||
async function handleAssignRole(user: UserData) {
|
async function handleAssignRole(user: UserVo) {
|
||||||
if (!user?.userId) {
|
if (!user?.userId) {
|
||||||
coiMsgError('用户信息无效')
|
coiMsgError('用户信息无效')
|
||||||
return
|
return
|
||||||
@ -1087,6 +1102,29 @@ onMounted(() => {
|
|||||||
getUserList()
|
getUserList()
|
||||||
getRoleList()
|
getRoleList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载前清理资源
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 标记组件已卸载
|
||||||
|
isComponentMounted.value = false
|
||||||
|
|
||||||
|
// 清理定时器
|
||||||
|
if (progressInterval.value) {
|
||||||
|
clearInterval(progressInterval.value)
|
||||||
|
progressInterval.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理所有创建的Blob URL
|
||||||
|
createdBlobUrls.value.forEach((url) => {
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
createdBlobUrls.value = []
|
||||||
|
|
||||||
|
// 清理当前头像URL
|
||||||
|
if (currentAvatar.value.startsWith('blob:')) {
|
||||||
|
URL.revokeObjectURL(currentAvatar.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -1297,7 +1335,7 @@ onMounted(() => {
|
|||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:row-key="(row: UserData) => row.userId"
|
:row-key="(row: UserVo) => row.userId"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user