mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-10 20:57:10 +08:00
refactor: 重构角色管理
This commit is contained in:
@@ -6,7 +6,7 @@ export type * from './type'
|
||||
const BASE_URL = '/system/menu'
|
||||
|
||||
/** @desc 查询菜单列表 */
|
||||
export function listMenu(query: T.MenuQuery) {
|
||||
export function listMenu(query?: T.MenuQuery) {
|
||||
return http.get<T.MenuResp[]>(`${BASE_URL}/tree`, query)
|
||||
}
|
||||
|
||||
|
@@ -6,8 +6,8 @@ export type * from './type'
|
||||
const BASE_URL = '/system/role'
|
||||
|
||||
/** @desc 查询角色列表 */
|
||||
export function listRole(query: T.RolePageQuery) {
|
||||
return http.get<PageRes<T.RoleResp[]>>(`${BASE_URL}`, query)
|
||||
export function listRole(query: T.RoleQuery) {
|
||||
return http.get<T.RoleResp[]>(`${BASE_URL}/list`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询角色详情 */
|
||||
@@ -30,12 +30,27 @@ export function deleteRole(ids: string | Array<string>) {
|
||||
return http.del(`${BASE_URL}/${ids}`)
|
||||
}
|
||||
|
||||
/** @desc 修改角色权限 */
|
||||
export function updateRolePermission(id: string, data: any) {
|
||||
return http.put(`${BASE_URL}/${id}/permission`, data)
|
||||
}
|
||||
|
||||
/** @desc 查询角色关联用户 */
|
||||
export function listRoleUsers(id: string) {
|
||||
return http.get(`${BASE_URL}/${id}/user`)
|
||||
export function listRoleUser(id: string, query: T.RoleUserPageQuery) {
|
||||
return http.get<PageRes<T.RoleUserResp[]>>(`${BASE_URL}/${id}/user`, query)
|
||||
}
|
||||
|
||||
/** @desc 分配角色给用户 */
|
||||
export function assignToUsers(id: string, userIds: Array<string>) {
|
||||
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
|
||||
sort: Array<string>
|
||||
userIds?: Array<string>
|
||||
excludeUserIds?: Array<string>
|
||||
}
|
||||
export interface UserPageQuery extends UserQuery, PageQuery {}
|
||||
|
||||
@@ -62,11 +63,29 @@ export type RoleDetailResp = RoleResp & {
|
||||
menuCheckStrictly: 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 {
|
||||
description?: 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 {
|
||||
|
@@ -219,7 +219,7 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.arco-table-border .arco-table-container) {
|
||||
border: none;
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -94,11 +94,13 @@ const emit = defineEmits<{
|
||||
interface Props {
|
||||
multiple?: boolean
|
||||
value: string | string[]
|
||||
excludeValue?: string[]
|
||||
}
|
||||
|
||||
// 查询表单
|
||||
const queryForm = reactive<UserQuery>({
|
||||
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{
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -517,4 +542,4 @@
|
||||
::view-transition-new(root),
|
||||
.dark::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
}
|
||||
|
@@ -132,7 +132,7 @@ const reset = () => {
|
||||
// 删除
|
||||
const onDelete = (record: DictItemResp) => {
|
||||
return handleDelete(() => deleteDictItem(record.id), {
|
||||
content: `是否确定删除字典「${record.label}」?`,
|
||||
content: `是否确定删除字典项「${record.label}」?`,
|
||||
showModal: true,
|
||||
})
|
||||
}
|
||||
|
@@ -125,7 +125,7 @@ const onMenuItemClick = (mode: string, node: DictResp) => {
|
||||
} else if (mode === 'delete') {
|
||||
Modal.warning({
|
||||
title: '提示',
|
||||
content: `是否确定删除 [${node.name}]?`,
|
||||
content: `是否确定删除字典「${node.name}」?`,
|
||||
hideCancel: false,
|
||||
okButtonProps: { status: 'danger' },
|
||||
onBeforeOk: async () => {
|
||||
|
@@ -183,7 +183,7 @@ const handleRightMenuClick = async (mode: string, fileInfo: FileItem) => {
|
||||
if (mode === 'delete') {
|
||||
Modal.warning({
|
||||
title: '提示',
|
||||
content: `是否确定删除文件 [${fileInfo.name}]?`,
|
||||
content: `是否确定删除文件「${fileInfo.name}」?`,
|
||||
hideCancel: false,
|
||||
okButtonProps: { status: 'danger' },
|
||||
onOk: async () => {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="修改角色"
|
||||
:title="title"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 600 ? 600 : '100%'"
|
||||
@@ -15,7 +15,7 @@
|
||||
<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="请输入编码" :disabled="true" />
|
||||
<a-input v-model.trim="form.code" placeholder="请输入编码" :disabled="isUpdate" />
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" field="sort">
|
||||
<a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" />
|
||||
@@ -30,26 +30,6 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</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>
|
||||
<legend>数据权限</legend>
|
||||
<a-form-item hide-label field="dataScope">
|
||||
@@ -84,9 +64,11 @@
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message, type TreeNodeData } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { getRole, updateRole } from '@/apis/system/role'
|
||||
import type { GiForm } from '@/components/GiForm'
|
||||
|
||||
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<{
|
||||
(e: 'save-success'): void
|
||||
@@ -96,10 +78,11 @@ const { width } = useWindowSize()
|
||||
|
||||
const dataId = ref('')
|
||||
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 { deptList, getDeptList } = useDept()
|
||||
const { menuList, getMenuList } = useMenu()
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
name: [{ required: true, message: '请输入名称' }],
|
||||
@@ -108,44 +91,24 @@ const rules: FormInstance['rules'] = {
|
||||
}
|
||||
|
||||
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 reset = () => {
|
||||
isMenuExpanded.value = false
|
||||
isMenuCheckAll.value = false
|
||||
isDeptExpanded.value = true
|
||||
isDeptCheckAll.value = false
|
||||
menuTreeRef.value?.expandAll(isMenuExpanded.value)
|
||||
menuTreeRef.value?.checkAll(false)
|
||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
||||
deptTreeRef.value?.checkAll(false)
|
||||
formRef.value?.resetFields()
|
||||
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 = () => {
|
||||
if (!deptTreeRef.value) {
|
||||
@@ -162,32 +125,28 @@ const getDeptAllCheckedKeys = () => {
|
||||
}
|
||||
|
||||
// 展开/折叠
|
||||
const onExpanded = (type: string) => {
|
||||
if (type === 'menu') {
|
||||
menuTreeRef.value?.expandAll(isMenuExpanded.value)
|
||||
} else if (type === 'dept') {
|
||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
||||
}
|
||||
const onExpanded = () => {
|
||||
deptTreeRef.value?.expandAll(isDeptExpanded.value)
|
||||
}
|
||||
|
||||
// 全选/全不选
|
||||
const onCheckAll = (type: string) => {
|
||||
if (type === 'menu') {
|
||||
menuTreeRef.value?.checkAll(isMenuCheckAll.value)
|
||||
} else if (type === 'dept') {
|
||||
deptTreeRef.value?.checkAll(isDeptCheckAll.value)
|
||||
}
|
||||
const onCheckAll = () => {
|
||||
deptTreeRef.value?.checkAll(isDeptCheckAll.value)
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return false
|
||||
try {
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return false
|
||||
form.menuIds = getMenuAllCheckedKeys()
|
||||
form.deptIds = getDeptAllCheckedKeys()
|
||||
await updateRole(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
if (isUpdate.value) {
|
||||
await updateRole(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
} else {
|
||||
await addRole(form)
|
||||
Message.success('新增成功')
|
||||
}
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -195,23 +154,25 @@ const save = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 打开
|
||||
const onOpen = async (id: string) => {
|
||||
// 新增
|
||||
const onAdd = async () => {
|
||||
reset()
|
||||
dataId.value = id
|
||||
if (!menuList.value.length) {
|
||||
await getMenuList()
|
||||
}
|
||||
if (!deptList.value.length) {
|
||||
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)
|
||||
Object.assign(form, data)
|
||||
data.menuIds?.forEach((node) => {
|
||||
nextTick(() => {
|
||||
menuTreeRef.value?.checkNode(node, true, true)
|
||||
})
|
||||
})
|
||||
data.deptIds?.forEach((node) => {
|
||||
nextTick(() => {
|
||||
deptTreeRef.value?.checkNode(node, true, true)
|
||||
@@ -220,7 +181,7 @@ const onOpen = async (id: string) => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -236,12 +197,4 @@ fieldset legend {
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.menu-tree{
|
||||
:deep(.arco-tree-node-is-leaf) {
|
||||
display: inline-flex;
|
||||
}
|
||||
:deep(.arco-tree-node-indent-block){
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
</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"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
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 dataId = ref('')
|
||||
const visible = ref(false)
|
||||
const selectedUsers = ref<string[]>([])
|
||||
const excludeUsers = ref<string[]>([])
|
||||
|
||||
// 用户选择回调
|
||||
const onSelectUser = (value: string[]) => {
|
||||
@@ -48,6 +53,7 @@ const save = async () => {
|
||||
await assignToUsers(dataId.value, selectedUsers.value)
|
||||
Message.success('分配成功')
|
||||
reset()
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
@@ -58,8 +64,9 @@ const save = async () => {
|
||||
const onOpen = async (id: string) => {
|
||||
dataId.value = id
|
||||
// 初始化选择的用户
|
||||
const { data } = await listRoleUsers(id)
|
||||
selectedUsers.value = data
|
||||
const { data } = await listRoleUserId(id)
|
||||
excludeUsers.value = data
|
||||
selectedUsers.value = []
|
||||
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>
|
||||
<div class="gi_table_page">
|
||||
<GiTable
|
||||
title=""
|
||||
row-key="id"
|
||||
: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>
|
||||
<div class="gi_page">
|
||||
<SplitPanel>
|
||||
<template #left>
|
||||
<RoleTree @node-click="handleSelectRole" />
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['system:role:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
</a-button>
|
||||
<template #main>
|
||||
<a-tabs v-model:activeKey="activeTab" class="gi_tabs" size="large">
|
||||
<a-tab-pane key="1" title="功能权限">
|
||||
<component :is="Pane1" v-if="activeTab === '1'" :role-id="roleId" />
|
||||
</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 #dataScope="{ record }">
|
||||
<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" />
|
||||
</SplitPanel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import RoleAddModal from './RoleAddModal.vue'
|
||||
import RoleUpdateDrawer from './RoleUpdateDrawer.vue'
|
||||
import RoleDetailDrawer from './RoleDetailDrawer.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'
|
||||
import RoleTree from './tree/index.vue'
|
||||
import Pane1 from './components/Pane1.vue'
|
||||
import Pane2 from './components/Pane2.vue'
|
||||
|
||||
defineOptions({ name: 'SystemRole' })
|
||||
|
||||
const { data_scope_enum } = useDict('data_scope_enum')
|
||||
const activeTab = ref('1')
|
||||
|
||||
const queryForm = reactive<RoleQuery>({
|
||||
sort: ['id,desc'],
|
||||
})
|
||||
|
||||
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)
|
||||
const roleId = ref('')
|
||||
// 根据选中角色查询
|
||||
const handleSelectRole = (keys: Array<any>) => {
|
||||
roleId.value = keys.length === 1 ? keys[0] : undefined
|
||||
}
|
||||
</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,83 +10,79 @@
|
||||
<DeptTree @node-click="handleSelectDept" />
|
||||
</template>
|
||||
<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
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['nickname']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #top>
|
||||
<GiForm v-model="queryForm" :options="options" :columns="queryFormColumns" @search="search" @reset="reset"></GiForm>
|
||||
</template>
|
||||
<template #toolbar-left>
|
||||
<a-button v-permission="['system:user:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['nickname']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #top>
|
||||
<GiForm v-model="queryForm" :options="options" :columns="queryFormColumns" @search="search" @reset="reset"></GiForm>
|
||||
</template>
|
||||
<template #toolbar-left>
|
||||
<a-button v-permission="['system:user:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<template #default>新增</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['system:user:import']" @click="onImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>导入</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['system:user:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #nickname="{ record }">
|
||||
<GiCellAvatar :avatar="record.avatar" :name="record.nickname" />
|
||||
</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 #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:user:detail']" title="详情" @click="onDetail(record)">详情</a-link>
|
||||
<a-link v-permission="['system:user:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
||||
<a-link
|
||||
v-permission="['system:user:delete']"
|
||||
status="danger"
|
||||
:disabled="record.isSystem"
|
||||
:title="record.isSystem ? '系统内置数据不能删除' : '删除'"
|
||||
@click="onDelete(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
<a-dropdown>
|
||||
<a-button v-if="has.hasPermOr(['system:user:resetPwd', 'system:user:updateRole'])" type="text" size="mini" title="更多">
|
||||
<template #icon>
|
||||
<icon-more :size="16" />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button v-permission="['system:user:import']" @click="onImport">
|
||||
<template #icon><icon-upload /></template>
|
||||
<template #default>导入</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #toolbar-right>
|
||||
<a-button v-permission="['system:user:export']" @click="onExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<template #default>导出</template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #nickname="{ record }">
|
||||
<GiCellAvatar :avatar="record.avatar" :name="record.nickname" />
|
||||
</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 #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:user:detail']" title="详情" @click="onDetail(record)">详情</a-link>
|
||||
<a-link v-permission="['system:user:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
||||
<a-link
|
||||
v-permission="['system:user:delete']"
|
||||
status="danger"
|
||||
:disabled="record.isSystem"
|
||||
:title="record.isSystem ? '系统内置数据不能删除' : '删除'"
|
||||
@click="onDelete(record)"
|
||||
>
|
||||
删除
|
||||
</a-link>
|
||||
<a-dropdown>
|
||||
<a-button v-if="has.hasPermOr(['system:user:resetPwd', 'system:user:updateRole'])" type="text" size="mini" title="更多">
|
||||
<template #icon>
|
||||
<icon-more :size="16" />
|
||||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption v-permission="['system:user:resetPwd']" title="重置密码" @click="onResetPwd(record)">重置密码</a-doption>
|
||||
<a-doption v-permission="['system:user:updateRole']" title="分配角色" @click="onUpdateRole(record)">分配角色</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<template #content>
|
||||
<a-doption v-permission="['system:user:resetPwd']" title="重置密码" @click="onResetPwd(record)">重置密码</a-doption>
|
||||
<a-doption v-permission="['system:user:updateRole']" title="分配角色" @click="onUpdateRole(record)">分配角色</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
</template>
|
||||
</SplitPanel>
|
||||
|
||||
|
Reference in New Issue
Block a user