Files
one-pipe-system/src/views/system/role/index.vue
sexygoat b510b4539f
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 11m14s
修复bug
2026-04-08 10:19:25 +08:00

1216 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ArtTableFullScreen>
<div class="role-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
:show-expand="false"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton @click="showDialog('add')" v-permission="'role:add'">新增角色</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="ID"
:loading="loading"
:data="roleList"
:currentPage="pagination.page"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增角色' : '编辑角色'"
width="30%"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="角色名称" prop="role_name">
<ElInput v-model="form.role_name" placeholder="请输入角色名称" />
</ElFormItem>
<ElFormItem label="角色描述" prop="role_desc">
<ElInput
v-model="form.role_desc"
type="textarea"
:rows="3"
placeholder="请输入角色描述"
/>
</ElFormItem>
<ElFormItem label="角色类型" prop="role_type">
<ElSelect
v-model="form.role_type"
placeholder="请选择角色类型"
style="width: 100%"
:disabled="dialogType === 'edit'"
>
<ElOption
v-for="item in ROLE_TYPE_OPTIONS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="状态">
<ElSwitch
v-model="form.status"
:active-value="CommonStatus.ENABLED"
:inactive-value="CommonStatus.DISABLED"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
提交
</ElButton>
</div>
</template>
</ElDialog>
<!-- 分配权限对话框 -->
<ElDialog v-model="permissionDialogVisible" width="800px">
<template #header>
<div class="dialog-header">
<span class="dialog-title">分配权限</span>
<div class="role-info">
<ElTag :type="currentRole.role_type === 1 ? 'primary' : 'success'" size="small">
{{ currentRole.role_type === 1 ? '平台角色' : '客户角色' }}
</ElTag>
<span class="role-name">{{ currentRole.role_name }}</span>
</div>
</div>
</template>
<div class="permission-tree-transfer-container">
<!-- 左侧可分配权限树 -->
<div class="transfer-panel">
<div class="panel-header">
<span class="panel-title">可分配权限</span>
<ElInput
v-model="leftTreeFilter"
placeholder="搜索权限"
clearable
size="small"
style="width: 180px"
/>
</div>
<div class="panel-body">
<ElTree
ref="leftTreeRef"
:data="availablePermissions"
:props="{ children: 'children', label: 'label' }"
node-key="id"
show-checkbox
check-strictly
:filter-node-method="filterNode"
:default-expand-all="false"
:check-on-click-node="false"
@check="handleLeftTreeCheck"
class="permission-tree"
>
<template #default="{ node, data }">
<span style="display: flex; gap: 8px; align-items: center">
<span>{{ node.label }}</span>
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
</ElTag>
</span>
</template>
</ElTree>
</div>
</div>
<!-- 中间操作按钮 -->
<div class="transfer-buttons">
<ElButton
type="primary"
:icon="'ArrowRight'"
@click="addPermissions"
:disabled="getLeftCheckedKeys().length === 0"
>
添加
</ElButton>
</div>
<!-- 右侧已分配权限树 -->
<div class="transfer-panel">
<div class="panel-header">
<span class="panel-title">已分配权限</span>
<ElInput
v-model="rightTreeFilter"
placeholder="搜索权限"
clearable
size="small"
style="width: 180px"
/>
</div>
<div class="panel-body">
<ElTree
ref="rightTreeRef"
:data="assignedPermissions"
:props="{ children: 'children', label: 'label' }"
node-key="id"
:filter-node-method="filterNode"
:default-expand-all="false"
class="permission-tree permission-tree-with-action"
>
<template #default="{ node, data }">
<span class="tree-node-content">
<span class="tree-node-label">
<span>{{ node.label }}</span>
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
</ElTag>
</span>
<ElButton
type="danger"
size="small"
link
@click="removeSinglePermission(data)"
>
移除
</ElButton>
</span>
</template>
</ElTree>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="permissionDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { RoleService, PermissionService } from '@/api/modules'
import {
ElMessage,
ElMessageBox,
ElTag,
ElTree,
ElSwitch,
ElButton,
ElInput,
ElSelect,
ElOption
} from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type { PlatformRole, PermissionTreeNode } from '@/types/api'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { formatDateTime } from '@/utils/business/format'
import {
CommonStatus,
getStatusText,
ROLE_TYPE_OPTIONS,
getRoleTypeLabel
} from '@/config/constants'
defineOptions({ name: 'Role' })
const { hasAuth } = useAuth()
const dialogVisible = ref(false)
const permissionDialogVisible = ref(false)
const loading = ref(false)
const submitLoading = ref(false)
const tableRef = ref()
const leftTreeRef = ref()
const rightTreeRef = ref()
const currentRoleId = ref<number>(0)
const currentRole = ref<{ role_name: string; role_type: number }>({
role_name: '',
role_type: 1
})
const selectedPermissions = ref<number[]>([])
const originalPermissions = ref<number[]>([]) // 保存原始权限,用于对比
const availablePermissions = ref<any[]>([]) // 左侧可分配权限树数据
const assignedPermissions = ref<any[]>([]) // 右侧已分配权限树数据
const allPermissionsMap = ref<Map<number, any>>(new Map()) // 所有权限的映射表
const leftTreeFilter = ref('') // 左侧树搜索关键字
const rightTreeFilter = ref('') // 右侧树搜索关键字
const isHandlingCheck = ref(false) // 标志位:是否正在处理勾选事件
// 搜索表单初始值
const initialSearchState = {
role_name: '',
role_type: undefined as number | undefined,
status: undefined as number | undefined
}
// 搜索表单
const searchForm = reactive({ ...initialSearchState })
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '角色名称',
prop: 'role_name',
type: 'input',
config: {
clearable: true,
placeholder: '请输入角色名称'
}
},
{
label: '角色类型',
prop: 'role_type',
type: 'select',
config: {
clearable: true,
placeholder: '请选择角色类型'
},
options: () => ROLE_TYPE_OPTIONS
},
{
label: '状态',
prop: 'status',
type: 'select',
config: {
clearable: true,
placeholder: '请选择状态'
},
options: () => [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]
}
]
// 分页
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
// 列配置
const columnOptions = [
{ label: '角色名称', prop: 'role_name' },
{ label: '角色描述', prop: 'role_desc' },
{ label: '角色类型', prop: 'role_type' },
{ label: '状态', prop: 'status' },
{ label: '创建时间', prop: 'CreatedAt' }
]
const formRef = ref<FormInstance>()
const rules = reactive<FormRules>({
role_name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
role_desc: [{ max: 200, message: '描述不能超过200个字符', trigger: 'blur' }],
role_type: [{ required: true, message: '请选择角色类型', trigger: 'change' }]
})
const form = reactive<any>({
id: 0,
role_name: '',
role_desc: '',
role_type: 1,
status: CommonStatus.ENABLED
})
const roleList = ref<PlatformRole[]>([])
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'role_name',
label: '角色名称',
width: 180
},
{
prop: 'role_type',
label: '角色类型',
width: 100,
formatter: (row: any) => {
return h(ElTag, { type: row.role_type === 1 ? 'primary' : 'success' }, () =>
getRoleTypeLabel(row.role_type)
)
}
},
{
prop: 'status',
label: '状态',
width: 100,
formatter: (row: any) => {
return h(ElSwitch, {
modelValue: row.status,
activeValue: CommonStatus.ENABLED,
inactiveValue: CommonStatus.DISABLED,
activeText: getStatusText(CommonStatus.ENABLED),
inactiveText: getStatusText(CommonStatus.DISABLED),
inlinePrompt: true,
disabled: !hasAuth('role:update_status'),
'onUpdate:modelValue': (val: string | number | boolean) =>
handleStatusChange(row, val as number)
})
}
},
{
prop: 'role_desc',
label: '角色描述'
},
{
prop: 'CreatedAt',
label: '创建时间',
width: 180,
formatter: (row: any) => formatDateTime(row.CreatedAt)
},
{
prop: 'actions',
label: '操作',
width: 220,
fixed: 'right',
formatter: (row: any) => {
const buttons: any[] = []
if (hasAuth('role:permission')) {
buttons.push(
h(
ElButton,
{
type: 'primary',
link: true,
onClick: () => showPermissionDialog(row)
},
() => '分配权限'
)
)
}
if (hasAuth('role:edit')) {
buttons.push(
h(
ElButton,
{
type: 'primary',
link: true,
onClick: () => showDialog('edit', row)
},
() => '编辑'
)
)
}
if (hasAuth('role:delete')) {
buttons.push(
h(
ElButton,
{
type: 'danger',
link: true,
onClick: () => deleteRole(row)
},
() => '删除'
)
)
}
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
}
}
])
onMounted(() => {
getTableData()
})
// 监听搜索关键字变化
watch(leftTreeFilter, (val) => {
leftTreeRef.value?.filter(val)
})
watch(rightTreeFilter, (val) => {
rightTreeRef.value?.filter(val)
})
// 树节点过滤方法
const filterNode = (value: string, data: any) => {
if (!value) return true
return data.label.toLowerCase().includes(value.toLowerCase())
}
// 更新父节点的勾选状态(计算应该半选的父节点)
const updateParentCheckStatus = (checkedKeys: number[]) => {
const checkedSet = new Set(checkedKeys)
const parentIdsToHalfCheck = new Set<number>()
// 递归检查节点的子节点勾选状态
const checkNode = (node: any): { allChecked: boolean; someChecked: boolean } => {
if (!node.children || node.children.length === 0) {
// 叶子节点
return {
allChecked: checkedSet.has(node.id),
someChecked: checkedSet.has(node.id)
}
}
// 有子节点的节点
let allChecked = true
let someChecked = false
for (const child of node.children) {
const childStatus = checkNode(child)
if (!childStatus.allChecked) {
allChecked = false
}
if (childStatus.someChecked) {
someChecked = true
}
}
// 当前节点被勾选
const currentNodeChecked = checkedSet.has(node.id)
// 判断父节点应该显示的状态:
// 1. 如果当前节点被勾选,但不是所有子节点都被勾选 -> 半选
// 2. 如果有子节点被勾选,但当前节点未被勾选 -> 半选
// 3. 如果所有子节点都被勾选且当前节点也被勾选 -> 全选
if (currentNodeChecked) {
// 当前节点被勾选
if (!allChecked) {
// 但不是所有子节点都被勾选 -> 半选
parentIdsToHalfCheck.add(node.id)
}
// 如果 allChecked则为全选不需要设置半选
} else if (someChecked) {
// 当前节点未勾选,但有子节点被勾选 -> 半选
parentIdsToHalfCheck.add(node.id)
}
return {
allChecked: allChecked && currentNodeChecked,
someChecked: someChecked || currentNodeChecked
}
}
// 检查所有顶层节点
availablePermissions.value.forEach((node) => checkNode(node))
return Array.from(parentIdsToHalfCheck)
}
// 判断节点是否为一级菜单没有父节点或parent_id为0/null
const isFirstLevelMenu = (nodeId: number): boolean => {
const node = allPermissionsMap.value.get(nodeId)
if (!node) return false
return !node.parent_id || node.parent_id === 0
}
// 获取节点的所有子节点ID递归
const getAllChildrenIds = (nodeId: number): number[] => {
const childrenIds: number[] = []
const collectChildren = (id: number) => {
const node = allPermissionsMap.value.get(id)
if (!node || !node.children || node.children.length === 0) return
node.children.forEach((child: any) => {
childrenIds.push(child.id)
collectChildren(child.id)
})
}
collectChildren(nodeId)
return childrenIds
}
// 上一次勾选的keys用于判断是勾选还是取消勾选
const previousCheckedKeys = ref<number[]>([])
// 处理左侧树的勾选事件
const handleLeftTreeCheck = (data: any, checked: any) => {
if (isHandlingCheck.value) return
isHandlingCheck.value = true
try {
// 获取当前勾选的keys
let currentChecked = checked.checkedKeys as number[]
// 判断是勾选还是取消勾选通过对比当前和之前的keys
const isChecking =
currentChecked.includes(data.id) && !previousCheckedKeys.value.includes(data.id)
const isUnchecking =
!currentChecked.includes(data.id) && previousCheckedKeys.value.includes(data.id)
if (isChecking) {
// 正在勾选节点
// 判断当前节点是否为一级菜单
if (isFirstLevelMenu(data.id)) {
// 一级菜单:自动勾选所有子节点(包括所有子菜单和按钮)
const childrenIds = getAllChildrenIds(data.id)
const newCheckedKeys = [...new Set([...currentChecked, ...childrenIds])]
// 设置新的勾选状态
leftTreeRef.value?.setCheckedKeys(newCheckedKeys, false)
currentChecked = newCheckedKeys
} else {
// 二级及以下菜单/按钮:只勾选自己,不勾选子节点
const childrenIds = getAllChildrenIds(data.id)
// 从当前勾选中移除所有子节点(如果有的话)
const newCheckedKeys = currentChecked.filter((id) => !childrenIds.includes(id))
// 设置新的勾选状态
leftTreeRef.value?.setCheckedKeys(newCheckedKeys, false)
currentChecked = newCheckedKeys
}
} else if (isUnchecking) {
// 正在取消勾选节点
// 判断当前节点是否为一级菜单
if (isFirstLevelMenu(data.id)) {
// 一级菜单:自动取消所有子节点(包括所有子菜单和按钮)
const childrenIds = getAllChildrenIds(data.id)
const newCheckedKeys = currentChecked.filter((id) => !childrenIds.includes(id))
// 设置新的勾选状态
leftTreeRef.value?.setCheckedKeys(newCheckedKeys, false)
currentChecked = newCheckedKeys
}
// 二级及以下菜单/按钮:直接取消勾选,不需要额外处理
}
// 更新上一次勾选的keys
previousCheckedKeys.value = [...currentChecked]
// 计算应该半选的父节点
const halfCheckedIds = updateParentCheckStatus(currentChecked)
// 使用内部API直接操作树的半选状态
nextTick(() => {
if (leftTreeRef.value && leftTreeRef.value.store) {
// 清除所有半选状态
Object.values(leftTreeRef.value.store.nodesMap).forEach((node: any) => {
node.indeterminate = false
})
// 设置应该半选的节点
halfCheckedIds.forEach((id: number) => {
const node = leftTreeRef.value.store.nodesMap[id]
if (node) {
node.indeterminate = true
}
})
}
})
} finally {
isHandlingCheck.value = false
}
}
// 构建权限树数据结构(只包含启用的权限)
const buildTreeData = (treeNodes: PermissionTreeNode[]): any[] => {
return treeNodes
.filter((node) => node.status === 1) // 只显示启用的权限
.map((node) => ({
id: node.id,
label: node.perm_name,
perm_type: node.perm_type,
status: node.status ?? 1,
children:
node.children && node.children.length > 0 ? buildTreeData(node.children) : undefined
}))
}
// 构建权限映射表(包括所有节点和子节点)
const buildPermissionMap = (treeNodes: PermissionTreeNode[], map: Map<number, any>) => {
treeNodes.forEach((node) => {
map.set(node.id, node)
if (node.children && node.children.length > 0) {
buildPermissionMap(node.children, map)
}
})
}
// 根据权限ID列表过滤树节点包含指定ID的节点
const filterTreeByIds = (treeNodes: any[], ids: number[]): any[] => {
const result: any[] = []
treeNodes.forEach((node) => {
if (ids.includes(node.id)) {
const newNode = { ...node }
if (node.children && node.children.length > 0) {
newNode.children = filterTreeByIds(node.children, ids)
}
result.push(newNode)
} else if (node.children && node.children.length > 0) {
const filteredChildren = filterTreeByIds(node.children, ids)
if (filteredChildren.length > 0) {
result.push({
...node,
children: filteredChildren
})
}
}
})
return result
}
// 根据权限ID列表设置树节点的禁用状态
// 同时禁用状态为禁用(status=0)的权限
const setTreeNodesDisabled = (treeNodes: any[], idsToDisable: number[]): any[] => {
return treeNodes.map((node) => {
const newNode = {
...node,
// 如果已分配或状态为禁用,则禁用该节点
disabled: idsToDisable.includes(node.id) || node.status === 0
}
if (node.children && node.children.length > 0) {
newNode.children = setTreeNodesDisabled(node.children, idsToDisable)
}
return newNode
})
}
// 保存原始权限树数据
const originalPermissionTree = ref<PermissionTreeNode[]>([])
// 加载所有权限树
const loadAllPermissions = async () => {
try {
const res = await PermissionService.getPermissionTree()
if (res.code === 0) {
const treeData = res.data || []
// 保存原始树数据
originalPermissionTree.value = treeData
// 构建权限映射表
const map = new Map()
buildPermissionMap(treeData, map)
allPermissionsMap.value = map
// 构建树形数据
const fullTreeData = buildTreeData(treeData)
// 左侧显示所有权限,但已分配的权限设置为禁用
availablePermissions.value = setTreeNodesDisabled(
JSON.parse(JSON.stringify(fullTreeData)),
selectedPermissions.value
)
// 右侧显示已分配的权限
if (selectedPermissions.value.length > 0) {
assignedPermissions.value = filterTreeByIds(fullTreeData, selectedPermissions.value)
} else {
assignedPermissions.value = []
}
}
} catch (error) {
console.error('获取权限树失败:', error)
}
}
// 获取左侧树完全勾选的节点(不包括半选节点)
const getLeftCheckedKeys = (): number[] => {
if (!leftTreeRef.value) return []
return leftTreeRef.value.getCheckedKeys(false)
}
// 获取节点的所有父节点ID
const getParentNodeIds = (nodeId: number): number[] => {
const parentIds: number[] = []
// 在原始权限树中递归查找父节点
const findInTree = (
treeNodes: PermissionTreeNode[],
targetId: number,
parentId?: number
): number | null => {
for (const node of treeNodes) {
if (node.id === targetId) {
return parentId || null
}
if (node.children && node.children.length > 0) {
const found = findInTree(node.children, targetId, node.id)
if (found !== null) return found
}
}
return null
}
// 递归向上查找所有父节点
const findAllParents = (currentId: number) => {
const parentId = findInTree(originalPermissionTree.value, currentId)
if (parentId) {
parentIds.push(parentId)
findAllParents(parentId)
}
}
findAllParents(nodeId)
return parentIds
}
// 获取左侧树勾选的节点(包括必要的父节点,用于提交服务器)
const getLeftCheckedKeysWithHalf = (): number[] => {
if (!leftTreeRef.value) return []
const checkedKeys = leftTreeRef.value.getCheckedKeys(false)
const allIds = new Set<number>(checkedKeys)
// 为每个勾选的节点找到所有父节点
checkedKeys.forEach((key: number) => {
const parents = getParentNodeIds(key)
parents.forEach((parentId) => allIds.add(parentId))
})
return Array.from(allIds)
}
// 添加权限
const addPermissions = async () => {
const checkedKeys = getLeftCheckedKeys()
if (checkedKeys.length === 0) return
try {
// 提交到服务器时包含勾选和半选节点
const keysToSubmit = getLeftCheckedKeysWithHalf()
await RoleService.assignPermissions(currentRoleId.value, keysToSubmit)
// 本地只记录完全勾选的节点
selectedPermissions.value = [...new Set([...selectedPermissions.value, ...checkedKeys])]
// 重新构建左右两侧树
const fullTreeData = buildTreeData(originalPermissionTree.value)
// 左侧显示所有权限,但已分配的权限设置为禁用
availablePermissions.value = setTreeNodesDisabled(
JSON.parse(JSON.stringify(fullTreeData)),
selectedPermissions.value
)
// 右侧显示已分配的权限
assignedPermissions.value = filterTreeByIds(fullTreeData, selectedPermissions.value)
// 清空左侧树的勾选状态
leftTreeRef.value?.setCheckedKeys([], false)
previousCheckedKeys.value = []
ElMessage.success('权限添加成功')
} catch (error) {
console.error('添加权限失败:', error)
ElMessage.error('权限添加失败')
}
}
// 检查节点是否有子节点(子菜单或按钮)
const hasChildren = (data: any): boolean => {
return data.children && data.children.length > 0
}
// 移除单个权限
const removeSinglePermission = async (data: any) => {
// 检查是否有子菜单或按钮
if (hasChildren(data)) {
ElMessage.warning('该权限下还有子菜单或按钮,请先移除子项后再移除此权限')
return
}
try {
// 保存右侧树的展开节点
const expandedKeys = rightTreeRef.value?.store?.nodesMap
? Object.keys(rightTreeRef.value.store.nodesMap)
.filter((key) => rightTreeRef.value.store.nodesMap[key].expanded)
.map((key) => Number(key))
: []
await RoleService.removePermission(currentRoleId.value, data.id)
// 更新已选权限列表
selectedPermissions.value = selectedPermissions.value.filter((id) => id !== data.id)
// 重新构建左右两侧树
const fullTreeData = buildTreeData(originalPermissionTree.value)
// 左侧显示所有权限,但已分配的权限设置为禁用
availablePermissions.value = setTreeNodesDisabled(
JSON.parse(JSON.stringify(fullTreeData)),
selectedPermissions.value
)
// 右侧显示已分配的权限
if (selectedPermissions.value.length > 0) {
assignedPermissions.value = filterTreeByIds(fullTreeData, selectedPermissions.value)
} else {
assignedPermissions.value = []
}
// 等待DOM更新后恢复展开状态
await nextTick()
if (rightTreeRef.value && expandedKeys.length > 0) {
expandedKeys.forEach((key) => {
const node = rightTreeRef.value.store.nodesMap[key]
if (node && !node.isLeaf) {
node.expanded = true
}
})
}
ElMessage.success('权限移除成功')
} catch (error) {
console.error('移除权限失败:', error)
ElMessage.error('权限移除失败')
}
}
// 显示分配权限对话框
const showPermissionDialog = async (row: PlatformRole) => {
currentRoleId.value = row.ID
currentRole.value = {
role_name: row.role_name,
role_type: row.role_type
}
selectedPermissions.value = []
originalPermissions.value = []
previousCheckedKeys.value = []
leftTreeFilter.value = ''
rightTreeFilter.value = ''
try {
// 加载当前角色的权限
const res = await RoleService.getRolePermissions(row.ID)
if (res.code === 0 || Array.isArray(res.data)) {
// 提取权限ID数组
const permissions = res.data || []
if (Array.isArray(permissions) && permissions.length > 0) {
// 如果返回的是权限对象数组提取ID或id字段
if (typeof permissions[0] === 'object') {
// 优先使用ID字段如果没有则使用id字段
if ('ID' in permissions[0]) {
selectedPermissions.value = permissions.map((perm: any) => perm.ID)
} else if ('id' in permissions[0]) {
selectedPermissions.value = permissions.map((perm: any) => perm.id)
}
} else {
// 如果返回的是ID数组
selectedPermissions.value = permissions
}
}
// 保存原始权限,用于后续对比
originalPermissions.value = [...selectedPermissions.value]
}
// 每次打开对话框时重新加载最新的权限列表
await loadAllPermissions()
// 数据加载完成后再打开对话框
permissionDialogVisible.value = true
} catch (error) {
console.error('获取角色权限失败:', error)
}
}
// 获取角色列表
const getTableData = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
pageSize: pagination.pageSize,
role_name: searchForm.role_name || undefined,
role_type: searchForm.role_type,
status: searchForm.status
}
const res = await RoleService.getRoles(params)
if (res.code === 0) {
roleList.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(searchForm, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
const dialogType = ref('add')
// 显示新增/编辑对话框
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.id = row.ID
form.role_name = row.role_name
form.role_desc = row.role_desc
form.role_type = row.role_type
form.status = row.status
} else {
form.id = 0
form.role_name = ''
form.role_desc = ''
form.role_type = 1
form.status = CommonStatus.ENABLED
}
}
// 删除角色
const deleteRole = (row: any) => {
ElMessageBox.confirm(`确定删除角色 ${row.role_name} 吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
})
.then(async () => {
try {
await RoleService.deleteRole(row.ID)
ElMessage.success('删除成功')
await getTableData()
} catch (error) {
console.error(error)
}
})
.catch(() => {
// 用户取消删除
})
}
// 提交表单
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
submitLoading.value = true
try {
if (dialogType.value === 'add') {
const data = {
role_name: form.role_name,
role_desc: form.role_desc,
role_type: form.role_type,
status: form.status
}
await RoleService.createRole(data)
ElMessage.success('新增成功')
} else {
// 更新角色时只发送允许的字段
const data = {
role_name: form.role_name,
role_desc: form.role_desc,
role_type: form.role_type,
status: form.status
}
await RoleService.updateRole(form.id, data)
ElMessage.success('修改成功')
}
dialogVisible.value = false
formEl.resetFields()
await getTableData()
} catch (error) {
console.error(error)
} finally {
submitLoading.value = false
}
}
})
}
// 状态切换
const handleStatusChange = async (row: any, newStatus: number) => {
const oldStatus = row.status
// 先更新UI
row.status = newStatus
try {
await RoleService.updateRoleStatus(row.ID, newStatus as 0 | 1)
ElMessage.success('状态切换成功')
} catch (error) {
// 切换失败,恢复原状态
row.status = oldStatus
console.error(error)
}
}
</script>
<style scoped lang="scss">
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.dialog-title {
font-size: 18px;
font-weight: 600;
}
.role-info {
display: flex;
gap: 8px;
align-items: center;
.role-name {
font-size: 14px;
color: var(--el-text-color-regular);
}
}
}
.permission-tree-transfer-container {
display: flex;
gap: 20px;
align-items: stretch;
justify-content: space-between;
min-height: 500px;
padding: 20px 0;
.transfer-panel {
display: flex;
flex: 1;
flex-direction: column;
max-width: 340px;
overflow: hidden;
border: 1px solid var(--el-border-color);
border-radius: 4px;
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color);
.panel-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.panel-body {
flex: 1;
padding: 12px;
overflow-y: auto;
.permission-tree {
:deep(.el-tree-node) {
margin: 4px 0;
}
:deep(.el-tree-node__content) {
height: 36px;
padding-right: 8px;
line-height: 36px;
}
:deep(.el-tree-node__label) {
flex: 1;
}
}
.permission-tree-with-action {
:deep(.el-tree-node__content) {
height: auto;
min-height: 36px;
padding: 4px 8px 4px 0;
}
.tree-node-content {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
width: 100%;
.tree-node-label {
display: flex;
flex: 1;
gap: 8px;
align-items: center;
}
.el-button {
flex-shrink: 0;
margin-left: auto;
}
}
}
}
}
.transfer-buttons {
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
justify-content: center;
padding: 0 10px;
.el-button {
width: 100px;
}
}
}
</style>