mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-11-10 02:57:12 +08:00
refactor: 重构角色管理
This commit is contained in:
@@ -6,7 +6,7 @@ export type * from './type'
|
|||||||
const BASE_URL = '/system/menu'
|
const BASE_URL = '/system/menu'
|
||||||
|
|
||||||
/** @desc 查询菜单列表 */
|
/** @desc 查询菜单列表 */
|
||||||
export function listMenu(query: T.MenuQuery) {
|
export function listMenu(query?: T.MenuQuery) {
|
||||||
return http.get<T.MenuResp[]>(`${BASE_URL}/tree`, query)
|
return http.get<T.MenuResp[]>(`${BASE_URL}/tree`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export type * from './type'
|
|||||||
const BASE_URL = '/system/role'
|
const BASE_URL = '/system/role'
|
||||||
|
|
||||||
/** @desc 查询角色列表 */
|
/** @desc 查询角色列表 */
|
||||||
export function listRole(query: T.RolePageQuery) {
|
export function listRole(query: T.RoleQuery) {
|
||||||
return http.get<PageRes<T.RoleResp[]>>(`${BASE_URL}`, query)
|
return http.get<T.RoleResp[]>(`${BASE_URL}/list`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @desc 查询角色详情 */
|
/** @desc 查询角色详情 */
|
||||||
@@ -30,12 +30,27 @@ export function deleteRole(ids: string | Array<string>) {
|
|||||||
return http.del(`${BASE_URL}/${ids}`)
|
return http.del(`${BASE_URL}/${ids}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @desc 修改角色权限 */
|
||||||
|
export function updateRolePermission(id: string, data: any) {
|
||||||
|
return http.put(`${BASE_URL}/${id}/permission`, data)
|
||||||
|
}
|
||||||
|
|
||||||
/** @desc 查询角色关联用户 */
|
/** @desc 查询角色关联用户 */
|
||||||
export function listRoleUsers(id: string) {
|
export function listRoleUser(id: string, query: T.RoleUserPageQuery) {
|
||||||
return http.get(`${BASE_URL}/${id}/user`)
|
return http.get<PageRes<T.RoleUserResp[]>>(`${BASE_URL}/${id}/user`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @desc 分配角色给用户 */
|
/** @desc 分配角色给用户 */
|
||||||
export function assignToUsers(id: string, userIds: Array<string>) {
|
export function assignToUsers(id: string, userIds: Array<string>) {
|
||||||
return http.post(`${BASE_URL}/${id}/user`, userIds)
|
return http.post(`${BASE_URL}/${id}/user`, userIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @desc 取消分配角色给用户 */
|
||||||
|
export function unassignFromUsers(userRoleIds: Array<string | number>) {
|
||||||
|
return http.del(`${BASE_URL}/user`, userRoleIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @desc 查询角色关联用户 ID */
|
||||||
|
export function listRoleUserId(id: string) {
|
||||||
|
return http.get(`${BASE_URL}/${id}/user/id`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface UserQuery {
|
|||||||
deptId?: string
|
deptId?: string
|
||||||
sort: Array<string>
|
sort: Array<string>
|
||||||
userIds?: Array<string>
|
userIds?: Array<string>
|
||||||
|
excludeUserIds?: Array<string>
|
||||||
}
|
}
|
||||||
export interface UserPageQuery extends UserQuery, PageQuery {}
|
export interface UserPageQuery extends UserQuery, PageQuery {}
|
||||||
|
|
||||||
@@ -62,11 +63,29 @@ export type RoleDetailResp = RoleResp & {
|
|||||||
menuCheckStrictly: boolean
|
menuCheckStrictly: boolean
|
||||||
deptCheckStrictly: boolean
|
deptCheckStrictly: boolean
|
||||||
}
|
}
|
||||||
|
export interface RoleUserResp {
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
nickname: string
|
||||||
|
gender: number
|
||||||
|
description: string
|
||||||
|
status: 1 | 2
|
||||||
|
isSystem?: boolean
|
||||||
|
deptId: string
|
||||||
|
deptName: string
|
||||||
|
roleIds: Array<number>
|
||||||
|
roleNames: Array<string>
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
export interface RoleQuery {
|
export interface RoleQuery {
|
||||||
description?: string
|
description?: string
|
||||||
sort: Array<string>
|
sort: Array<string>
|
||||||
}
|
}
|
||||||
export interface RolePageQuery extends RoleQuery, PageQuery {}
|
export interface RoleUserQuery {
|
||||||
|
description?: string
|
||||||
|
sort: Array<string>
|
||||||
|
}
|
||||||
|
export interface RoleUserPageQuery extends RoleUserQuery, PageQuery {}
|
||||||
|
|
||||||
/** 菜单类型 */
|
/** 菜单类型 */
|
||||||
export interface MenuResp {
|
export interface MenuResp {
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ onUnmounted(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
:deep(.arco-table-border .arco-table-container) {
|
:deep(.arco-table-border .arco-table-container) {
|
||||||
border: none;
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,11 +94,13 @@ const emit = defineEmits<{
|
|||||||
interface Props {
|
interface Props {
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
value: string | string[]
|
value: string | string[]
|
||||||
|
excludeValue?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询表单
|
// 查询表单
|
||||||
const queryForm = reactive<UserQuery>({
|
const queryForm = reactive<UserQuery>({
|
||||||
sort: ['t1.createTime,desc', 't1.id,desc'],
|
sort: ['t1.createTime,desc', 't1.id,desc'],
|
||||||
|
excludeUserIds: props.excludeValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 用户列表
|
// 用户列表
|
||||||
|
|||||||
@@ -313,6 +313,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gi_tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.arco-tabs-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.arco-tabs-content-list {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-tabs-content-item {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-tabs-pane {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.detail{
|
.detail{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ const reset = () => {
|
|||||||
// 删除
|
// 删除
|
||||||
const onDelete = (record: DictItemResp) => {
|
const onDelete = (record: DictItemResp) => {
|
||||||
return handleDelete(() => deleteDictItem(record.id), {
|
return handleDelete(() => deleteDictItem(record.id), {
|
||||||
content: `是否确定删除字典「${record.label}」?`,
|
content: `是否确定删除字典项「${record.label}」?`,
|
||||||
showModal: true,
|
showModal: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ const onMenuItemClick = (mode: string, node: DictResp) => {
|
|||||||
} else if (mode === 'delete') {
|
} else if (mode === 'delete') {
|
||||||
Modal.warning({
|
Modal.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: `是否确定删除 [${node.name}]?`,
|
content: `是否确定删除字典「${node.name}」?`,
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
okButtonProps: { status: 'danger' },
|
okButtonProps: { status: 'danger' },
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const handleRightMenuClick = async (mode: string, fileInfo: FileItem) => {
|
|||||||
if (mode === 'delete') {
|
if (mode === 'delete') {
|
||||||
Modal.warning({
|
Modal.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: `是否确定删除文件 [${fileInfo.name}]?`,
|
content: `是否确定删除文件「${fileInfo.name}」?`,
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
okButtonProps: { status: 'danger' },
|
okButtonProps: { status: 'danger' },
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-drawer
|
<a-drawer
|
||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
title="修改角色"
|
:title="title"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
:esc-to-close="false"
|
:esc-to-close="false"
|
||||||
:width="width >= 600 ? 600 : '100%'"
|
:width="width >= 600 ? 600 : '100%'"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<a-input v-model.trim="form.name" placeholder="请输入名称" />
|
<a-input v-model.trim="form.name" placeholder="请输入名称" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="编码" field="code">
|
<a-form-item label="编码" field="code">
|
||||||
<a-input v-model.trim="form.code" placeholder="请输入编码" :disabled="true" />
|
<a-input v-model.trim="form.code" placeholder="请输入编码" :disabled="isUpdate" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="排序" field="sort">
|
<a-form-item label="排序" field="sort">
|
||||||
<a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" />
|
<a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" />
|
||||||
@@ -30,26 +30,6 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
|
||||||
<legend>功能权限</legend>
|
|
||||||
<a-form-item hide-label :disabled="form.isSystem">
|
|
||||||
<a-space>
|
|
||||||
<a-checkbox v-model="isMenuExpanded" @change="onExpanded('menu')">展开/折叠</a-checkbox>
|
|
||||||
<a-checkbox v-model="isMenuCheckAll" @change="onCheckAll('menu')">全选/全不选</a-checkbox>
|
|
||||||
<a-checkbox v-model="form.menuCheckStrictly">父子联动</a-checkbox>
|
|
||||||
</a-space>
|
|
||||||
<template #extra>
|
|
||||||
<a-tree
|
|
||||||
ref="menuTreeRef"
|
|
||||||
class="menu-tree"
|
|
||||||
:data="menuList"
|
|
||||||
:default-expand-all="isMenuExpanded"
|
|
||||||
:check-strictly="!form.menuCheckStrictly"
|
|
||||||
checkable
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</a-form-item>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>数据权限</legend>
|
<legend>数据权限</legend>
|
||||||
<a-form-item hide-label field="dataScope">
|
<a-form-item hide-label field="dataScope">
|
||||||
@@ -84,9 +64,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type FormInstance, Message, type TreeNodeData } from '@arco-design/web-vue'
|
import { type FormInstance, Message, type TreeNodeData } from '@arco-design/web-vue'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { getRole, updateRole } from '@/apis/system/role'
|
import type { GiForm } from '@/components/GiForm'
|
||||||
|
|
||||||
import { useResetReactive } from '@/hooks'
|
import { useResetReactive } from '@/hooks'
|
||||||
import { useDept, useDict, useMenu } from '@/hooks/app'
|
import { useDept, useDict } from '@/hooks/app'
|
||||||
|
import { addRole, getRole, updateRole } from '@/apis'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'save-success'): void
|
(e: 'save-success'): void
|
||||||
@@ -96,10 +78,11 @@ const { width } = useWindowSize()
|
|||||||
|
|
||||||
const dataId = ref('')
|
const dataId = ref('')
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const formRef = ref<FormInstance>()
|
const isUpdate = computed(() => !!dataId.value)
|
||||||
|
const title = computed(() => (isUpdate.value ? '修改角色' : '新增角色'))
|
||||||
|
const formRef = ref<InstanceType<typeof GiForm>>()
|
||||||
const { data_scope_enum } = useDict('data_scope_enum')
|
const { data_scope_enum } = useDict('data_scope_enum')
|
||||||
const { deptList, getDeptList } = useDept()
|
const { deptList, getDeptList } = useDept()
|
||||||
const { menuList, getMenuList } = useMenu()
|
|
||||||
|
|
||||||
const rules: FormInstance['rules'] = {
|
const rules: FormInstance['rules'] = {
|
||||||
name: [{ required: true, message: '请输入名称' }],
|
name: [{ required: true, message: '请输入名称' }],
|
||||||
@@ -108,44 +91,24 @@ const rules: FormInstance['rules'] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [form, resetForm] = useResetReactive({
|
const [form, resetForm] = useResetReactive({
|
||||||
menuCheckStrictly: true,
|
|
||||||
deptCheckStrictly: true,
|
deptCheckStrictly: true,
|
||||||
sort: 999,
|
sort: 999,
|
||||||
dataScope: 4,
|
dataScope: 4,
|
||||||
})
|
})
|
||||||
|
|
||||||
const menuTreeRef = ref()
|
|
||||||
const deptTreeRef = ref()
|
const deptTreeRef = ref()
|
||||||
const isMenuExpanded = ref(false)
|
|
||||||
const isDeptExpanded = ref(true)
|
const isDeptExpanded = ref(true)
|
||||||
const isMenuCheckAll = ref(false)
|
|
||||||
const isDeptCheckAll = ref(false)
|
const isDeptCheckAll = ref(false)
|
||||||
// 重置
|
// 重置
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
isMenuExpanded.value = false
|
|
||||||
isMenuCheckAll.value = false
|
|
||||||
isDeptExpanded.value = true
|
isDeptExpanded.value = true
|
||||||
isDeptCheckAll.value = false
|
isDeptCheckAll.value = false
|
||||||
menuTreeRef.value?.expandAll(isMenuExpanded.value)
|
|
||||||
menuTreeRef.value?.checkAll(false)
|
|
||||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
||||||
deptTreeRef.value?.checkAll(false)
|
deptTreeRef.value?.checkAll(false)
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
resetForm()
|
resetForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有选中的菜单
|
|
||||||
const getMenuAllCheckedKeys = () => {
|
|
||||||
// 获取目前被选中的菜单
|
|
||||||
const checkedNodes = menuTreeRef.value?.getCheckedNodes()
|
|
||||||
const checkedKeys = checkedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
// 获取半选中的菜单
|
|
||||||
const halfCheckedNodes = menuTreeRef.value?.getHalfCheckedNodes()
|
|
||||||
const halfCheckedKeys = halfCheckedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
checkedKeys.unshift(...halfCheckedKeys)
|
|
||||||
return checkedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有选中的部门
|
// 获取所有选中的部门
|
||||||
const getDeptAllCheckedKeys = () => {
|
const getDeptAllCheckedKeys = () => {
|
||||||
if (!deptTreeRef.value) {
|
if (!deptTreeRef.value) {
|
||||||
@@ -162,32 +125,28 @@ const getDeptAllCheckedKeys = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 展开/折叠
|
// 展开/折叠
|
||||||
const onExpanded = (type: string) => {
|
const onExpanded = () => {
|
||||||
if (type === 'menu') {
|
|
||||||
menuTreeRef.value?.expandAll(isMenuExpanded.value)
|
|
||||||
} else if (type === 'dept') {
|
|
||||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 全选/全不选
|
// 全选/全不选
|
||||||
const onCheckAll = (type: string) => {
|
const onCheckAll = () => {
|
||||||
if (type === 'menu') {
|
|
||||||
menuTreeRef.value?.checkAll(isMenuCheckAll.value)
|
|
||||||
} else if (type === 'dept') {
|
|
||||||
deptTreeRef.value?.checkAll(isDeptCheckAll.value)
|
deptTreeRef.value?.checkAll(isDeptCheckAll.value)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
try {
|
|
||||||
const isInvalid = await formRef.value?.validate()
|
const isInvalid = await formRef.value?.validate()
|
||||||
if (isInvalid) return false
|
if (isInvalid) return false
|
||||||
form.menuIds = getMenuAllCheckedKeys()
|
try {
|
||||||
form.deptIds = getDeptAllCheckedKeys()
|
form.deptIds = getDeptAllCheckedKeys()
|
||||||
|
if (isUpdate.value) {
|
||||||
await updateRole(form, dataId.value)
|
await updateRole(form, dataId.value)
|
||||||
Message.success('修改成功')
|
Message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await addRole(form)
|
||||||
|
Message.success('新增成功')
|
||||||
|
}
|
||||||
emit('save-success')
|
emit('save-success')
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -195,23 +154,25 @@ const save = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开
|
// 新增
|
||||||
const onOpen = async (id: string) => {
|
const onAdd = async () => {
|
||||||
reset()
|
reset()
|
||||||
dataId.value = id
|
|
||||||
if (!menuList.value.length) {
|
|
||||||
await getMenuList()
|
|
||||||
}
|
|
||||||
if (!deptList.value.length) {
|
if (!deptList.value.length) {
|
||||||
await getDeptList()
|
await getDeptList()
|
||||||
}
|
}
|
||||||
|
dataId.value = ''
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
const onUpdate = async (id: string) => {
|
||||||
|
reset()
|
||||||
|
if (!deptList.value.length) {
|
||||||
|
await getDeptList()
|
||||||
|
}
|
||||||
|
dataId.value = id
|
||||||
const { data } = await getRole(id)
|
const { data } = await getRole(id)
|
||||||
Object.assign(form, data)
|
Object.assign(form, data)
|
||||||
data.menuIds?.forEach((node) => {
|
|
||||||
nextTick(() => {
|
|
||||||
menuTreeRef.value?.checkNode(node, true, true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
data.deptIds?.forEach((node) => {
|
data.deptIds?.forEach((node) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
deptTreeRef.value?.checkNode(node, true, true)
|
deptTreeRef.value?.checkNode(node, true, true)
|
||||||
@@ -220,7 +181,7 @@ const onOpen = async (id: string) => {
|
|||||||
visible.value = true
|
visible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ onOpen })
|
defineExpose({ onAdd, onUpdate })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -236,12 +197,4 @@ fieldset legend {
|
|||||||
border: 1px solid var(--color-neutral-3);
|
border: 1px solid var(--color-neutral-3);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
.menu-tree{
|
|
||||||
:deep(.arco-tree-node-is-leaf) {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
:deep(.arco-tree-node-indent-block){
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-modal
|
|
||||||
v-model:visible="visible"
|
|
||||||
title="新增角色"
|
|
||||||
:mask-closable="false"
|
|
||||||
:esc-to-close="true"
|
|
||||||
draggable
|
|
||||||
:width="width >= 600 ? 600 : '100%'"
|
|
||||||
@close="reset"
|
|
||||||
>
|
|
||||||
<a-steps :current="current" class="mb-15" @change="onChangeCurrent">
|
|
||||||
<a-step>基础信息</a-step>
|
|
||||||
<a-step>功能权限</a-step>
|
|
||||||
<a-step>数据权限</a-step>
|
|
||||||
</a-steps>
|
|
||||||
<a-form ref="formRef" :model="form" :rules="rules" size="large" auto-label-width>
|
|
||||||
<fieldset v-show="current === 1">
|
|
||||||
<a-form-item label="名称" field="name">
|
|
||||||
<a-input v-model.trim="form.name" placeholder="请输入名称" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="编码" field="code">
|
|
||||||
<a-input v-model.trim="form.code" placeholder="请输入编码" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="排序" field="sort">
|
|
||||||
<a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="描述" field="description">
|
|
||||||
<a-textarea
|
|
||||||
v-model.trim="form.description"
|
|
||||||
placeholder="请输入描述"
|
|
||||||
show-word-limit
|
|
||||||
:max-length="200"
|
|
||||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset v-show="current === 2">
|
|
||||||
<a-form-item hide-label :disabled="form.isSystem" class="w-full">
|
|
||||||
<a-space>
|
|
||||||
<a-checkbox v-model="isMenuExpanded" @change="onExpanded('menu')">展开/折叠</a-checkbox>
|
|
||||||
<a-checkbox v-model="isMenuCheckAll" @change="onCheckAll('menu')">全选/全不选</a-checkbox>
|
|
||||||
<a-checkbox v-model="form.menuCheckStrictly">父子联动</a-checkbox>
|
|
||||||
</a-space>
|
|
||||||
<template #extra>
|
|
||||||
<a-tree
|
|
||||||
ref="menuTreeRef"
|
|
||||||
v-model:checked-keys="form.menuIds"
|
|
||||||
class="w-full menu-tree"
|
|
||||||
:data="menuList"
|
|
||||||
:default-expand-all="isMenuExpanded"
|
|
||||||
:check-strictly="!form.menuCheckStrictly"
|
|
||||||
:virtual-list-props="{ height: 400 }"
|
|
||||||
checkable
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</a-form-item>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset v-show="current === 3">
|
|
||||||
<a-form-item hide-label field="dataScope">
|
|
||||||
<a-select
|
|
||||||
v-model.trim="form.dataScope"
|
|
||||||
:options="data_scope_enum"
|
|
||||||
placeholder="请选择数据权限"
|
|
||||||
:disabled="form.isSystem"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="form.dataScope === 5" hide-label :disabled="form.isSystem">
|
|
||||||
<a-space>
|
|
||||||
<a-checkbox v-model="isDeptExpanded" @change="onExpanded('dept')">展开/折叠</a-checkbox>
|
|
||||||
<a-checkbox v-model="isDeptCheckAll" @change="onCheckAll('dept')">全选/全不选</a-checkbox>
|
|
||||||
<a-checkbox v-model="form.deptCheckStrictly">父子联动</a-checkbox>
|
|
||||||
</a-space>
|
|
||||||
<template #extra>
|
|
||||||
<a-tree
|
|
||||||
ref="deptTreeRef"
|
|
||||||
v-model:checked-keys="form.deptIds"
|
|
||||||
class="w-full"
|
|
||||||
:data="deptList"
|
|
||||||
:default-expand-all="isDeptExpanded"
|
|
||||||
:check-strictly="!form.deptCheckStrictly"
|
|
||||||
:virtual-list-props="{ height: 350 }"
|
|
||||||
checkable
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</a-form-item>
|
|
||||||
</fieldset>
|
|
||||||
</a-form>
|
|
||||||
<template #footer>
|
|
||||||
<a-space size="large">
|
|
||||||
<a-button :disabled="current === 1" type="secondary" @click="onPrev">
|
|
||||||
<IconLeft />
|
|
||||||
上一步
|
|
||||||
</a-button>
|
|
||||||
<a-button v-if="current !== 3" :disabled="current === 3" type="primary" @click="onNext">
|
|
||||||
下一步
|
|
||||||
<IconRight />
|
|
||||||
</a-button>
|
|
||||||
<a-button v-if="current === 3" type="primary" @click="onClickOk">确定</a-button>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { type FormInstance, Message, type TreeNodeData } from '@arco-design/web-vue'
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
|
||||||
import { addRole } from '@/apis/system/role'
|
|
||||||
import { useResetReactive } from '@/hooks'
|
|
||||||
import { useDept, useDict, useMenu } from '@/hooks/app'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'save-success'): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
|
||||||
|
|
||||||
const dataId = ref('')
|
|
||||||
const visible = ref(false)
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const { data_scope_enum } = useDict('data_scope_enum')
|
|
||||||
const { deptList, getDeptList } = useDept()
|
|
||||||
const { menuList, getMenuList } = useMenu()
|
|
||||||
|
|
||||||
const rules: FormInstance['rules'] = {
|
|
||||||
name: [{ required: true, message: '请输入名称' }],
|
|
||||||
code: [{ required: true, message: '请输入编码' }],
|
|
||||||
dataScope: [{ required: true, message: '请选择数据权限' }],
|
|
||||||
}
|
|
||||||
|
|
||||||
const [form, resetForm] = useResetReactive({
|
|
||||||
menuCheckStrictly: true,
|
|
||||||
deptCheckStrictly: true,
|
|
||||||
sort: 999,
|
|
||||||
dataScope: 4,
|
|
||||||
})
|
|
||||||
|
|
||||||
const menuTreeRef = ref()
|
|
||||||
const deptTreeRef = ref()
|
|
||||||
const isMenuExpanded = ref(false)
|
|
||||||
const isDeptExpanded = ref(true)
|
|
||||||
const isMenuCheckAll = ref(false)
|
|
||||||
const isDeptCheckAll = ref(false)
|
|
||||||
const current = ref<number>(1)
|
|
||||||
// 重置
|
|
||||||
const reset = () => {
|
|
||||||
isMenuExpanded.value = false
|
|
||||||
isMenuCheckAll.value = false
|
|
||||||
isDeptExpanded.value = true
|
|
||||||
isDeptCheckAll.value = false
|
|
||||||
menuTreeRef.value?.expandAll(isMenuExpanded.value)
|
|
||||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
|
||||||
current.value = 1
|
|
||||||
formRef.value?.resetFields()
|
|
||||||
resetForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上一步
|
|
||||||
const onPrev = () => {
|
|
||||||
current.value = Math.max(1, current.value - 1)
|
|
||||||
}
|
|
||||||
// 下一步
|
|
||||||
const onNext = async () => {
|
|
||||||
try {
|
|
||||||
if (current.value === 1) {
|
|
||||||
const isInvalid = await formRef.value?.validateField(['name', 'code', 'sort', 'description'])
|
|
||||||
if (isInvalid) return
|
|
||||||
}
|
|
||||||
current.value = Math.min(3, current.value + 1)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 当前页
|
|
||||||
const onChangeCurrent = (page: number) => {
|
|
||||||
current.value = page
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有选中的菜单
|
|
||||||
const getMenuAllCheckedKeys = () => {
|
|
||||||
// 获取目前被选中的菜单
|
|
||||||
const checkedNodes = menuTreeRef.value?.getCheckedNodes()
|
|
||||||
const checkedKeys = checkedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
// 获取半选中的菜单
|
|
||||||
const halfCheckedNodes = menuTreeRef.value?.getHalfCheckedNodes()
|
|
||||||
const halfCheckedKeys = halfCheckedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
checkedKeys.unshift(...halfCheckedKeys)
|
|
||||||
return checkedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有选中的部门
|
|
||||||
const getDeptAllCheckedKeys = () => {
|
|
||||||
if (!deptTreeRef.value) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
// 获取目前被选中的部门
|
|
||||||
const checkedNodes = deptTreeRef.value?.getCheckedNodes()
|
|
||||||
const checkedKeys = checkedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
// 获取半选中的部门
|
|
||||||
const halfCheckedNodes = deptTreeRef.value?.getHalfCheckedNodes()
|
|
||||||
const halfCheckedKeys = halfCheckedNodes.map((item: TreeNodeData) => item.key)
|
|
||||||
checkedKeys.unshift(...halfCheckedKeys)
|
|
||||||
return checkedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
// 操作树
|
|
||||||
const handleTreeAction = (type, action) => {
|
|
||||||
const refMap = {
|
|
||||||
menu: menuTreeRef,
|
|
||||||
dept: deptTreeRef,
|
|
||||||
}
|
|
||||||
const ref = refMap[type]
|
|
||||||
if (ref && action === 'expand') {
|
|
||||||
ref.value?.expandAll(type === 'menu' ? isMenuExpanded.value : isDeptExpanded.value)
|
|
||||||
} else if (ref && action === 'check') {
|
|
||||||
ref.value?.checkAll(type === 'menu' ? isMenuCheckAll.value : isDeptCheckAll.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用时
|
|
||||||
const onExpanded = (type) => handleTreeAction(type, 'expand')
|
|
||||||
const onCheckAll = (type) => handleTreeAction(type, 'check')
|
|
||||||
|
|
||||||
// 保存
|
|
||||||
const save = async () => {
|
|
||||||
try {
|
|
||||||
const isInvalid = await formRef.value?.validate()
|
|
||||||
if (isInvalid) return false
|
|
||||||
form.menuIds = getMenuAllCheckedKeys()
|
|
||||||
form.deptIds = getDeptAllCheckedKeys()
|
|
||||||
await addRole(form)
|
|
||||||
Message.success('新增成功')
|
|
||||||
emit('save-success')
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认
|
|
||||||
const onClickOk = () => {
|
|
||||||
if (unref(current) === 3) {
|
|
||||||
save()
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开
|
|
||||||
const onOpen = async () => {
|
|
||||||
reset()
|
|
||||||
if (!menuList.value.length) {
|
|
||||||
await getMenuList()
|
|
||||||
}
|
|
||||||
if (!deptList.value.length) {
|
|
||||||
await getDeptList()
|
|
||||||
}
|
|
||||||
dataId.value = ''
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ onOpen })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
fieldset {
|
|
||||||
padding: 15px 15px 0 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid var(--color-neutral-3);
|
|
||||||
border-radius: 3px;
|
|
||||||
height: 440px;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset legend {
|
|
||||||
color: rgb(var(--gray-10));
|
|
||||||
padding: 2px 5px 2px 5px;
|
|
||||||
border: 1px solid var(--color-neutral-3);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-15 {
|
|
||||||
margin-bottom: 15px
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-form-item-extra) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-modal-footer){
|
|
||||||
margin-top: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-tree{
|
|
||||||
:deep(.arco-tree-node-is-leaf) {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
:deep(.arco-tree-node-indent-block){
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -9,20 +9,25 @@
|
|||||||
@before-ok="save"
|
@before-ok="save"
|
||||||
@close="reset"
|
@close="reset"
|
||||||
>
|
>
|
||||||
<UserSelect v-if="visible" ref="UserSelectRef" v-model:value="selectedUsers" @select-user="onSelectUser" />
|
<UserSelect v-if="visible" ref="UserSelectRef" v-model:value="selectedUsers" :exclude-value="excludeUsers" @select-user="onSelectUser" />
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Message } from '@arco-design/web-vue'
|
import { Message } from '@arco-design/web-vue'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { assignToUsers, listRoleUsers } from '@/apis/system/role'
|
import { assignToUsers, listRoleUserId } from '@/apis/system/role'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
const dataId = ref('')
|
const dataId = ref('')
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const selectedUsers = ref<string[]>([])
|
const selectedUsers = ref<string[]>([])
|
||||||
|
const excludeUsers = ref<string[]>([])
|
||||||
|
|
||||||
// 用户选择回调
|
// 用户选择回调
|
||||||
const onSelectUser = (value: string[]) => {
|
const onSelectUser = (value: string[]) => {
|
||||||
@@ -48,6 +53,7 @@ const save = async () => {
|
|||||||
await assignToUsers(dataId.value, selectedUsers.value)
|
await assignToUsers(dataId.value, selectedUsers.value)
|
||||||
Message.success('分配成功')
|
Message.success('分配成功')
|
||||||
reset()
|
reset()
|
||||||
|
emit('save-success')
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false
|
return false
|
||||||
@@ -58,8 +64,9 @@ const save = async () => {
|
|||||||
const onOpen = async (id: string) => {
|
const onOpen = async (id: string) => {
|
||||||
dataId.value = id
|
dataId.value = id
|
||||||
// 初始化选择的用户
|
// 初始化选择的用户
|
||||||
const { data } = await listRoleUsers(id)
|
const { data } = await listRoleUserId(id)
|
||||||
selectedUsers.value = data
|
excludeUsers.value = data
|
||||||
|
selectedUsers.value = []
|
||||||
visible.value = true
|
visible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-drawer v-model:visible="visible" title="角色详情" :width="width >= 600 ? 600 : '100%'" :footer="false">
|
|
||||||
<a-descriptions title="基础信息" :column="2" size="large" class="general-description">
|
|
||||||
<a-descriptions-item label="ID">{{ dataDetail?.id }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="数据权限">
|
|
||||||
<GiCellTag :value="dataDetail?.dataScope" :dict="data_scope_enum" />
|
|
||||||
</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="名称">{{ dataDetail?.name }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="编码">{{ dataDetail?.code }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="创建人">{{ dataDetail?.createUserString }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="创建时间">{{ dataDetail?.createTime }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="修改人">{{ dataDetail?.updateUserString }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="修改时间">{{ dataDetail?.updateTime }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="描述" :span="2">{{ dataDetail?.description }}</a-descriptions-item>
|
|
||||||
</a-descriptions>
|
|
||||||
<a-descriptions
|
|
||||||
title="功能权限"
|
|
||||||
:column="2"
|
|
||||||
size="large"
|
|
||||||
class="permission general-description"
|
|
||||||
style="margin-top: 20px; position: relative"
|
|
||||||
>
|
|
||||||
<a-descriptions-item :span="2">
|
|
||||||
<a-tree
|
|
||||||
:checked-keys="dataDetail?.menuIds"
|
|
||||||
:data="menuList"
|
|
||||||
default-expand-all
|
|
||||||
check-strictly
|
|
||||||
checkable
|
|
||||||
/>
|
|
||||||
</a-descriptions-item>
|
|
||||||
</a-descriptions>
|
|
||||||
<a-descriptions
|
|
||||||
v-if="dataDetail?.dataScope === 5"
|
|
||||||
title="数据权限"
|
|
||||||
:column="2"
|
|
||||||
size="large"
|
|
||||||
class="general-description"
|
|
||||||
style="margin-top: 20px; position: relative"
|
|
||||||
>
|
|
||||||
<a-descriptions-item :span="2">
|
|
||||||
<a-tree
|
|
||||||
:checked-keys="dataDetail?.deptIds"
|
|
||||||
:data="deptList"
|
|
||||||
default-expand-all
|
|
||||||
check-strictly
|
|
||||||
checkable
|
|
||||||
/>
|
|
||||||
</a-descriptions-item>
|
|
||||||
</a-descriptions>
|
|
||||||
</a-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
|
||||||
import { type RoleDetailResp, getRole as getDetail } from '@/apis/system/role'
|
|
||||||
import { useDept, useDict, useMenu } from '@/hooks/app'
|
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
|
||||||
|
|
||||||
const dataId = ref('')
|
|
||||||
const dataDetail = ref<RoleDetailResp>()
|
|
||||||
const visible = ref(false)
|
|
||||||
const { data_scope_enum } = useDict('data_scope_enum')
|
|
||||||
const { deptList, getDeptList } = useDept()
|
|
||||||
const { menuList, getMenuList } = useMenu()
|
|
||||||
|
|
||||||
// 查询详情
|
|
||||||
const getDataDetail = async () => {
|
|
||||||
const { data } = await getDetail(dataId.value)
|
|
||||||
dataDetail.value = data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开
|
|
||||||
const onOpen = async (id: string) => {
|
|
||||||
dataId.value = id
|
|
||||||
if (!menuList.value.length) {
|
|
||||||
await getMenuList()
|
|
||||||
}
|
|
||||||
if (!deptList.value.length) {
|
|
||||||
await getDeptList()
|
|
||||||
}
|
|
||||||
await getDataDetail()
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ onOpen })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.permission :deep(.arco-descriptions-item-label-block) {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
338
src/views/system/role/components/Pane1.vue
Normal file
338
src/views/system/role/components/Pane1.vue
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
<template>
|
||||||
|
<GiTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="id"
|
||||||
|
:data="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||||
|
:pagination="false"
|
||||||
|
:disabled-tools="['fullscreen', 'size', 'setting']"
|
||||||
|
:row-selection="{ type: 'checkbox', showCheckedAll, selectRowKeys: selectedKeys }"
|
||||||
|
@select="select"
|
||||||
|
@select-all="selectAll"
|
||||||
|
@refresh="refresh"
|
||||||
|
>
|
||||||
|
<template #toolbar-left>
|
||||||
|
<a-button v-permission="['system:role:updatePermission']" type="primary" :disabled="disabled" @click="save">
|
||||||
|
<template #icon><icon-save /></template>保存权限
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-right>
|
||||||
|
<a-tooltip :content="isCascade ? '取消父子联动' : '父子联动'">
|
||||||
|
<a-button @click="isCascade = !isCascade">
|
||||||
|
<template #icon>
|
||||||
|
<icon-check v-if="!isCascade" />
|
||||||
|
<icon-close v-else />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip :content="isExpanded ? '折叠' : '展开'">
|
||||||
|
<a-button @click="onExpanded">
|
||||||
|
<template #icon>
|
||||||
|
<icon-mind-mapping v-if="!isExpanded" />
|
||||||
|
<icon-list v-else />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template #expand-icon="{ expanded }">
|
||||||
|
<IconDown v-if="expanded" />
|
||||||
|
<IconRight v-else />
|
||||||
|
</template>
|
||||||
|
<template #title="{ record }">
|
||||||
|
<GiSvgIcon :name="record.icon" :size="15" />
|
||||||
|
<span style="margin-left: 5px; vertical-align: middle">{{ record.title }}</span>
|
||||||
|
</template>
|
||||||
|
<template #permissions="{ record }">
|
||||||
|
<div v-if="record.permissions && record.permissions.length > 0">
|
||||||
|
<a-checkbox-group v-model="record.checkedPermissions" :disabled="disabled" @change="selectPermission(record)">
|
||||||
|
<a-checkbox v-for="permission in record.permissions" :key="permission.id" :value="permission.id">
|
||||||
|
{{ permission.title }}
|
||||||
|
</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</GiTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, ref } from 'vue'
|
||||||
|
import { Message, type TableInstance } from '@arco-design/web-vue'
|
||||||
|
import { type MenuResp, listMenu } from '@/apis/system/menu'
|
||||||
|
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||||
|
import { isMobile } from '@/utils'
|
||||||
|
import type GiTable from '@/components/GiTable/index.vue'
|
||||||
|
import { useTable } from '@/hooks'
|
||||||
|
import { getRole, updateRolePermission } from '@/apis'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
roleId: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
roleId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableRef = ref<InstanceType<typeof GiTable>>()
|
||||||
|
// 是否父子联动
|
||||||
|
const isCascade = ref(true)
|
||||||
|
const isExpanded = ref(true)
|
||||||
|
// 是否禁用
|
||||||
|
const disabled = ref(false)
|
||||||
|
|
||||||
|
// 展开/折叠
|
||||||
|
const onExpanded = () => {
|
||||||
|
isExpanded.value = !isExpanded.value
|
||||||
|
tableRef.value?.tableRef?.expandAll(isExpanded.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归处理菜单数据,并进行类型转换
|
||||||
|
*
|
||||||
|
* @param menus 菜单数据
|
||||||
|
*/
|
||||||
|
const transformMenu = (menus: MenuResp[]) => {
|
||||||
|
return menus.map((item) => {
|
||||||
|
// 如果当前项有子项,递归处理子项
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
// 过滤出 type 为 3 的按钮权限
|
||||||
|
const permissions = item.children.filter((child) => child.type === 3 || child.permission).map((child) => ({
|
||||||
|
id: child.id,
|
||||||
|
title: child.title,
|
||||||
|
parentId: child.parentId,
|
||||||
|
permission: child.permission,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 过滤出 type 不为 3 的子项
|
||||||
|
item.children = item.children.filter((child) => child.type !== 3 && !child.permission)
|
||||||
|
|
||||||
|
// 如果有权限,将其添加到当前项的 permissions 属性中
|
||||||
|
if (permissions.length > 0) {
|
||||||
|
item.permissions = permissions
|
||||||
|
item.checkedPermissions = permissions.filter((permission) => permission.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理剩余的子项
|
||||||
|
item.children = transformMenu(item.children)
|
||||||
|
|
||||||
|
// 如果 children 为空数组,移除 children 属性
|
||||||
|
if (item.children.length === 0) {
|
||||||
|
delete item.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新表格数据的选中状态
|
||||||
|
const updateTableDataCheckedStatus = (data: MenuResp[], selectedKeys: (string | number)[]) => {
|
||||||
|
data.forEach((item) => {
|
||||||
|
item.disabled = disabled.value
|
||||||
|
// 设置菜单项的选中状态
|
||||||
|
item.isChecked = selectedKeys.includes(item.id)
|
||||||
|
// 设置权限的选中状态
|
||||||
|
if (item.permissions) {
|
||||||
|
item.checkedPermissions = item.permissions
|
||||||
|
.filter((permission) => selectedKeys.includes(permission.id))
|
||||||
|
.map((permission) => permission.id)
|
||||||
|
}
|
||||||
|
// 递归处理子菜单
|
||||||
|
if (item.children) {
|
||||||
|
updateTableDataCheckedStatus(item.children, selectedKeys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedKeys = ref<Set<string | number>>(new Set())
|
||||||
|
|
||||||
|
const {
|
||||||
|
tableData,
|
||||||
|
loading,
|
||||||
|
search,
|
||||||
|
} = useTable(() => listMenu(), {
|
||||||
|
immediate: true,
|
||||||
|
formatResult(data) {
|
||||||
|
return transformMenu(data)
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
nextTick(() => {
|
||||||
|
tableRef.value?.tableRef?.expandAll(true)
|
||||||
|
})
|
||||||
|
// 初始加载时应用已选中的权限
|
||||||
|
if (selectedKeys.value.size > 0) {
|
||||||
|
updateTableDataCheckedStatus(tableData.value, Array.from(selectedKeys.value))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns: TableInstanceColumns[] = [
|
||||||
|
{ title: '菜单', dataIndex: 'title', slotName: 'title', width: 170, fixed: !isMobile() ? 'left' : undefined },
|
||||||
|
{ title: '权限', dataIndex: 'permissions', slotName: 'permissions' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 级联选中子项
|
||||||
|
const cascadeSelectChild = (record: MenuResp, isCascade: boolean) => {
|
||||||
|
if (isCascade && record.children && record.children.length > 0) {
|
||||||
|
record.children.forEach((child) => {
|
||||||
|
child.isChecked = record.isChecked
|
||||||
|
tableRef.value?.tableRef?.select(child.id, child.isChecked)
|
||||||
|
child.isChecked
|
||||||
|
? selectedKeys.value.add(child.id)
|
||||||
|
: selectedKeys.value.delete(child.id)
|
||||||
|
if ((child.children && child.children.length > 0) || (child.permissions && child.permissions.length > 0)) {
|
||||||
|
cascadeSelectChild(child, isCascade)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 递归选中权限
|
||||||
|
if (isCascade && record.permissions && record.permissions.length > 0) {
|
||||||
|
record.permissions.forEach((permission) => {
|
||||||
|
permission.isChecked = record.isChecked
|
||||||
|
permission.isChecked
|
||||||
|
? selectedKeys.value.add(permission.id)
|
||||||
|
: selectedKeys.value.delete(permission.id)
|
||||||
|
})
|
||||||
|
record.checkedPermissions = record.permissions.filter((permission) => permission.isChecked).map((permission) => permission.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找指定菜单
|
||||||
|
const findItem = (id: string, data: MenuResp[]) => {
|
||||||
|
for (const item of data) {
|
||||||
|
if (item.id === id) return item
|
||||||
|
if (item.children?.length) {
|
||||||
|
const found = findItem(id, item.children)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 级联选中父项目
|
||||||
|
const cascadeSelectParent = (record: MenuResp, isCascade: boolean) => {
|
||||||
|
if (isCascade && record.parentId && record.parentId !== '0') {
|
||||||
|
const parent = findItem(record.parentId, tableData.value)
|
||||||
|
if (parent) {
|
||||||
|
// 如果父项目的某个子项被选中了,它就依然保持选中状态
|
||||||
|
parent.isChecked = parent.children?.some((child) => child.isChecked)
|
||||||
|
tableRef.value?.tableRef?.select(parent.id, parent.isChecked)
|
||||||
|
if (!parent.isChecked && !record.isChecked) {
|
||||||
|
selectedKeys.value.delete(parent.id)
|
||||||
|
} else {
|
||||||
|
selectedKeys.value.add(parent.id)
|
||||||
|
}
|
||||||
|
if (parent.parentId && parent.parentId !== 0) {
|
||||||
|
cascadeSelectParent(parent, isCascade)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
const select: TableInstance['onSelect'] = (rowKeys, checked, record) => {
|
||||||
|
const isChecked = rowKeys.includes(checked)
|
||||||
|
isChecked
|
||||||
|
? selectedKeys.value.add(record.id)
|
||||||
|
: selectedKeys.value.delete(record.id)
|
||||||
|
record.isChecked = isChecked
|
||||||
|
// 级联选中子项
|
||||||
|
cascadeSelectChild(record, isCascade.value)
|
||||||
|
// 级联选中父项
|
||||||
|
cascadeSelectParent(record, isCascade.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选
|
||||||
|
const selectAll: TableInstance['onSelectAll'] = (checked) => {
|
||||||
|
tableData.value.forEach((item) => {
|
||||||
|
item.isChecked = checked
|
||||||
|
cascadeSelectChild(item, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中权限
|
||||||
|
const selectPermission = (record) => {
|
||||||
|
const checkPermissions = record.checkedPermissions
|
||||||
|
// 取消选中
|
||||||
|
if (checkPermissions.length === 0) {
|
||||||
|
if (isCascade.value) {
|
||||||
|
record.isChecked = false
|
||||||
|
selectedKeys.value.delete(record.id)
|
||||||
|
tableRef.value?.tableRef?.select(record.id, record.isChecked)
|
||||||
|
cascadeSelectParent(record, isCascade.value)
|
||||||
|
}
|
||||||
|
record.permissions.forEach((permission) => {
|
||||||
|
permission.isChecked = false
|
||||||
|
selectedKeys.value.delete(permission.id)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 选中
|
||||||
|
if (checkPermissions.length > 0) {
|
||||||
|
if (isCascade.value) {
|
||||||
|
record.isChecked = true
|
||||||
|
selectedKeys.value.add(record.id)
|
||||||
|
tableRef.value?.tableRef?.select(record.id, record.isChecked)
|
||||||
|
cascadeSelectParent(record, isCascade.value)
|
||||||
|
}
|
||||||
|
record.permissions.forEach((permission) => {
|
||||||
|
permission.isChecked = checkPermissions.includes(permission.id)
|
||||||
|
permission.isChecked
|
||||||
|
? selectedKeys.value.add(permission.id)
|
||||||
|
: selectedKeys.value.delete(permission.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const save = async () => {
|
||||||
|
await updateRolePermission(props.roleId, {
|
||||||
|
menuIds: Array.from(selectedKeys.value),
|
||||||
|
menuCheckStrictly: isCascade.value,
|
||||||
|
})
|
||||||
|
Message.success('保存成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCheckedAll = ref(true)
|
||||||
|
// 加载角色详情
|
||||||
|
const fetchRole = async (id: string) => {
|
||||||
|
disabled.value = !has.hasPermOr(['system:role:updatePermission'])
|
||||||
|
// 查询角色详情
|
||||||
|
const { data } = await getRole(id)
|
||||||
|
if (!disabled.value) {
|
||||||
|
disabled.value = data.isSystem
|
||||||
|
}
|
||||||
|
isCascade.value = data.menuCheckStrictly
|
||||||
|
// 更新选中键集合
|
||||||
|
selectedKeys.value = new Set(data.menuIds)
|
||||||
|
// 更新表格数据的选中状态
|
||||||
|
updateTableDataCheckedStatus(tableData.value, data.menuIds)
|
||||||
|
// 手动设置表格行的选中状态,确保组件响应
|
||||||
|
await nextTick(() => {
|
||||||
|
tableRef.value?.tableRef?.selectAll(false)
|
||||||
|
tableRef.value?.tableRef?.select(data.menuIds, true)
|
||||||
|
showCheckedAll.value = !disabled.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新
|
||||||
|
const refresh = () => {
|
||||||
|
search()
|
||||||
|
fetchRole(props.roleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 roleId 的变化
|
||||||
|
watch(
|
||||||
|
() => props.roleId,
|
||||||
|
async (newRoleId) => {
|
||||||
|
if (newRoleId) {
|
||||||
|
await fetchRole(newRoleId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
179
src/views/system/role/components/Pane2.vue
Normal file
179
src/views/system/role/components/Pane2.vue
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<GiTable
|
||||||
|
row-key="id"
|
||||||
|
:data="dataList"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="loading"
|
||||||
|
:scroll="{ x: '100%', y: '100%', minWidth: 600 }"
|
||||||
|
:pagination="pagination"
|
||||||
|
:disabled-tools="['size', 'setting', 'fullscreen']"
|
||||||
|
:disabled-column-keys="['nickname']"
|
||||||
|
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
@select="select"
|
||||||
|
@select-all="selectAll"
|
||||||
|
@refresh="reset"
|
||||||
|
>
|
||||||
|
<template #toolbar-left>
|
||||||
|
<a-button v-permission="['system:role:assign']" type="primary" @click="onAssign">
|
||||||
|
<template #icon><icon-plus /></template>
|
||||||
|
<template #default>分配角色</template>
|
||||||
|
</a-button>
|
||||||
|
<a-button v-permission="['system:role:unassign']" type="primary" status="danger" :disabled="!selectedKeys.length" :title="!selectedKeys.length ? '请选择' : ''" @click="onMulDelete">
|
||||||
|
<template #icon><icon-delete /></template>
|
||||||
|
<template #default>取消分配</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-right>
|
||||||
|
<a-input-search v-model="queryForm.description" placeholder="搜索用户名/昵称/描述" allow-clear @search="search" />
|
||||||
|
<a-button @click="reset">
|
||||||
|
<template #icon>
|
||||||
|
<icon-refresh />
|
||||||
|
</template>
|
||||||
|
<template #default>重置</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template #gender="{ record }">
|
||||||
|
<GiCellGender :gender="record.gender" />
|
||||||
|
</template>
|
||||||
|
<template #roleNames="{ record }">
|
||||||
|
<GiCellTags :data="record.roleNames" />
|
||||||
|
</template>
|
||||||
|
<template #status="{ record }">
|
||||||
|
<GiCellStatus :status="record.status" />
|
||||||
|
</template>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-link
|
||||||
|
v-permission="['system:role:unassign']"
|
||||||
|
status="danger"
|
||||||
|
:disabled="record.disabled"
|
||||||
|
:title="record.disabled ? '该用户为系统内置用户不能取消分配' : '取消分配'"
|
||||||
|
@click="onDelete(record)"
|
||||||
|
>
|
||||||
|
取消分配
|
||||||
|
</a-link>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</GiTable>
|
||||||
|
|
||||||
|
<RoleAssignModal ref="RoleAssignModalRef" @save-success="search" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang='tsx' setup>
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
|
import RoleAssignModal from '../RoleAssignModal.vue'
|
||||||
|
import { useResetReactive, useTable } from '@/hooks'
|
||||||
|
import { type RoleUserQuery, type RoleUserResp, listRoleUser, unassignFromUsers } from '@/apis/system/role'
|
||||||
|
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||||
|
import { isMobile } from '@/utils'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
roleId: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
roleId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const [queryForm, resetForm] = useResetReactive<RoleUserQuery>({
|
||||||
|
sort: ['t1.id,desc'],
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
tableData: dataList,
|
||||||
|
loading,
|
||||||
|
pagination,
|
||||||
|
search,
|
||||||
|
selectedKeys,
|
||||||
|
select,
|
||||||
|
selectAll,
|
||||||
|
handleDelete,
|
||||||
|
} = useTable((page) => listRoleUser(props.roleId, { ...queryForm, ...page }), { immediate: false })
|
||||||
|
const columns: TableInstanceColumns[] = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
width: 66,
|
||||||
|
align: 'center',
|
||||||
|
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
||||||
|
fixed: !isMobile() ? 'left' : undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '昵称',
|
||||||
|
dataIndex: 'nickname',
|
||||||
|
slotName: 'nickname',
|
||||||
|
minWidth: 130,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
fixed: !isMobile() ? 'left' : undefined,
|
||||||
|
},
|
||||||
|
{ title: '用户名', dataIndex: 'username', slotName: 'username', minWidth: 120, ellipsis: true, tooltip: true },
|
||||||
|
{ title: '状态', dataIndex: 'status', slotName: 'status', align: 'center' },
|
||||||
|
{ title: '性别', dataIndex: 'gender', slotName: 'gender', align: 'center' },
|
||||||
|
{ title: '所属部门', dataIndex: 'deptName', minWidth: 140, ellipsis: true, tooltip: true },
|
||||||
|
{ title: '角色', dataIndex: 'roleNames', slotName: 'roleNames', minWidth: 165 },
|
||||||
|
{ title: '描述', dataIndex: 'description', minWidth: 130, ellipsis: true, tooltip: true },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
slotName: 'action',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
fixed: !isMobile() ? 'right' : undefined,
|
||||||
|
show: has.hasPermOr([
|
||||||
|
'system:role:unassign',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const reset = () => {
|
||||||
|
resetForm()
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const onMulDelete = () => {
|
||||||
|
if (!selectedKeys.value.length) {
|
||||||
|
return Message.warning('请选择数据')
|
||||||
|
}
|
||||||
|
Modal.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `是否确定取消分配角色给所选的${selectedKeys.value.length}个用户?`,
|
||||||
|
hideCancel: false,
|
||||||
|
onOk: async () => {
|
||||||
|
await unassignFromUsers(selectedKeys.value)
|
||||||
|
Message.success('取消成功')
|
||||||
|
search()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const onDelete = (record: RoleUserResp) => {
|
||||||
|
return handleDelete(() => unassignFromUsers([record.id]), {
|
||||||
|
content: `是否确定取消分配角色给用户「${record.nickname}(${record.username})」?`,
|
||||||
|
successTip: '取消成功',
|
||||||
|
showModal: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoleAssignModalRef = ref<InstanceType<typeof RoleAssignModal>>()
|
||||||
|
// 分配
|
||||||
|
const onAssign = () => {
|
||||||
|
RoleAssignModalRef.value?.onOpen(props.roleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 roleId 的变化
|
||||||
|
watch(
|
||||||
|
() => props.roleId,
|
||||||
|
async (newRoleId) => {
|
||||||
|
if (newRoleId) {
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -1,160 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gi_table_page">
|
<div class="gi_page">
|
||||||
<GiTable
|
<SplitPanel>
|
||||||
title=""
|
<template #left>
|
||||||
row-key="id"
|
<RoleTree @node-click="handleSelectRole" />
|
||||||
:data="dataList"
|
|
||||||
:columns="columns"
|
|
||||||
:loading="loading"
|
|
||||||
:bordered="false"
|
|
||||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
|
||||||
:pagination="pagination"
|
|
||||||
:disabled-tools="['size']"
|
|
||||||
:disabled-column-keys="['name']"
|
|
||||||
@refresh="search"
|
|
||||||
>
|
|
||||||
<template #toolbar-left>
|
|
||||||
<a-input-search v-model="queryForm.description" placeholder="搜索名称/编码/描述" allow-clear @search="search" />
|
|
||||||
<a-button @click="reset">
|
|
||||||
<template #icon><icon-refresh /></template>
|
|
||||||
<template #default>重置</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #toolbar-right>
|
<template #main>
|
||||||
<a-button v-permission="['system:role:add']" type="primary" @click="onAdd">
|
<a-tabs v-model:activeKey="activeTab" class="gi_tabs" size="large">
|
||||||
<template #icon><icon-plus /></template>
|
<a-tab-pane key="1" title="功能权限">
|
||||||
<template #default>新增</template>
|
<component :is="Pane1" v-if="activeTab === '1'" :role-id="roleId" />
|
||||||
</a-button>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" title="角色用户">
|
||||||
|
<component :is="Pane2" v-if="activeTab === '2'" :role-id="roleId" />
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</template>
|
</template>
|
||||||
<template #dataScope="{ record }">
|
</SplitPanel>
|
||||||
<GiCellTag :value="record.dataScope" :dict="data_scope_enum" />
|
|
||||||
</template>
|
|
||||||
<template #isSystem="{ record }">
|
|
||||||
<a-tag v-if="record.isSystem" color="red" size="small">是</a-tag>
|
|
||||||
<a-tag v-else color="arcoblue" size="small">否</a-tag>
|
|
||||||
</template>
|
|
||||||
<template #action="{ record }">
|
|
||||||
<a-space>
|
|
||||||
<a-link v-permission="['system:role:detail']" title="详情" @click="onDetail(record)">详情</a-link>
|
|
||||||
<a-link v-permission="['system:role:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
|
||||||
<a-link v-permission="['system:role:assign']" title="分配" @click="onAssign(record)">分配</a-link>
|
|
||||||
<a-link
|
|
||||||
v-permission="['system:role:delete']"
|
|
||||||
status="danger"
|
|
||||||
:disabled="record.isSystem"
|
|
||||||
:title="record.isSystem ? '系统内置数据不能删除' : '删除'"
|
|
||||||
@click="onDelete(record)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</a-link>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
</GiTable>
|
|
||||||
|
|
||||||
<RoleAddModal ref="RoleAddModalRef" @save-success="search" />
|
|
||||||
<RoleUpdateDrawer ref="RoleUpdateDrawerRef" @save-success="search" />
|
|
||||||
<RoleDetailDrawer ref="RoleDetailDrawerRef" />
|
|
||||||
<RoleAssignModal ref="RoleAssignModalRef" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import RoleAddModal from './RoleAddModal.vue'
|
import RoleTree from './tree/index.vue'
|
||||||
import RoleUpdateDrawer from './RoleUpdateDrawer.vue'
|
import Pane1 from './components/Pane1.vue'
|
||||||
import RoleDetailDrawer from './RoleDetailDrawer.vue'
|
import Pane2 from './components/Pane2.vue'
|
||||||
import RoleAssignModal from './RoleAssignModal.vue'
|
|
||||||
import { type RoleQuery, type RoleResp, deleteRole, listRole } from '@/apis/system/role'
|
|
||||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
|
||||||
import { useTable } from '@/hooks'
|
|
||||||
import { useDict } from '@/hooks/app'
|
|
||||||
import { isMobile } from '@/utils'
|
|
||||||
import has from '@/utils/has'
|
|
||||||
|
|
||||||
defineOptions({ name: 'SystemRole' })
|
defineOptions({ name: 'SystemRole' })
|
||||||
|
|
||||||
const { data_scope_enum } = useDict('data_scope_enum')
|
const activeTab = ref('1')
|
||||||
|
|
||||||
const queryForm = reactive<RoleQuery>({
|
const roleId = ref('')
|
||||||
sort: ['id,desc'],
|
// 根据选中角色查询
|
||||||
})
|
const handleSelectRole = (keys: Array<any>) => {
|
||||||
|
roleId.value = keys.length === 1 ? keys[0] : undefined
|
||||||
const {
|
|
||||||
tableData: dataList,
|
|
||||||
loading,
|
|
||||||
pagination,
|
|
||||||
search,
|
|
||||||
handleDelete,
|
|
||||||
} = useTable((page) => listRole({ ...queryForm, ...page }), { immediate: true })
|
|
||||||
const columns: TableInstanceColumns[] = [
|
|
||||||
{
|
|
||||||
title: '序号',
|
|
||||||
width: 66,
|
|
||||||
align: 'center',
|
|
||||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
|
||||||
},
|
|
||||||
{ title: '名称', dataIndex: 'name', slotName: 'name', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '编码', dataIndex: 'code', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '数据权限', dataIndex: 'dataScope', slotName: 'dataScope', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '排序', dataIndex: 'sort', align: 'center', show: false },
|
|
||||||
{ title: '系统内置', dataIndex: 'isSystem', slotName: 'isSystem', align: 'center', show: false },
|
|
||||||
{ title: '描述', dataIndex: 'description', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '创建人', dataIndex: 'createUserString', ellipsis: true, tooltip: true, show: false },
|
|
||||||
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
|
|
||||||
{ title: '修改人', dataIndex: 'updateUserString', ellipsis: true, tooltip: true, show: false },
|
|
||||||
{ title: '修改时间', dataIndex: 'updateTime', width: 180, show: false },
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: 'action',
|
|
||||||
slotName: 'action',
|
|
||||||
width: 200,
|
|
||||||
align: 'center',
|
|
||||||
fixed: !isMobile() ? 'right' : undefined,
|
|
||||||
show: has.hasPermOr([
|
|
||||||
'system:role:detail',
|
|
||||||
'system:role:update',
|
|
||||||
'system:role:delete',
|
|
||||||
'system:role:assign',
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
const reset = () => {
|
|
||||||
queryForm.description = undefined
|
|
||||||
search()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const onDelete = (record: RoleResp) => {
|
|
||||||
return handleDelete(() => deleteRole(record.id), {
|
|
||||||
content: `是否确定删除角色「${record.name}(${record.code})」?`,
|
|
||||||
showModal: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoleAddModalRef = ref<InstanceType<typeof RoleAddModal>>()
|
|
||||||
// 新增
|
|
||||||
const onAdd = () => {
|
|
||||||
RoleAddModalRef.value?.onOpen()
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoleUpdateDrawerRef = ref<InstanceType<typeof RoleUpdateDrawer>>()
|
|
||||||
// 修改
|
|
||||||
const onUpdate = (record: RoleResp) => {
|
|
||||||
RoleUpdateDrawerRef.value?.onOpen(record.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoleDetailDrawerRef = ref<InstanceType<typeof RoleDetailDrawer>>()
|
|
||||||
// 详情
|
|
||||||
const onDetail = (record: RoleResp) => {
|
|
||||||
RoleDetailDrawerRef.value?.onOpen(record.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoleAssignModalRef = ref<InstanceType<typeof RoleAssignModal>>()
|
|
||||||
// 分配
|
|
||||||
const onAssign = (record: RoleResp) => {
|
|
||||||
RoleAssignModalRef.value?.onOpen(record.id)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
69
src/views/system/role/tree/RightMenu.vue
Normal file
69
src/views/system/role/tree/RightMenu.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<a-menu class="right-menu">
|
||||||
|
<a-menu-item v-permission="['system:role:update']" title="修改" @click="onClick('update')">
|
||||||
|
<span>修改</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
v-permission="['system:role:delete']"
|
||||||
|
class="danger"
|
||||||
|
:disabled="data.isSystem"
|
||||||
|
:title="data.isSystem ? '该角色为系统内置角色' : '删除'"
|
||||||
|
@click="onClick('delete')"
|
||||||
|
>
|
||||||
|
<span>删除</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { RoleResp } from '@/apis/system/role'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: RoleResp
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'on-menu-item-click', mode: string, data: RoleResp): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 点击菜单项
|
||||||
|
const onClick = (mode: string) => {
|
||||||
|
emit('on-menu-item-click', mode, props.data)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.arco-menu-inner) {
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
.arco-menu-item {
|
||||||
|
height: 34px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: rgb(var(--danger-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger.arco-menu-disabled {
|
||||||
|
color: var(--color-danger-light-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-menu {
|
||||||
|
width: 120px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--color-border-2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
235
src/views/system/role/tree/index.vue
Normal file
235
src/views/system/role/tree/index.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="search">
|
||||||
|
<a-input v-model="searchKey" placeholder="搜索名称/编码" allow-clear>
|
||||||
|
<template #prefix><icon-search /></template>
|
||||||
|
</a-input>
|
||||||
|
<a-button v-permission="['system:role:add']" type="primary" @click="onAdd">
|
||||||
|
<template #icon><icon-plus /></template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="tree-wrapper">
|
||||||
|
<div class="tree">
|
||||||
|
<a-tree
|
||||||
|
:data="(treeData as unknown as TreeNodeData[])"
|
||||||
|
:field-names="{ key: 'id' }"
|
||||||
|
block-node
|
||||||
|
:selected-keys="selectedKeys"
|
||||||
|
@select="select"
|
||||||
|
>
|
||||||
|
<template #title="node">
|
||||||
|
<a-typography-paragraph
|
||||||
|
:ellipsis="{
|
||||||
|
rows: 1,
|
||||||
|
showTooltip: true,
|
||||||
|
css: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ node.name }} ({{ node.code }})
|
||||||
|
</a-typography-paragraph>
|
||||||
|
</template>
|
||||||
|
<template #extra="node">
|
||||||
|
<a-trigger trigger="click" align-point animation-name="slide-dynamic-origin" auto-fit-transform-origin position="bl" scroll-to-close>
|
||||||
|
<icon-more-vertical v-if="has.hasPermOr(['system:role:update', 'system:role:delete'])" class="action" />
|
||||||
|
<template #content>
|
||||||
|
<RightMenu :data="node" @on-menu-item-click="onMenuItemClick" />
|
||||||
|
</template>
|
||||||
|
</a-trigger>
|
||||||
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RoleAddDrawer ref="RoleAddDrawerRef" @save-success="getTreeData" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
|
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||||
|
import { mapTree } from 'xe-utils'
|
||||||
|
import RoleAddDrawer from '../RoleAddDrawer.vue'
|
||||||
|
import RightMenu from './RightMenu.vue'
|
||||||
|
import { type RoleResp, deleteRole, listRole } from '@/apis/system/role'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'node-click', keys: Array<any>): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selectedKeys = ref()
|
||||||
|
// 选中节点
|
||||||
|
const select = (keys: Array<any>) => {
|
||||||
|
if (selectedKeys.value && selectedKeys.value[0] === keys[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedKeys.value = keys
|
||||||
|
emit('node-click', keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeItem extends RoleResp {
|
||||||
|
popupVisible: boolean
|
||||||
|
}
|
||||||
|
const dataList = ref<TreeItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
// 查询树列表
|
||||||
|
const getTreeData = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const { data } = await listRole({ sort: ['sort,asc'] })
|
||||||
|
dataList.value = mapTree(data, (i) => ({
|
||||||
|
...i,
|
||||||
|
popupVisible: false,
|
||||||
|
icon: () => {
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
await nextTick(() => {
|
||||||
|
select([dataList.value[0]?.id])
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤树
|
||||||
|
const searchKey = ref('')
|
||||||
|
const search = (keyword: string) => {
|
||||||
|
const loop = (data: TreeItem[]) => {
|
||||||
|
const result = [] as TreeItem[]
|
||||||
|
data.forEach((item: TreeItem) => {
|
||||||
|
if (item.name?.toLowerCase().includes(keyword) || item.code?.toLowerCase().includes(keyword)) {
|
||||||
|
result.push({ ...item })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return loop(dataList.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeData = computed(() => {
|
||||||
|
if (!searchKey.value) return dataList.value
|
||||||
|
return search(searchKey.value.toLowerCase())
|
||||||
|
})
|
||||||
|
|
||||||
|
const RoleAddDrawerRef = ref<InstanceType<typeof RoleAddDrawer>>()
|
||||||
|
// 新增
|
||||||
|
const onAdd = () => {
|
||||||
|
RoleAddDrawerRef.value?.onAdd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击菜单项
|
||||||
|
const onMenuItemClick = (mode: string, node: RoleResp) => {
|
||||||
|
if (mode === 'update') {
|
||||||
|
RoleAddDrawerRef.value?.onUpdate(node.id)
|
||||||
|
} else if (mode === 'delete') {
|
||||||
|
Modal.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `是否确定删除角色「${node.name}」?`,
|
||||||
|
hideCancel: false,
|
||||||
|
okButtonProps: { status: 'danger' },
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteRole(node.id)
|
||||||
|
if (res.success) {
|
||||||
|
Message.success('删除成功')
|
||||||
|
await getTreeData()
|
||||||
|
}
|
||||||
|
return res.success
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTreeData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.arco-tree-node) {
|
||||||
|
line-height: normal;
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
margin: 5px 0;
|
||||||
|
.action {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-secondary-hover);
|
||||||
|
.action {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-tree-node-switcher {
|
||||||
|
width: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-tree-node-title {
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-tree-node-title-text {
|
||||||
|
width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-selected) {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: rgba(var(--primary-6), 0.1);
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(var(--primary-6), 0.1);
|
||||||
|
}
|
||||||
|
.arco-typography {
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.arco-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
/* margin-bottom:10px;*/
|
||||||
|
.tree {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,8 +10,6 @@
|
|||||||
<DeptTree @node-click="handleSelectDept" />
|
<DeptTree @node-click="handleSelectDept" />
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<a-row align="stretch" :gutter="14" class="h-full page_content">
|
|
||||||
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" :xxl="24" class="h-full overflow-hidden">
|
|
||||||
<GiTable
|
<GiTable
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:data="dataList"
|
:data="dataList"
|
||||||
@@ -85,8 +83,6 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</GiTable>
|
</GiTable>
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</template>
|
</template>
|
||||||
</SplitPanel>
|
</SplitPanel>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user