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

View File

@ -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"