This commit is contained in:
@@ -32,29 +32,14 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="contextMenuRef"
|
||||
:menu-items="contextMenuItems"
|
||||
:menu-width="120"
|
||||
@select="handleContextMenuSelect"
|
||||
/>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
@@ -80,8 +65,12 @@
|
||||
style="width: 100%"
|
||||
:disabled="dialogType === 'edit'"
|
||||
>
|
||||
<ElOption label="平台角色" :value="1" />
|
||||
<ElOption label="客户角色" :value="2" />
|
||||
<ElOption
|
||||
v-for="item in ROLE_TYPE_OPTIONS"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
@@ -240,13 +229,13 @@
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText } from '@/config/constants'
|
||||
import {
|
||||
CommonStatus,
|
||||
getStatusText,
|
||||
ROLE_TYPE_OPTIONS,
|
||||
getRoleTypeLabel
|
||||
} from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'Role' })
|
||||
|
||||
@@ -272,17 +261,6 @@
|
||||
const leftTreeFilter = ref('') // 左侧树搜索关键字
|
||||
const rightTreeFilter = ref('') // 右侧树搜索关键字
|
||||
const isHandlingCheck = ref(false) // 标志位:是否正在处理勾选事件
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentRow = ref<PlatformRole | null>(null)
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
@@ -313,10 +291,7 @@
|
||||
clearable: true,
|
||||
placeholder: '请选择角色类型'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '平台角色', value: 1 },
|
||||
{ label: '客户角色', value: 2 }
|
||||
]
|
||||
options: () => ROLE_TYPE_OPTIONS
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
@@ -342,7 +317,6 @@
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'ID' },
|
||||
{ label: '角色名称', prop: 'role_name' },
|
||||
{ label: '角色描述', prop: 'role_desc' },
|
||||
{ label: '角色类型', prop: 'role_type' },
|
||||
@@ -373,34 +347,25 @@
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'ID',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'role_name',
|
||||
label: '角色名称',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'role_desc',
|
||||
label: '角色描述'
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
prop: 'role_type',
|
||||
label: '角色类型',
|
||||
minWidth: 120,
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return h(ElTag, { type: row.role_type === 1 ? 'primary' : 'success' }, () =>
|
||||
row.role_type === 1 ? '平台角色' : '客户角色'
|
||||
getRoleTypeLabel(row.role_type)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
minWidth: 100,
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return h(ElSwitch, {
|
||||
modelValue: row.status,
|
||||
@@ -415,11 +380,68 @@
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
@@ -442,33 +464,6 @@
|
||||
return data.label.toLowerCase().includes(value.toLowerCase())
|
||||
}
|
||||
|
||||
// 获取节点的所有子节点ID(包括子菜单和按钮)
|
||||
const getAllChildrenIds = (node: any): number[] => {
|
||||
const ids: number[] = []
|
||||
const traverse = (n: any) => {
|
||||
if (n.children && n.children.length > 0) {
|
||||
n.children.forEach((child: any) => {
|
||||
ids.push(child.id)
|
||||
traverse(child)
|
||||
})
|
||||
}
|
||||
}
|
||||
traverse(node)
|
||||
return ids
|
||||
}
|
||||
|
||||
// 在树数据中查找节点
|
||||
const findNodeInTree = (treeData: any[], nodeId: number): any => {
|
||||
for (const node of treeData) {
|
||||
if (node.id === nodeId) return node
|
||||
if (node.children && node.children.length > 0) {
|
||||
const found = findNodeInTree(node.children, nodeId)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 更新父节点的勾选状态(计算应该半选的父节点)
|
||||
const updateParentCheckStatus = (checkedKeys: number[]) => {
|
||||
const checkedSet = new Set(checkedKeys)
|
||||
@@ -502,14 +497,19 @@
|
||||
const currentNodeChecked = checkedSet.has(node.id)
|
||||
|
||||
// 判断父节点应该显示的状态:
|
||||
// 1. 如果有子节点被勾选(someChecked=true),但不是全部子节点被勾选或当前节点未被勾选 -> 半选
|
||||
// 2. 如果所有子节点都被勾选且当前节点也被勾选 -> 全选
|
||||
if (someChecked) {
|
||||
if (!allChecked || !currentNodeChecked) {
|
||||
// 子节点部分被勾选,或者当前节点未被勾选 -> 半选
|
||||
// 1. 如果当前节点被勾选,但不是所有子节点都被勾选 -> 半选
|
||||
// 2. 如果有子节点被勾选,但当前节点未被勾选 -> 半选
|
||||
// 3. 如果所有子节点都被勾选且当前节点也被勾选 -> 全选
|
||||
if (currentNodeChecked) {
|
||||
// 当前节点被勾选
|
||||
if (!allChecked) {
|
||||
// 但不是所有子节点都被勾选 -> 半选
|
||||
parentIdsToHalfCheck.add(node.id)
|
||||
}
|
||||
// 如果 allChecked && currentNodeChecked,则为全选,不需要设置半选
|
||||
// 如果 allChecked,则为全选,不需要设置半选
|
||||
} else if (someChecked) {
|
||||
// 当前节点未勾选,但有子节点被勾选 -> 半选
|
||||
parentIdsToHalfCheck.add(node.id)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -524,6 +524,34 @@
|
||||
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
|
||||
@@ -532,7 +560,52 @@
|
||||
|
||||
try {
|
||||
// 获取当前勾选的keys
|
||||
const currentChecked = checked.checkedKeys as number[]
|
||||
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)
|
||||
@@ -626,23 +699,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 获取树中所有节点的ID(包括父节点和子节点)
|
||||
const getAllNodeIds = (treeNodes: any[]): number[] => {
|
||||
const ids: number[] = []
|
||||
|
||||
const traverse = (nodes: any[]) => {
|
||||
nodes.forEach((node) => {
|
||||
ids.push(node.id)
|
||||
if (node.children && node.children.length > 0) {
|
||||
traverse(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
traverse(treeNodes)
|
||||
return ids
|
||||
}
|
||||
|
||||
// 保存原始权限树数据
|
||||
const originalPermissionTree = ref<PermissionTreeNode[]>([])
|
||||
|
||||
@@ -765,6 +821,7 @@
|
||||
|
||||
// 清空左侧树的勾选状态
|
||||
leftTreeRef.value?.setCheckedKeys([], false)
|
||||
previousCheckedKeys.value = []
|
||||
|
||||
ElMessage.success('权限添加成功')
|
||||
} catch (error) {
|
||||
@@ -842,6 +899,7 @@
|
||||
}
|
||||
selectedPermissions.value = []
|
||||
originalPermissions.value = []
|
||||
previousCheckedKeys.value = []
|
||||
leftTreeFilter.value = ''
|
||||
rightTreeFilter.value = ''
|
||||
|
||||
@@ -999,6 +1057,7 @@
|
||||
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)
|
||||
@@ -1031,57 +1090,9 @@
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单项配置
|
||||
const contextMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
|
||||
if (hasAuth('role:permission')) {
|
||||
items.push({ key: 'assignPermission', label: '分配权限' })
|
||||
}
|
||||
|
||||
if (hasAuth('role:edit')) {
|
||||
items.push({ key: 'edit', label: '编辑' })
|
||||
}
|
||||
|
||||
if (hasAuth('role:delete')) {
|
||||
items.push({ key: 'delete', label: '删除' })
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: any, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentRow.value = row
|
||||
contextMenuRef.value?.show(event)
|
||||
}
|
||||
|
||||
// 处理右键菜单选择
|
||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentRow.value) return
|
||||
|
||||
switch (item.key) {
|
||||
case 'assignPermission':
|
||||
showPermissionDialog(currentRow.value)
|
||||
break
|
||||
case 'edit':
|
||||
showDialog('edit', currentRow.value)
|
||||
break
|
||||
case 'delete':
|
||||
deleteRole(currentRow.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user