feat: 角色权限和数据权限配置

This commit is contained in:
chansee97 2025-09-03 00:56:05 +08:00
parent 0f4325fd68
commit 4687b32dc8
7 changed files with 257 additions and 14 deletions

View File

@ -30,9 +30,9 @@ namespace Entity {
*/
status?: number
/**
*
* 1 2 3 4 5
*/
sort: number
dataScope: number
/**
*
*/
@ -41,6 +41,8 @@ namespace Entity {
*
*/
updateTime?: string
menus?: Entity.Menu[]
depts?: Entity.Dept[]
[property: string]: any
}
}

View File

@ -1,7 +1,7 @@
import type { DataTableColumns } from 'naive-ui'
import { NButton, NPopconfirm, NSpace, NSwitch } from 'naive-ui'
import type { ProSearchFormColumns } from 'pro-naive-ui'
import { renderProCopyableText, renderProDateText } from 'pro-naive-ui'
import { renderProCopyableText, renderProTags } from 'pro-naive-ui'
export const searchColumns: ProSearchFormColumns<Entity.Role> = [
{
@ -51,11 +51,25 @@ export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<
key: 'roleKey',
render: row => renderProCopyableText(row.roleKey),
},
{
title: '数据范围',
align: 'center',
key: 'dataScope',
render: (row) => {
const dataScopeMap: Record<number, string> = {
1: '全部数据权限',
2: '自定数据权限',
3: '本部门数据权限',
4: '本部门及以下数据权限',
5: '仅本人数据权限',
}
return renderProTags(dataScopeMap[row.dataScope])
},
},
{
title: '备注',
align: 'center',
key: 'remark',
width: 200,
},
{
title: '状态',
@ -80,9 +94,6 @@ export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<
align: 'center',
key: 'createTime',
width: 200,
render: row => renderProDateText(row.createTime, {
pattern: 'datetime',
}),
},
{
title: '操作',

View File

@ -2,6 +2,8 @@
import { useBoolean } from '@/hooks'
import { createRole, getRoleById, updateRole } from '@/api'
import { createProModalForm } from 'pro-naive-ui'
import TreeDeptSelect from './TreeDeptSelect.vue'
import TreeMenuSelect from './TreeMenuSelect.vue'
interface Props {
modalName?: string
@ -17,16 +19,18 @@ const emit = defineEmits<{
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
const modalForm = createProModalForm<Partial<Entity.Role>>({
const modalForm = createProModalForm<Partial<Entity.Role> & { deptIds?: number[], menuIds?: number[] }>({
omitEmptyString: false,
initialValues: {
status: 0,
dataScope: 1,
},
onSubmit: submitModal,
})
type ModalType = 'add' | 'edit'
const modalType = shallowRef<ModalType>('add')
const modalTitle = computed(() => {
const titleMap: Record<ModalType, string> = {
add: '添加',
@ -37,11 +41,10 @@ const modalTitle = computed(() => {
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Role>) {
modalType.value = type
modalForm.open()
const handlers = {
async add() {
// 使
},
async edit() {
if (!data)
@ -49,6 +52,9 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.Role>) {
const { data: role } = await getRoleById(data.id!)
modalForm.values.value = role
//
modalForm.values.value.menuIds = role.menus?.map(menu => menu.id) || []
modalForm.values.value.deptIds = role.depts?.map(dept => dept.id) || []
},
}
await handlers[type]()
@ -99,7 +105,7 @@ defineExpose({
:title="modalTitle"
:form="modalForm"
:loading="submitLoading"
width="600px"
width="800px"
>
<div class="grid grid-cols-2 gap-4">
<pro-input
@ -117,6 +123,46 @@ defineExpose({
path="status"
:field-props="{ checkedValue: 0, uncheckedValue: 1 }"
/>
<pro-select
title="数据范围"
path="dataScope"
:field-props="{
options: [
{ label: '全部数据权限', value: 1 },
{ label: '自定数据权限', value: 2 },
{ label: '本部门数据权限', value: 3 },
{ label: '本部门及以下数据权限', value: 4 },
{ label: '仅本人数据权限', value: 5 },
],
}"
/>
<pro-field
class="col-span-2"
title="菜单权限"
path="menuIds"
>
<template #input="{ inputProps }">
<TreeMenuSelect
:value="inputProps.value"
@update:value="inputProps.onUpdateValue"
/>
</template>
</pro-field>
<pro-field
v-if="modalForm.values.value.dataScope === 2"
class="col-span-2"
title="数据权限"
path="deptIds"
>
<template #input="{ inputProps }">
<TreeDeptSelect
:value="inputProps.value"
@update:value="inputProps.onUpdateValue"
/>
</template>
</pro-field>
<pro-textarea
class="col-span-2"
title="备注"

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { getDeptOptions } from '@/api'
const deptOptions = ref<any[]>([])
const checkedKeys = defineModel<number[]>('value', { default: () => [] })
const expandedKeys = ref<number[]>([])
const cascade = ref(true)
const isExpanded = ref(false)
const isAllSelected = ref(false)
// keys
function getAllKeys(nodes: any[]): number[] {
let keys: number[] = []
nodes.forEach((node) => {
keys.push(node.value)
if (node.children && node.children.length > 0) {
keys = keys.concat(getAllKeys(node.children))
}
})
return keys
}
// checkbox
function handleExpandedChange(expanded: boolean) {
if (expanded) {
expandedKeys.value = getAllKeys(deptOptions.value)
}
else {
expandedKeys.value = []
}
}
function handleAllSelectedChange(selected: boolean) {
if (selected) {
checkedKeys.value = getAllKeys(deptOptions.value)
}
else {
checkedKeys.value = []
}
}
//
async function loadDeptOptions() {
try {
const { data } = await getDeptOptions()
deptOptions.value = data
}
catch (error) {
console.error('加载部门数据失败:', error)
deptOptions.value = []
}
}
//
onMounted(() => {
loadDeptOptions()
})
</script>
<template>
<div class="w-full">
<div class="mb-2 flex gap-4 justify-end">
<n-checkbox v-model:checked="isExpanded" @update:checked="handleExpandedChange">
展开全部
</n-checkbox>
<n-checkbox v-model:checked="isAllSelected" @update:checked="handleAllSelectedChange">
全选
</n-checkbox>
<n-checkbox v-model:checked="cascade">
父子联动
</n-checkbox>
</div>
<n-scrollbar class="border border-gray-200 rounded p-2" style="max-height: 240px">
<n-tree
v-model:checked-keys="checkedKeys"
v-model:expanded-keys="expandedKeys"
block-line
show-line
checkable
:cascade="cascade"
:data="deptOptions"
key-field="value"
/>
</n-scrollbar>
</div>
</template>

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { getMenuOptions } from '@/api'
const menuOptions = ref<any[]>([])
const checkedKeys = defineModel<number[]>('value', { default: () => [] })
const expandedKeys = ref<number[]>([])
const cascade = ref(true)
const isExpanded = ref(false)
const isAllSelected = ref(false)
// keys
function getAllKeys(nodes: any[]): number[] {
let keys: number[] = []
nodes.forEach((node) => {
keys.push(node.value)
if (node.children && node.children.length > 0) {
keys = keys.concat(getAllKeys(node.children))
}
})
return keys
}
// checkbox
function handleExpandedChange(expanded: boolean) {
if (expanded) {
expandedKeys.value = getAllKeys(menuOptions.value)
}
else {
expandedKeys.value = []
}
}
function handleAllSelectedChange(selected: boolean) {
if (selected) {
checkedKeys.value = getAllKeys(menuOptions.value)
}
else {
checkedKeys.value = []
}
}
//
async function loadMenuOptions() {
try {
const { data } = await getMenuOptions()
menuOptions.value = data
}
catch (error) {
console.error('加载菜单数据失败:', error)
menuOptions.value = []
}
}
//
onMounted(() => {
loadMenuOptions()
})
</script>
<template>
<div class="w-full">
<div class="mb-2 flex gap-4 justify-end">
<n-checkbox v-model:checked="isExpanded" @update:checked="handleExpandedChange">
展开全部
</n-checkbox>
<n-checkbox v-model:checked="isAllSelected" @update:checked="handleAllSelectedChange">
全选
</n-checkbox>
<n-checkbox v-model:checked="cascade">
父子联动
</n-checkbox>
</div>
<n-scrollbar class="border border-gray-200 rounded p-2" style="max-height: 240px">
<n-tree
v-model:checked-keys="checkedKeys"
v-model:expanded-keys="expandedKeys"
block-line
show-line
checkable
:cascade="cascade"
:data="menuOptions"
key-field="value"
/>
</n-scrollbar>
</div>
</template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useBoolean } from '@/hooks'
import { createUser, getRoleOptions, getUserById, updateUser } from '@/api'
import { createUser, getDeptOptions, getRoleOptions, getUserById, updateUser } from '@/api'
import { createProModalForm } from 'pro-naive-ui'
import { Regex } from '@/constants'
@ -37,12 +37,16 @@ const modalTitle = computed(() => {
return `${titleMap[modalType.value]}${modalName}`
})
const roleOptions = ref<Entity.TreeNode[]>([])
const deptOptions = ref<any[]>([])
async function openModal(type: ModalType = 'add', data?: Partial<Entity.User>) {
modalType.value = type
getRoleOptions().then((res) => {
roleOptions.value = res.data
})
getDeptOptions().then((res) => {
deptOptions.value = res.data
})
modalForm.open()
const handlers = {
async add() {
@ -132,9 +136,14 @@ defineExpose({
],
}"
/>
<pro-digit
title="部门ID"
<pro-tree-select
title="部门"
path="deptId"
:field-props="{
options: deptOptions,
keyField: 'value',
clearable: true,
}"
/>
<pro-input
title="邮箱"

View File

@ -113,6 +113,7 @@ onMounted(() => {
</template>
<n-tree
block-line
show-line
:data="treeData"
key-field="value"
:pattern="deptPattern"