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