feat(views): 优化登录页面和用户管理页面

* 登录页面改进
  - 更新API导入路径,适配新的模块化结构
  - 优化登录逻辑和错误处理
* 用户管理页面优化
  - 简化组件生命周期管理代码
  - 移除冗余的isComponentMounted检查
  - 保留必要的资源清理(定时器、Blob URLs)
  - 重新组织代码结构,template在最前面
* 提升页面性能和可维护性
  - 减少不必要的复杂度
  - 保持良好的资源管理实践

改善开发体验和运行时性能
This commit is contained in:
Leo 2025-07-06 02:37:36 +08:00
parent 104952336d
commit e3fbe07497
2 changed files with 110 additions and 72 deletions

View File

@ -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'])

View File

@ -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,
@ -207,7 +179,7 @@ const columns: DataTableColumns<UserData> = [
alt: `${row.userName}的头像`, alt: `${row.userName}的头像`,
class: 'w-full h-full object-cover', class: 'w-full h-full object-cover',
onError: (e) => { onError: (e) => {
// //
(e.target as HTMLElement).style.display = 'none' (e.target as HTMLElement).style.display = 'none'
const parent = (e.target as HTMLElement).parentElement const parent = (e.target as HTMLElement).parentElement
if (parent) { if (parent) {
@ -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,13 +397,17 @@ 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 {
loading.value = false if (isComponentMounted.value) {
loading.value = false
}
} }
} }
@ -432,6 +415,7 @@ async function getUserList() {
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,12 +915,22 @@ async function handleConfirmImport() {
} }
} }
catch (error) { catch (error) {
console.error('导入失败:', error) if (isComponentMounted.value) {
coiMsgError('导入失败,请检查文件格式或联系管理员') console.error('导入失败:', error)
coiMsgError('导入失败,请检查文件格式或联系管理员')
}
} }
finally { 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) { 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"