refactor(system): 优化用户和角色管理页面交互体验
**角色管理页面优化:** - 新增批量修改和批量分配权限按钮,提升操作效率 - 优化权限分配界面,新增展开/折叠、全选/全不选、父子联动等功能按钮 - 所有按钮统一添加图标,改善视觉效果和用户体验 - 权限标识码使用 NTag 组件显示,遵循主题色统一规范 - 调整操作列宽度,适应新增的权限分配按钮 **用户管理页面优化:** - 移除调试日志,优化代码可读性 - 改进文件上传大小限制从10MB调整为2MB,提升上传体验 - 优化错误处理,提供更详细的错误信息反馈 - 改进Excel导入功能的错误提示和异常处理 **个人中心页面优化:** - 头像上传大小限制从5MB调整为2MB - 优化头像上传错误处理,支持解析后端返回的具体错误信息 - 改进文件大小超限提示,显示当前文件大小和最大允许大小 - 增强网络错误和HTTP状态码错误的用户友好提示
This commit is contained in:
parent
0e202ce003
commit
8360dbc665
@ -422,15 +422,16 @@ async function handleAvatarChange(event: Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证文件大小(5MB)
|
// 验证文件大小(2MB)
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
const fileSizeMB = file.size / 1024 / 1024
|
||||||
coiMsgError('图片大小不能超过5MB')
|
if (fileSizeMB > 2) {
|
||||||
|
coiMsgError(`头像文件大小超出限制!当前文件:${fileSizeMB.toFixed(2)}MB,最大允许:2MB`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uploading.value = true
|
uploading.value = true
|
||||||
const result = await uploadAvatar(file, 5)
|
const result = await uploadAvatar(file, 2)
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
// 使用配置文件中的服务地址构建头像访问URL
|
// 使用配置文件中的服务地址构建头像访问URL
|
||||||
const baseUrl = serviceConfig[import.meta.env.MODE].url
|
const baseUrl = serviceConfig[import.meta.env.MODE].url
|
||||||
@ -446,8 +447,38 @@ async function handleAvatarChange(event: Event) {
|
|||||||
coiMsgError('头像上传失败')
|
coiMsgError('头像上传失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch (error: any) {
|
||||||
coiMsgError('头像上传失败')
|
// 解析后端返回的错误信息
|
||||||
|
let errorMessage = '头像上传失败'
|
||||||
|
|
||||||
|
// 检查是否是服务返回的结构化错误
|
||||||
|
if (error?.isSuccess === false) {
|
||||||
|
// alova 返回的结构化错误
|
||||||
|
if (error.message) {
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
else if (error.msg) {
|
||||||
|
errorMessage = error.msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (error?.response?.data?.msg) {
|
||||||
|
// 原始HTTP响应错误
|
||||||
|
errorMessage = error.response.data.msg
|
||||||
|
}
|
||||||
|
else if (error?.message) {
|
||||||
|
// 网络错误或其他错误
|
||||||
|
if (error.message.includes('413')) {
|
||||||
|
errorMessage = '头像文件大小超出限制,请选择较小的文件'
|
||||||
|
}
|
||||||
|
else if (error.message.includes('400')) {
|
||||||
|
errorMessage = '头像文件格式不支持或文件无效'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coiMsgError(errorMessage)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
uploading.value = false
|
uploading.value = false
|
||||||
|
|||||||
@ -103,6 +103,21 @@
|
|||||||
新增
|
新增
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|
||||||
|
<NButton
|
||||||
|
v-button="PERMISSIONS.ROLE.UPDATE"
|
||||||
|
type="info"
|
||||||
|
:disabled="selectedRows.length !== 1"
|
||||||
|
class="px-3 flex items-center"
|
||||||
|
@click="handleBatchEdit"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon class="mr-1" style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:edit />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
修改
|
||||||
|
</NButton>
|
||||||
|
|
||||||
<NButton
|
<NButton
|
||||||
v-button="PERMISSIONS.ROLE.DELETE"
|
v-button="PERMISSIONS.ROLE.DELETE"
|
||||||
type="error"
|
type="error"
|
||||||
@ -117,6 +132,21 @@
|
|||||||
</template>
|
</template>
|
||||||
删除
|
删除
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|
||||||
|
<NButton
|
||||||
|
v-button="PERMISSIONS.ROLE.MENU"
|
||||||
|
type="warning"
|
||||||
|
:disabled="selectedRows.length !== 1 || (selectedRows.length === 1 && selectedRows[0].roleId === 1)"
|
||||||
|
class="px-3 flex items-center"
|
||||||
|
@click="handleBatchAssignMenu"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon class="mr-1" style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:key />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
分配权限
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 text-sm text-gray-500">
|
<div class="flex items-center gap-4 text-sm text-gray-500">
|
||||||
@ -307,6 +337,12 @@
|
|||||||
:type="allExpanded ? 'default' : 'primary'"
|
:type="allExpanded ? 'default' : 'primary'"
|
||||||
@click="toggleExpandAll"
|
@click="toggleExpandAll"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:minus v-if="allExpanded" />
|
||||||
|
<icon-park-outline:plus v-else />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
{{ allExpanded ? '折叠' : '展开' }}
|
{{ allExpanded ? '折叠' : '展开' }}
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|
||||||
@ -315,6 +351,12 @@
|
|||||||
:type="allSelected ? 'default' : 'primary'"
|
:type="allSelected ? 'default' : 'primary'"
|
||||||
@click="toggleSelectAll"
|
@click="toggleSelectAll"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:close v-if="allSelected" />
|
||||||
|
<icon-park-outline:check v-else />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
{{ allSelected ? '全不选' : '全选' }}
|
{{ allSelected ? '全不选' : '全选' }}
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|
||||||
@ -323,6 +365,11 @@
|
|||||||
:type="cascadeEnabled ? 'primary' : 'default'"
|
:type="cascadeEnabled ? 'primary' : 'default'"
|
||||||
@click="toggleCascade"
|
@click="toggleCascade"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:link />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
父子联动
|
父子联动
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|
||||||
@ -331,6 +378,11 @@
|
|||||||
:type="showPermissionCode ? 'primary' : 'default'"
|
:type="showPermissionCode ? 'primary' : 'default'"
|
||||||
@click="togglePermissionCode"
|
@click="togglePermissionCode"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon style="transform: translateY(-1px)">
|
||||||
|
<icon-park-outline:battery-charge />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
权限标识
|
权限标识
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
@ -372,7 +424,7 @@ import type { DataTableColumns, FormInst } from 'naive-ui'
|
|||||||
import { NButton, NIcon, NPopconfirm, NSpace, NSwitch, NTag, NTooltip, NTree } from 'naive-ui'
|
import { NButton, NIcon, NPopconfirm, NSpace, NSwitch, NTag, NTooltip, NTree } from 'naive-ui'
|
||||||
import IconParkOutlineEditOne from '~icons/icon-park-outline/edit-one'
|
import IconParkOutlineEditOne from '~icons/icon-park-outline/edit-one'
|
||||||
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
import IconParkOutlineDelete from '~icons/icon-park-outline/delete'
|
||||||
import IconParkOutlineShield from '~icons/icon-park-outline/shield'
|
import IconParkOutlineKey from '~icons/icon-park-outline/key'
|
||||||
import CoiDialog from '@/components/common/CoiDialog.vue'
|
import CoiDialog from '@/components/common/CoiDialog.vue'
|
||||||
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
import CoiEmpty from '@/components/common/CoiEmpty.vue'
|
||||||
import {
|
import {
|
||||||
@ -553,7 +605,7 @@ const columns: DataTableColumns<RoleVo> = [
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 160,
|
width: 200,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
@ -615,7 +667,7 @@ const columns: DataTableColumns<RoleVo> = [
|
|||||||
class: 'action-btn action-btn-warning',
|
class: 'action-btn action-btn-warning',
|
||||||
onClick: () => handleAssignMenu(row),
|
onClick: () => handleAssignMenu(row),
|
||||||
}, {
|
}, {
|
||||||
icon: () => h(NIcon, { size: 18 }, { default: () => h(IconParkOutlineShield) }),
|
icon: () => h(NIcon, { size: 18 }, { default: () => h(IconParkOutlineKey) }),
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -780,6 +832,31 @@ function handleRowSelectionChange(rowKeys: (string | number)[]) {
|
|||||||
selectedRows.value = tableData.value.filter(row => numericKeys.includes(row.roleId))
|
selectedRows.value = tableData.value.filter(row => numericKeys.includes(row.roleId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量修改(选中一个角色进行修改)
|
||||||
|
function handleBatchEdit() {
|
||||||
|
if (selectedRows.value.length !== 1) {
|
||||||
|
coiMsgWarning('请选择一个角色进行修改')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleEdit(selectedRows.value[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量分配权限(选中一个角色进行权限分配)
|
||||||
|
function handleBatchAssignMenu() {
|
||||||
|
if (selectedRows.value.length !== 1) {
|
||||||
|
coiMsgWarning('请选择一个角色进行权限分配')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRole = selectedRows.value[0]
|
||||||
|
if (selectedRole.roleId === 1) {
|
||||||
|
coiMsgError('超级管理员角色无需分配权限')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAssignMenu(selectedRole)
|
||||||
|
}
|
||||||
|
|
||||||
// 新增角色
|
// 新增角色
|
||||||
async function handleAdd() {
|
async function handleAdd() {
|
||||||
modalTitle.value = '新增角色'
|
modalTitle.value = '新增角色'
|
||||||
@ -1111,10 +1188,12 @@ function renderPermissionCode({ option }: { option: any }) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return h('span', {
|
return h(NTag, {
|
||||||
class: 'ml-2 px-2 py-1 bg-blue-50 text-blue-600 text-xs rounded font-mono border border-blue-200',
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
class: 'ml-2 font-mono',
|
||||||
style: 'font-size: 11px; line-height: 1.2; transform: translateY(-5px); display: inline-flex; align-items: center;',
|
style: 'font-size: 11px; line-height: 1.2; transform: translateY(-5px); display: inline-flex; align-items: center;',
|
||||||
}, code)
|
}, { default: () => code })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
|
|||||||
@ -1140,14 +1140,12 @@ async function getUserList() {
|
|||||||
pagination.value.itemCount = data.total || 0
|
pagination.value.itemCount = data.total || 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.warn('获取用户列表失败,可能是权限或网络问题')
|
|
||||||
coiMsgError('获取用户列表失败,请检查网络连接或联系管理员')
|
coiMsgError('获取用户列表失败,请检查网络连接或联系管理员')
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
pagination.value.itemCount = 0
|
pagination.value.itemCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('获取用户列表失败:', error)
|
|
||||||
coiMsgError('获取用户列表失败,请检查网络连接')
|
coiMsgError('获取用户列表失败,请检查网络连接')
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
pagination.value.itemCount = 0
|
pagination.value.itemCount = 0
|
||||||
@ -1166,8 +1164,7 @@ async function getRoleList() {
|
|||||||
roleOptions.value = data
|
roleOptions.value = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.warn('获取角色列表失败,将不显示角色选择:', error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1291,8 +1288,7 @@ async function handleEdit(user: UserVo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('获取用户详情失败:', error)
|
|
||||||
// 如果获取详情出错,使用列表数据作为备用
|
// 如果获取详情出错,使用列表数据作为备用
|
||||||
formData.value = {
|
formData.value = {
|
||||||
loginName: user.loginName,
|
loginName: user.loginName,
|
||||||
@ -1324,8 +1320,7 @@ async function handleDelete(userId: number) {
|
|||||||
coiMsgError('删除失败')
|
coiMsgError('删除失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('删除失败:', error)
|
|
||||||
coiMsgError('删除失败')
|
coiMsgError('删除失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1359,8 +1354,7 @@ async function handleBatchDelete() {
|
|||||||
coiMsgError('批量删除失败')
|
coiMsgError('批量删除失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('批量删除失败:', error)
|
|
||||||
coiMsgError('批量删除失败')
|
coiMsgError('批量删除失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1382,8 +1376,7 @@ async function handleToggleStatus(user: UserVo) {
|
|||||||
coiMsgError(`用户${statusText}失败`)
|
coiMsgError(`用户${statusText}失败`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('状态修改失败:', error)
|
|
||||||
coiMsgError('状态修改失败,请检查网络连接')
|
coiMsgError('状态修改失败,请检查网络连接')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1425,8 +1418,7 @@ async function handleConfirmResetPassword() {
|
|||||||
coiMsgError('重置密码失败,请稍后重试')
|
coiMsgError('重置密码失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('重置密码API调用失败:', error)
|
|
||||||
coiMsgError('重置密码失败,请检查网络连接')
|
coiMsgError('重置密码失败,请检查网络连接')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1567,8 +1559,7 @@ async function handleExportCurrent() {
|
|||||||
downloadBlob(response, filename)
|
downloadBlob(response, filename)
|
||||||
coiMsgSuccess('导出成功')
|
coiMsgSuccess('导出成功')
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('导出失败:', error)
|
|
||||||
coiMsgError('导出失败,请重试')
|
coiMsgError('导出失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1583,8 +1574,7 @@ async function handleExportAll() {
|
|||||||
downloadBlob(response, filename)
|
downloadBlob(response, filename)
|
||||||
coiMsgSuccess('导出成功')
|
coiMsgSuccess('导出成功')
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('导出失败:', error)
|
|
||||||
coiMsgError('导出失败,请重试')
|
coiMsgError('导出失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1599,8 +1589,7 @@ async function handleDownloadTemplate() {
|
|||||||
downloadBlob(response, filename)
|
downloadBlob(response, filename)
|
||||||
coiMsgSuccess('模板下载成功')
|
coiMsgSuccess('模板下载成功')
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('模板下载失败:', error)
|
|
||||||
coiMsgError('模板下载失败,请重试')
|
coiMsgError('模板下载失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1631,6 +1620,8 @@ function handleImport() {
|
|||||||
|
|
||||||
// 文件选择处理
|
// 文件选择处理
|
||||||
function handleFileSelect(file: File) {
|
function handleFileSelect(file: File) {
|
||||||
|
// 添加调试日志
|
||||||
|
|
||||||
// 验证文件类型
|
// 验证文件类型
|
||||||
const allowedTypes = [
|
const allowedTypes = [
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
||||||
@ -1642,9 +1633,10 @@ function handleFileSelect(file: File) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证文件大小(限制为10MB)
|
// 验证文件大小(限制为2MB)
|
||||||
if (file.size > 10 * 1024 * 1024) {
|
const fileSizeMB = file.size / 1024 / 1024
|
||||||
coiMsgError('文件大小不能超过10MB')
|
if (fileSizeMB > 2) {
|
||||||
|
coiMsgError(`Excel文件大小超出限制!当前文件:${fileSizeMB.toFixed(2)}MB,最大允许:2MB`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1698,9 +1690,38 @@ async function handleConfirmImport() {
|
|||||||
coiMsgError(response.message || '导入失败,请重试')
|
coiMsgError(response.message || '导入失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error: any) {
|
||||||
console.error('导入失败:', error)
|
// 解析后端返回的错误信息
|
||||||
coiMsgError('导入失败,请检查文件格式或联系管理员')
|
let errorMessage = '导入失败,请检查文件格式或联系管理员'
|
||||||
|
|
||||||
|
// 检查是否是服务返回的结构化错误
|
||||||
|
if (error?.isSuccess === false) {
|
||||||
|
// alova 返回的结构化错误
|
||||||
|
if (error.message) {
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
else if (error.msg) {
|
||||||
|
errorMessage = error.msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (error?.response?.data?.msg) {
|
||||||
|
// 原始HTTP响应错误
|
||||||
|
errorMessage = error.response.data.msg
|
||||||
|
}
|
||||||
|
else if (error?.message) {
|
||||||
|
// 网络错误或其他错误
|
||||||
|
if (error.message.includes('413')) {
|
||||||
|
errorMessage = 'Excel文件大小超出限制,请选择较小的文件'
|
||||||
|
}
|
||||||
|
else if (error.message.includes('400')) {
|
||||||
|
errorMessage = 'Excel文件格式不支持或文件内容有误'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errorMessage = error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coiMsgError(errorMessage)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// 清理定时器
|
// 清理定时器
|
||||||
@ -1866,8 +1887,7 @@ async function handleSubmit() {
|
|||||||
coiMsgError(isEdit.value ? '更新失败,请稍后重试' : '创建失败,请稍后重试')
|
coiMsgError(isEdit.value ? '更新失败,请稍后重试' : '创建失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch {
|
||||||
console.error('API调用失败:', error)
|
|
||||||
coiMsgError(isEdit.value ? '更新失败,请检查网络连接' : '创建失败,请检查网络连接')
|
coiMsgError(isEdit.value ? '更新失败,请检查网络连接' : '创建失败,请检查网络连接')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user