新增部门管理功能,角色权限支持部门数据权限划分,接口权限认证加入部门数据权限字段

This commit is contained in:
ktianc 2024-01-05 11:36:04 +08:00
parent d16382c90c
commit bade36dd1b
25 changed files with 1288 additions and 131 deletions

View File

@ -0,0 +1,25 @@
import request from '@/config/axios'
export const getDeptListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/depts', params })
}
export const delDeptListApi = (data: any): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/depts', data })
}
export const addDeptListApi = (data: any): Promise<IResponse> => {
return request.post({ url: '/vadmin/auth/depts', data })
}
export const putDeptListApi = (data: any): Promise<IResponse> => {
return request.put({ url: `/vadmin/auth/depts/${data.id}`, data })
}
export const getDeptTreeOptionsApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/dept/tree/options' })
}
export const getDeptUserTreeOptionsApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/dept/user/tree/options' })
}

View File

@ -0,0 +1,232 @@
<script setup lang="tsx">
import { reactive, ref, unref } from 'vue'
import {
getDeptListApi,
delDeptListApi,
addDeptListApi,
putDeptListApi
} from '@/api/vadmin/auth/dept'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
defineOptions({
name: 'AuthDept'
})
const { t } = useI18n()
const { tableRegister, tableState, tableMethods } = useTable({
fetchDataApi: async () => {
const { pageSize, currentPage } = tableState
const res = await getDeptListApi({
page: unref(currentPage),
limit: unref(pageSize)
})
return {
list: res.data || [],
total: res.count || 0
}
},
fetchDelApi: async (value) => {
const res = await delDeptListApi(value)
return res.code === 200
}
})
const { dataList, loading } = tableState
const { getList, delList } = tableMethods
const tableColumns = reactive<TableColumn[]>([
{
field: 'name',
label: '部门名称',
disabled: true,
show: true
},
{
field: 'dept_key',
label: '部门标识',
show: true
},
{
field: 'owner',
label: '负责人',
show: true
},
{
field: 'phone',
label: '联系电话',
show: true
},
{
field: 'email',
label: '邮箱',
show: true
},
{
field: 'desc',
label: '描述',
show: true
},
{
field: 'order',
label: '排序',
width: '120px',
show: true
},
{
field: 'disabled',
label: '是否禁用',
width: '120px',
show: true,
slots: {
default: (data: any) => {
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
</>
)
}
}
},
{
field: 'action',
width: '200px',
label: '操作',
show: true,
slots: {
default: (data: any) => {
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton type="primary" link size="small" onClick={() => addSonAction(row)}>
添加子部门
</ElButton>
<ElButton
type="danger"
loading={delLoading.value}
link
size="small"
onClick={() => delData(row)}
>
删除
</ElButton>
</>
)
}
}
}
])
const delLoading = ref(false)
const delData = async (row: any) => {
delLoading.value = true
await delList(true, [row.id]).finally(() => {
delLoading.value = false
})
}
const dialogVisible = ref(false)
const dialogTitle = ref('')
const currentRow = ref()
const parentId = ref(undefined)
const actionType = ref('')
const writeRef = ref<ComponentRef<typeof Write>>()
const saveLoading = ref(false)
const editAction = (row: any) => {
dialogTitle.value = '编辑'
actionType.value = 'edit'
currentRow.value = row
dialogVisible.value = true
}
const addAction = () => {
dialogTitle.value = '新增'
actionType.value = 'add'
currentRow.value = undefined
dialogVisible.value = true
}
const addSonAction = (row: any) => {
dialogTitle.value = '添加子部门'
actionType.value = 'addSon'
parentId.value = row.id
currentRow.value = undefined
dialogVisible.value = true
}
const save = async () => {
const write = unref(writeRef)
const formData = await write?.submit()
if (formData) {
saveLoading.value = true
try {
const res = ref({})
if (actionType.value === 'add' || actionType.value === 'addSon') {
res.value = await addDeptListApi(formData)
if (res.value) {
parentId.value = undefined
dialogVisible.value = false
getList()
}
} else if (actionType.value === 'edit') {
res.value = await putDeptListApi(formData)
if (res.value) {
dialogVisible.value = false
getList()
}
}
} finally {
saveLoading.value = false
}
}
}
</script>
<template>
<ContentWrap>
<Table
:columns="tableColumns"
showAction
default-expand-all
node-key="id"
:data="dataList"
:loading="loading"
@register="tableRegister"
@refresh="getList"
>
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增部门</ElButton>
</ElCol>
</ElRow>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<Write ref="writeRef" :current-row="currentRow" :parent-id="parentId" />
<template #footer>
<ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,167 @@
<script setup lang="tsx">
import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { propTypes } from '@/utils/propTypes'
import { getDeptTreeOptionsApi } from '@/api/vadmin/auth/dept'
const { required } = useValidator()
const props = defineProps({
currentRow: {
type: Object as PropType<any>,
default: () => null
},
parentId: propTypes.number.def(undefined)
})
const formSchema = reactive<FormSchema[]>([
{
field: 'parent_id',
label: '上级部门',
colProps: {
span: 24
},
component: 'TreeSelect',
componentProps: {
style: {
width: '100%'
},
checkStrictly: true,
placeholder: '请选择上级部门',
nodeKey: 'value',
defaultExpandAll: true
},
optionApi: async () => {
const res = await getDeptTreeOptionsApi()
return res.data
},
value: props.parentId
},
{
field: 'name',
label: '部门名称',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'dept_key',
label: '部门标识',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'owner',
label: '负责人',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'phone',
label: '联系电话',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'email',
label: '邮箱',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'desc',
label: '描述',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'order',
label: '显示排序',
component: 'InputNumber',
colProps: {
span: 12
},
componentProps: {
style: {
width: '100%'
}
}
},
{
field: 'disabled',
label: '是否禁用',
colProps: {
span: 12
},
component: 'RadioGroup',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '正常',
value: false
},
{
label: '停用',
value: true
}
]
},
value: false
}
])
const rules = reactive({
name: [required()],
dept_key: [required()],
disabled: [required()],
order: [required()]
})
const { formRegister, formMethods } = useForm()
const { setValues, getFormData, getElFormExpose } = formMethods
const submit = async () => {
const elForm = await getElFormExpose()
const valid = await elForm?.validate()
if (valid) {
const formData = await getFormData()
return formData
}
}
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
defineExpose({
submit
})
</script>
<template>
<Form :rules="rules" @register="formRegister" :schema="formSchema" :labelWidth="100" />
</template>

View File

@ -15,7 +15,10 @@ import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import AuthManage from './components/AuthManage.vue'
import { Dialog } from '@/components/Dialog'
import { DictDetail, selectDictLabel } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
defineOptions({
name: 'AuthRole'
@ -45,12 +48,22 @@ const { tableRegister, tableState, tableMethods } = useTable({
const { dataList, loading, total, pageSize, currentPage } = tableState
const { getList, delList } = tableMethods
let dataRangeOptions = ref<DictDetail[]>([])
const getOptions = async () => {
const dictStore = useDictStore()
const dictOptions = await dictStore.getDictObj(['sys_vadmin_data_range'])
dataRangeOptions.value = dictOptions.sys_vadmin_data_range
}
getOptions()
const tableColumns = reactive<TableColumn[]>([
{
field: 'id',
label: '角色编号',
show: true,
disabled: true
show: false,
disabled: false
},
{
field: 'name',
@ -63,6 +76,21 @@ const tableColumns = reactive<TableColumn[]>([
label: '权限字符',
show: true
},
{
field: 'data_range',
label: '数据范围',
show: true,
slots: {
default: (data: any) => {
const row = data.row
return (
<>
<div>{selectDictLabel(unref(dataRangeOptions), row.data_range.toString())}</div>
</>
)
}
}
},
{
field: 'order',
label: '显示顺序',
@ -92,7 +120,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.is_admin} disabled />
<ElSwitch value={row.is_admin} disabled />
</>
)
}
@ -105,7 +133,7 @@ const tableColumns = reactive<TableColumn[]>([
},
{
field: 'action',
width: '150px',
width: '170px',
label: '操作',
show: true,
slots: {
@ -125,6 +153,15 @@ const tableColumns = reactive<TableColumn[]>([
>
编辑
</ElButton>
<ElButton
v-show={row.id !== 1}
type="primary"
link
size="small"
onClick={() => authManageActive(row)}
>
权限管理
</ElButton>
<ElButton
v-show={row.id !== 1}
type="danger"
@ -204,6 +241,18 @@ const delData = async (row: any) => {
})
}
const authManageRef = ref<ComponentRef<typeof AuthManage>>()
//
const authManageActive = async (row: any) => {
const res = await getRoleApi(row.id)
if (res) {
res.data.data_range = res.data.data_range.toString()
currentRow.value = res.data
authManageRef.value?.openDrawer()
}
}
const dialogVisible = ref(false)
const dialogTitle = ref('')
@ -217,15 +266,16 @@ const saveLoading = ref(false)
const editAction = async (row: any) => {
const res = await getRoleApi(row.id)
if (res) {
dialogTitle.value = '编辑'
dialogTitle.value = '编辑角色'
actionType.value = 'edit'
res.data.data_range = res.data.data_range.toString()
currentRow.value = res.data
dialogVisible.value = true
}
}
const addAction = () => {
dialogTitle.value = '新增'
dialogTitle.value = '新增角色'
actionType.value = 'add'
currentRow.value = undefined
dialogVisible.value = true
@ -298,4 +348,6 @@ const save = async () => {
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</template>
</Dialog>
<AuthManage ref="authManageRef" :current-row="currentRow" @get-list="getList" />
</template>

View File

@ -0,0 +1,255 @@
<script setup lang="ts">
import {
ElDrawer,
ElButton,
ElDivider,
ElSelect,
ElOption,
ElTree,
ElContainer,
ElHeader,
ElAside,
ElMain,
ElMessage
} from 'element-plus'
import { ref, nextTick, unref, PropType, watch } from 'vue'
import { Icon } from '@/components/Icon'
import { useDictStore } from '@/store/modules/dict'
import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { getDeptUserTreeOptionsApi } from '@/api/vadmin/auth/dept'
import { eachTree } from '@/utils/tree'
import { isEmptyVal } from '@/utils/is'
import { putRoleListApi } from '@/api/vadmin/auth/role'
const props = defineProps({
currentRow: {
type: Object as PropType<any>,
default: () => null
}
})
const emit = defineEmits(['getList'])
const data = ref({} as Recordable)
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
data.value = JSON.parse(JSON.stringify(currentRow))
},
{
deep: true,
immediate: true
}
)
const drawerVisible = ref(false)
const dataRangeOptions = ref()
const getOptions = async () => {
const dictStore = useDictStore()
const dictOptions = await dictStore.getDictObj(['sys_vadmin_data_range'])
dataRangeOptions.value = dictOptions.sys_vadmin_data_range
}
const defaultProps = {
children: 'children',
label: 'label'
}
//
let deptTreeData = ref([] as any[])
const deptTreeRef = ref<InstanceType<typeof ElTree>>()
const getDeptTreeOptions = async () => {
const res = await getDeptUserTreeOptionsApi()
if (res) {
deptTreeData.value = res.data
await nextTick()
if (props.currentRow) {
const dept_ids: number[] = props.currentRow.depts.map((item) => item.id)
const checked: number[] = []
//
eachTree(res.data, (v) => {
if (dept_ids.includes(v.value)) {
checked.push(v.value)
}
})
for (const item of checked) {
unref(deptTreeRef)?.setChecked(item, true, false)
}
}
}
}
//
let menuTreeData = ref([] as any[])
const menuTreeRef = ref<InstanceType<typeof ElTree>>()
const getMenuRoleTreeOptions = async () => {
const res = await getMenuRoleTreeOptionsApi()
if (res) {
menuTreeData.value = res.data
await nextTick()
if (props.currentRow) {
const menu_ids: number[] = props.currentRow.menus.map((item) => item.id)
const checked: number[] = []
//
eachTree(res.data, (v) => {
if (menu_ids.includes(v.value)) {
checked.push(v.value)
}
})
for (const item of checked) {
unref(menuTreeRef)?.setChecked(item, true, false)
}
}
}
}
const loading = ref(false)
const submit = async () => {
if (loading.value) return
if (isEmptyVal(data.value.data_range)) {
ElMessage.error('数据范围选择项不能为空!')
return
}
loading.value = true
const menu_ids = [
...(unref(menuTreeRef)?.getCheckedKeys() || []),
...(unref(menuTreeRef)?.getHalfCheckedKeys() || [])
]
data.value.menu_ids = menu_ids
data.value.dept_ids = unref(deptTreeRef)?.getCheckedKeys()
try {
const res = await putRoleListApi(data.value)
if (res) {
loading.value = false
ElMessage.success('保存成功')
closeDrawer()
emit('getList')
}
} finally {
loading.value = false
}
}
const openDrawer = () => {
drawerVisible.value = true
getMenuRoleTreeOptions()
getDeptTreeOptions()
}
const closeDrawer = () => {
drawerVisible.value = false
data.value = {}
unref(menuTreeRef)?.setCheckedKeys([])
unref(deptTreeRef)?.setCheckedKeys([])
}
getOptions()
defineExpose({
openDrawer
})
</script>
<template>
<div class="auth-manage-main-view">
<ElDrawer v-model="drawerVisible" :with-header="false" :size="1000" :before-close="closeDrawer">
<ElContainer>
<ElHeader>
<div class="flex justify-between pt-[20px] pb-[20px]">
<span>权限管理</span>
<span @click="closeDrawer" class="flex cursor-pointer">
<Icon icon="iconamoon:close-thin" :size="23" />
</span>
</div>
</ElHeader>
<ElDivider />
<div class="h-12 flex justify-between mt-3 mr-3 ml-5">
<div class="mt-1 text-[#909399]">
<span>角色名称{{ data.name }}</span>
</div>
<ElButton type="primary" :loading="loading" @click="submit">保存</ElButton>
</div>
<ElDivider />
<ElContainer>
<ElAside width="450px">
<div class="border-r-1 border-r-[#f0f0f0] b-r-solid h-[100%] p-[20px] box-border">
<div>
<div class="flex items-center">
<div class="yxt-divider"></div>
<span>数据权限</span>
</div>
<div class="ml-4 mt-3">
<ElSelect v-model="data.data_range" placeholder="请选择数据范围">
<ElOption
v-for="item in dataRangeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
<div
v-if="data.data_range === '4'"
class="mt-3 max-h-[65vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto"
>
<ElTree
ref="deptTreeRef"
:data="deptTreeData"
show-checkbox
node-key="value"
:props="defaultProps"
:default-expand-all="true"
:check-strictly="true"
/>
</div>
</div>
</div>
</div>
</ElAside>
<ElMain>
<div class="flex items-center">
<div class="yxt-divider"></div>
<span>菜单权限</span>
</div>
<div class="mt-5 max-h-[70vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border">
<ElTree
ref="menuTreeRef"
:data="menuTreeData"
show-checkbox
node-key="value"
:props="defaultProps"
:default-expand-all="true"
:check-strictly="false"
/>
</div>
</ElMain>
</ElContainer>
</ElContainer>
<ElDivider />
<!-- <ElDivider /> -->
</ElDrawer>
</div>
</template>
<style lang="less">
.auth-manage-main-view .el-drawer .el-drawer__body {
padding: 0;
}
.auth-manage-main-view .el-divider.el-divider--horizontal {
margin: 0;
}
</style>
<style scoped lang="less">
.yxt-divider {
background: #409eff;
width: 8px;
height: 20px;
display: inline-block;
margin-right: 10px;
}
</style>

View File

@ -1,11 +1,11 @@
<script setup lang="tsx">
import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, nextTick, reactive, ref, unref, watch } from 'vue'
import { PropType, reactive, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { ElCheckbox, ElTree } from 'element-plus'
import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { eachTree } from '@/utils/tree'
// import { ElTree } from 'element-plus'
// import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
// import { eachTree } from '@/utils/tree'
const { required } = useValidator()
@ -16,39 +16,38 @@ const props = defineProps({
}
})
let treeData = ref([] as any[])
// let treeData = ref([] as any[])
// const treeRef = ref<InstanceType<typeof ElTree>>()
const treeRef = ref<InstanceType<typeof ElTree>>()
// const getMenuRoleTreeOptions = async () => {
// const res = await getMenuRoleTreeOptionsApi()
// if (res) {
// treeData.value = res.data
// await nextTick()
// if (props.currentRow) {
// const menu_ids: number[] = props.currentRow.menus.map((item) => item.id)
// const checked: number[] = []
// //
// eachTree(res.data, (v) => {
// if (menu_ids.includes(v.value)) {
// checked.push(v.value)
// }
// })
// for (const item of checked) {
// unref(treeRef)?.setChecked(item, true, false)
// }
// }
// }
// }
const getMenuRoleTreeOptions = async () => {
const res = await getMenuRoleTreeOptionsApi()
if (res) {
treeData.value = res.data
await nextTick()
if (props.currentRow) {
const menu_ids: number[] = props.currentRow.menus.map((item) => item.id)
const checked: number[] = []
//
eachTree(res.data, (v) => {
if (menu_ids.includes(v.value)) {
checked.push(v.value)
}
})
for (const item of checked) {
unref(treeRef)?.setChecked(item, true, false)
}
}
}
}
// const defaultProps = {
// children: 'children',
// label: 'label'
// }
const defaultProps = {
children: 'children',
label: 'label'
}
let selectAll = ref(false)
let defaultExpandAll = ref(true)
let checkStrictly = ref(true)
// let selectAll = ref(false)
// let defaultExpandAll = ref(true)
// let checkStrictly = ref(true)
// key
const getTreeNodeKeys = (nodes: Recordable[]): number[] => {
@ -62,19 +61,19 @@ const getTreeNodeKeys = (nodes: Recordable[]): number[] => {
return keys
}
// /
const handleCheckedTreeExpand = (value: boolean) => {
defaultExpandAll.value = value
for (let i = 0; i < treeData.value.length; i++) {
treeRef.value!.store.nodesMap[treeData.value[i].value].expanded = value
}
}
// // /
// const handleCheckedTreeExpand = (value: boolean) => {
// defaultExpandAll.value = value
// for (let i = 0; i < treeData.value.length; i++) {
// treeRef.value!.store.nodesMap[treeData.value[i].value].expanded = value
// }
// }
///
const handleCheckedTreeNodeAll = (value: boolean) => {
selectAll.value = value
treeRef.value!.setCheckedKeys(value ? getTreeNodeKeys(treeData.value) : [])
}
// ///
// const handleCheckedTreeNodeAll = (value: boolean) => {
// selectAll.value = value
// treeRef.value!.setCheckedKeys(value ? getTreeNodeKeys(treeData.value) : [])
// }
const formSchema = reactive<FormSchema[]>([
{
@ -156,57 +155,64 @@ const formSchema = reactive<FormSchema[]>([
},
{
field: 'desc',
label: '描述',
colProps: {
span: 12
},
component: 'Input'
},
{
field: 'menu_ids',
label: '菜单权限',
label: '角色描述',
colProps: {
span: 24
},
formItemProps: {
slots: {
default: () => {
return (
<>
<div>
<div>
<ElCheckbox
modelValue={defaultExpandAll.value}
onChange={handleCheckedTreeExpand}
label="展开/折叠"
size="large"
/>
<ElCheckbox
modelValue={selectAll.value}
onChange={handleCheckedTreeNodeAll}
label="全选/全不选"
size="large"
/>
<ElCheckbox v-model={checkStrictly.value} label="父子联动" size="large" />
</div>
<div class="max-h-420px b-1 b-solid b-[#e5e7eb] p-10px overflow-auto">
<ElTree
ref={treeRef}
data={treeData.value}
show-checkbox
node-key="value"
props={defaultProps}
default-expand-all={defaultExpandAll.value}
check-strictly={!checkStrictly.value}
></ElTree>
</div>
</div>
</>
)
}
component: 'Input',
componentProps: {
rows: 4,
type: 'textarea',
style: {
width: '600px'
}
}
}
// {
// field: 'menu_ids',
// label: '',
// colProps: {
// span: 24
// },
// formItemProps: {
// slots: {
// default: () => {
// return (
// <>
// <div>
// <div>
// <ElCheckbox
// modelValue={defaultExpandAll.value}
// onChange={handleCheckedTreeExpand}
// label="/"
// size="large"
// />
// <ElCheckbox
// modelValue={selectAll.value}
// onChange={handleCheckedTreeNodeAll}
// label="/"
// size="large"
// />
// <ElCheckbox v-model={checkStrictly.value} label="" size="large" />
// </div>
// <div class="max-h-420px b-1 b-solid b-[#e5e7eb] p-10px overflow-auto">
// <ElTree
// ref={treeRef}
// data={treeData.value}
// show-checkbox
// node-key="value"
// props={defaultProps}
// default-expand-all={defaultExpandAll.value}
// check-strictly={!checkStrictly.value}
// ></ElTree>
// </div>
// </div>
// </>
// )
// }
// }
// }
// }
])
const rules = reactive({
@ -223,10 +229,10 @@ const submit = async () => {
const valid = await elForm?.validate()
if (valid) {
const formData = await getFormData()
formData.menu_ids = [
...(unref(treeRef)?.getCheckedKeys() || []),
...(unref(treeRef)?.getHalfCheckedKeys() || [])
]
// formData.menu_ids = [
// ...(unref(treeRef)?.getCheckedKeys() || []),
// ...(unref(treeRef)?.getHalfCheckedKeys() || [])
// ]
return formData
}
}
@ -243,7 +249,7 @@ watch(
}
)
getMenuRoleTreeOptions()
// getMenuRoleTreeOptions()
defineExpose({
submit

View File

@ -84,8 +84,8 @@ const tableColumns = reactive<TableColumn[]>([
field: 'id',
label: '用户编号',
width: '100px',
show: true,
disabled: true
show: false,
disabled: false
},
{
field: 'name',
@ -113,7 +113,7 @@ const tableColumns = reactive<TableColumn[]>([
{
field: 'gender',
label: '性别',
show: true,
show: false,
slots: {
default: (data: any) => {
const row = data.row
@ -140,10 +140,25 @@ const tableColumns = reactive<TableColumn[]>([
}
}
},
{
field: 'depts',
label: '部门',
show: true,
slots: {
default: (data: any) => {
const row = data.row
return (
<>
<div class="text-truncate">{row.depts.map((item) => item.name).join()}</div>
</>
)
}
}
},
{
field: 'is_active',
label: '是否可用',
show: true,
show: false,
slots: {
default: (data: any) => {
const row = data.row
@ -180,7 +195,7 @@ const tableColumns = reactive<TableColumn[]>([
field: 'create_datetime',
label: '创建时间',
width: '190px',
show: true
show: false
},
{
field: 'action',
@ -325,6 +340,7 @@ const editAction = async (row: any) => {
if (res) {
dialogTitle.value = '编辑用户'
res.data.role_ids = res.data.roles.map((item: any) => item.id)
res.data.dept_ids = res.data.depts.map((item: any) => item.id)
actionType.value = 'edit'
currentRow.value = res.data
dialogVisible.value = true

View File

@ -4,6 +4,7 @@ import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { getRoleOptionsApi } from '@/api/vadmin/auth/role'
import { getDeptUserTreeOptionsApi } from '@/api/vadmin/auth/dept'
const { required, isTelephone, isEmail } = useValidator()
@ -173,6 +174,28 @@ const formSchema = reactive<FormSchema[]>([
},
value: [],
ifshow: (values) => values.is_staff
},
{
field: 'dept_ids',
label: '部门',
colProps: {
span: 24
},
component: 'TreeSelect',
componentProps: {
style: {
width: '100%'
},
multiple: true,
checkStrictly: true,
defaultExpandAll: true
},
optionApi: async () => {
const res = await getDeptUserTreeOptionsApi()
return res.data
},
value: [],
ifshow: (values) => values.is_staff
}
])
@ -181,6 +204,7 @@ const rules = reactive({
is_active: [required()],
is_staff: [required()],
role_ids: [required()],
dept_ids: [required()],
telephone: [required(), { validator: isTelephone, trigger: 'blur' }],
email: [{ validator: isEmail, trigger: 'blur' }]
})

View File

@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
"""
系统版本
"""
VERSION = "3.4.2"
VERSION = "3.5.0"
"""安全警告: 不要在生产中打开调试运行!"""
DEBUG = False

View File

@ -50,6 +50,33 @@ class UserDal(DalBase):
self.model = models.VadminUser
self.schema = schemas.UserSimpleOut
async def recursion_get_dept_ids(
self,
user: models.VadminUser,
depts: list[models.VadminDept] = None,
dept_ids: list[int] = None
) -> list:
"""
递归获取所有关联部门 id
:param user:
:param depts: 所有部门实例
:param dept_ids: 父级部门 id 列表
:return:
"""
if not depts:
depts = await DeptDal(self.db).get_datas(limit=0, v_return_objs=True)
result = []
for i in user.depts:
result.append(i.id)
result.extend(await self.recursion_get_dept_ids(user, depts, result))
return list(set(result))
elif dept_ids:
result = [i.id for i in filter(lambda item: item.parent_id in dept_ids, depts)]
result.extend(await self.recursion_get_dept_ids(user, depts, result))
return result
else:
return []
async def update_login_info(self, user: models.VadminUser, last_ip: str) -> None:
"""
更新当前登录信息
@ -82,11 +109,15 @@ class UserDal(DalBase):
password = data.telephone[5:12] if settings.DEFAULT_PASSWORD == "0" else settings.DEFAULT_PASSWORD
data.password = self.model.get_password_hash(password)
data.avatar = data.avatar if data.avatar else settings.DEFAULT_AVATAR
obj = self.model(**data.model_dump(exclude={'role_ids'}))
obj = self.model(**data.model_dump(exclude={'role_ids', "dept_ids"}))
if data.role_ids:
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", data.role_ids), v_return_objs=True)
for role in roles:
obj.roles.add(role)
if data.dept_ids:
depts = await DeptDal(self.db).get_datas(limit=0, id=("in", data.dept_ids), v_return_objs=True)
for dept in depts:
obj.depts.add(dept)
await self.flush(obj)
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
@ -118,6 +149,14 @@ class UserDal(DalBase):
for role in roles:
obj.roles.add(role)
continue
elif key == "dept_ids":
if value:
depts = await DeptDal(self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
if obj.depts:
obj.depts.clear()
for dept in depts:
obj.depts.add(dept)
continue
setattr(obj, key, value)
await self.flush(obj)
return await self.out_dict(obj, None, v_return_obj, v_schema)
@ -395,11 +434,15 @@ class RoleDal(DalBase):
:param v_schema:
:return:
"""
obj = self.model(**data.model_dump(exclude={'menu_ids'}))
obj = self.model(**data.model_dump(exclude={'menu_ids', 'dept_ids'}))
if data.menu_ids:
menus = await MenuDal(db=self.db).get_datas(limit=0, id=("in", data.menu_ids), v_return_objs=True)
for menu in menus:
obj.menus.add(menu)
if data.dept_ids:
depts = await DeptDal(db=self.db).get_datas(limit=0, id=("in", data.dept_ids), v_return_objs=True)
for dept in depts:
obj.depts.add(dept)
await self.flush(obj)
return await self.out_dict(obj, v_options, v_return_obj, v_schema)
@ -420,7 +463,7 @@ class RoleDal(DalBase):
:param v_schema:
:return:
"""
obj = await self.get_data(data_id, v_options=[joinedload(self.model.menus)])
obj = await self.get_data(data_id, v_options=[joinedload(self.model.menus), joinedload(self.model.depts)])
obj_dict = jsonable_encoder(data)
for key, value in obj_dict.items():
if key == "menu_ids":
@ -431,6 +474,14 @@ class RoleDal(DalBase):
for menu in menus:
obj.menus.add(menu)
continue
elif key == "dept_ids":
if value:
depts = await DeptDal(db=self.db).get_datas(limit=0, id=("in", value), v_return_objs=True)
if obj.depts:
obj.depts.clear()
for dept in depts:
obj.depts.add(dept)
continue
setattr(obj, key, value)
await self.flush(obj)
return await self.out_dict(obj, None, v_return_obj, v_schema)
@ -559,7 +610,7 @@ class MenuDal(DalBase):
"""
data = []
for root in nodes:
router = schemas.TreeListOut.model_validate(root)
router = schemas.MenuTreeListOut.model_validate(root)
if root.menu_type == "0" or root.menu_type == "1":
sons = filter(lambda i: i.parent_id == root.id, menus)
router.children = self.generate_tree_list(menus, sons)
@ -611,3 +662,103 @@ class MenuDal(DalBase):
raise CustomException("无法删除存在角色关联的菜单", code=400)
await super(MenuDal, self).delete_datas(ids, v_soft, **kwargs)
class DeptDal(DalBase):
def __init__(self, db: AsyncSession):
super(DeptDal, self).__init__()
self.db = db
self.model = models.VadminDept
self.schema = schemas.DeptSimpleOut
async def get_tree_list(self, mode: int) -> list:
"""
1获取部门树列表
2获取部门树选择项添加/修改部门时使用
3获取部门树列表用户添加部门权限时使用
:param mode:
:return:
"""
if mode == 3:
sql = select(self.model).where(self.model.disabled == 0, self.model.is_delete == false())
else:
sql = select(self.model).where(self.model.is_delete == false())
queryset = await self.db.scalars(sql)
datas = list(queryset.all())
roots = filter(lambda i: not i.parent_id, datas)
if mode == 1:
menus = self.generate_tree_list(datas, roots)
elif mode == 2 or mode == 3:
menus = self.generate_tree_options(datas, roots)
else:
raise CustomException("获取部门失败,无可用选项", code=400)
return self.dept_order(menus)
def generate_tree_list(self, depts: list[models.VadminDept], nodes: filter) -> list:
"""
生成部门树列表
:param depts: 总部门列表
:param nodes: 每层节点部门列表
:return:
"""
data = []
for root in nodes:
router = schemas.DeptTreeListOut.model_validate(root)
sons = filter(lambda i: i.parent_id == root.id, depts)
router.children = self.generate_tree_list(depts, sons)
data.append(router.model_dump())
return data
def generate_tree_options(self, depts: list[models.VadminDept], nodes: filter) -> list:
"""
生成部门树选择项
:param depts: 总部门列表
:param nodes: 每层节点部门列表
:return:
"""
data = []
for root in nodes:
router = {"value": root.id, "label": root.name, "order": root.order}
sons = filter(lambda i: i.parent_id == root.id, depts)
router["children"] = self.generate_tree_options(depts, sons)
data.append(router)
return data
@classmethod
def dept_order(cls, datas: list, order: str = "order", children: str = "children") -> list:
"""
部门排序
:param datas:
:param order:
:param children:
:return:
"""
result = sorted(datas, key=lambda dept: dept[order])
for item in result:
if item[children]:
item[children] = sorted(item[children], key=lambda dept: dept[order])
return result
class TestDal(DalBase):
def __init__(self, db: AsyncSession):
super(TestDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut)
async def test(self):
# print("-----------------------开始------------------------")
options = [joinedload(self.model.roles)]
v_join = [[self.model.roles]]
v_where = [self.model.id == 1, models.VadminRole.id == 1]
v_start_sql = select(self.model)
result, count = await self.get_datas(
v_start_sql=v_start_sql,
v_join=v_join,
v_options=options,
v_where=v_where,
v_return_count=True
)
if result:
print(result)
print(count)
# print("-----------------------结束------------------------")

View File

@ -7,7 +7,8 @@
# @desc : 简要说明
from .m2m import vadmin_auth_user_roles, vadmin_auth_role_menus
from .m2m import vadmin_auth_user_roles, vadmin_auth_role_menus, vadmin_auth_user_depts, vadmin_auth_role_depts
from .menu import VadminMenu
from .role import VadminRole
from .user import VadminUser
from .dept import VadminDept

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/10/23 13:41
# @File : dept.py
# @IDE : PyCharm
# @desc : 部门模型
from sqlalchemy.orm import Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer, ForeignKey
class VadminDept(BaseModel):
__tablename__ = "vadmin_auth_dept"
__table_args__ = ({'comment': '部门表'})
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门名称")
dept_key: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门标识")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
order: Mapped[int | None] = mapped_column(Integer, comment="显示排序")
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
owner: Mapped[str | None] = mapped_column(String(255), comment="负责人")
phone: Mapped[str | None] = mapped_column(String(255), comment="联系电话")
email: Mapped[str | None] = mapped_column(String(255), comment="邮箱")
parent_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("vadmin_auth_dept.id", ondelete='CASCADE'),
comment="上级部门"
)

View File

@ -25,3 +25,17 @@ vadmin_auth_role_menus = Table(
Column("menu_id", Integer, ForeignKey("vadmin_auth_menu.id", ondelete="CASCADE")),
)
vadmin_auth_user_depts = Table(
"vadmin_auth_user_depts",
Base.metadata,
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
)
vadmin_auth_role_depts = Table(
"vadmin_auth_role_depts",
Base.metadata,
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
)

View File

@ -10,18 +10,21 @@ from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from sqlalchemy import String, Boolean, Integer
from .menu import VadminMenu
from .m2m import vadmin_auth_role_menus
from .dept import VadminDept
from .m2m import vadmin_auth_role_menus, vadmin_auth_role_depts
class VadminRole(BaseModel):
__tablename__ = "vadmin_auth_role"
__table_args__ = ({'comment': '角色表'})
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="名称")
role_key: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="权限字符")
name: Mapped[str] = mapped_column(String(50), index=True, comment="名称")
role_key: Mapped[str] = mapped_column(String(50), index=True, comment="权限字符")
data_range: Mapped[int] = mapped_column(Integer, default=4, comment="数据权限范围")
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
order: Mapped[int | None] = mapped_column(Integer, comment="排序")
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
is_admin: Mapped[bool] = mapped_column(Boolean, comment="是否为超级角色", default=False)
menus: Mapped[set[VadminMenu]] = relationship(secondary=vadmin_auth_role_menus)
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_role_depts)

View File

@ -12,7 +12,8 @@ from db.db_base import BaseModel
from sqlalchemy import String, Boolean, DateTime
from passlib.context import CryptContext
from .role import VadminRole
from .m2m import vadmin_auth_user_roles
from .dept import VadminDept
from .m2m import vadmin_auth_user_roles, vadmin_auth_user_depts
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
@ -41,6 +42,7 @@ class VadminUser(BaseModel):
is_wx_server_openid: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否已有服务端微信平台openid")
roles: Mapped[set[VadminRole]] = relationship(secondary=vadmin_auth_user_roles)
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_user_depts)
@staticmethod
def get_password_hash(password: str) -> str:

View File

@ -1,2 +1,3 @@
from .user import UserParams
from .role import RoleParams
from .dept import DeptParams

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/12/18 10:19
# @File : dept.py
# @IDE : PyCharm
# @desc : 查询参数-类依赖项
"""
类依赖项-官方文档https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
"""
from fastapi import Depends, Query
from core.dependencies import Paging, QueryParams
class DeptParams(QueryParams):
"""
列表分页
"""
def __init__(
self,
name: str | None = Query(None, title="部门名称"),
dept_key: str | None = Query(None, title="部门标识"),
disabled: bool | None = Query(None, title="是否禁用"),
params: Paging = Depends()
):
super().__init__(params)
self.name = ("like", name)
self.dept_key = ("like", dept_key)
self.disabled = disabled

View File

@ -1,3 +1,4 @@
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd, UserUpdateBaseInfo
from .role import Role, RoleOut, RoleIn, RoleOptionsOut, RoleSimpleOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, TreeListOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, MenuTreeListOut
from .dept import Dept, DeptSimpleOut, DeptTreeListOut

View File

@ -0,0 +1,40 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Create Time : 2023/10/25 12:19
# @File : dept.py
# @IDE : PyCharm
# @desc : pydantic 模型,用于数据库序列化操作
from pydantic import BaseModel, ConfigDict, Field
from core.data_types import DatetimeStr
from .menu import MenuSimpleOut
class Dept(BaseModel):
name: str
dept_key: str
disabled: bool = False
order: int | None = None
desc: str | None = None
owner: str | None = None
phone: str | None = None
email: str | None = None
parent_id: int | None = None
class DeptSimpleOut(Dept):
model_config = ConfigDict(from_attributes=True)
id: int
create_datetime: DatetimeStr
update_datetime: DatetimeStr
class DeptTreeListOut(DeptSimpleOut):
model_config = ConfigDict(from_attributes=True)
children: list[dict] = []

View File

@ -60,7 +60,7 @@ class RouterOut(BaseModel):
children: list[dict] = []
class TreeListOut(MenuSimpleOut):
class MenuTreeListOut(MenuSimpleOut):
model_config = ConfigDict(from_attributes=True)
children: list[dict] = []

View File

@ -10,6 +10,7 @@
from pydantic import BaseModel, ConfigDict, Field
from core.data_types import DatetimeStr
from .menu import MenuSimpleOut
from .dept import DeptSimpleOut
class Role(BaseModel):
@ -17,6 +18,7 @@ class Role(BaseModel):
disabled: bool = False
order: int | None = None
desc: str | None = None
data_range: int = 4
role_key: str
is_admin: bool = False
@ -33,10 +35,12 @@ class RoleOut(RoleSimpleOut):
model_config = ConfigDict(from_attributes=True)
menus: list[MenuSimpleOut] = []
depts: list[DeptSimpleOut] = []
class RoleIn(Role):
menu_ids: list[int] = []
dept_ids: list[int] = []
class RoleOptionsOut(BaseModel):

View File

@ -9,9 +9,9 @@
from pydantic import BaseModel, ConfigDict, field_validator
from pydantic_core.core_schema import FieldValidationInfo
from core.data_types import Telephone, DatetimeStr, Email
from .role import RoleSimpleOut
from .dept import DeptSimpleOut
class User(BaseModel):
@ -31,6 +31,7 @@ class UserIn(User):
创建用户
"""
role_ids: list[int] = []
dept_ids: list[int] = []
password: str | None = ""
@ -58,6 +59,7 @@ class UserUpdate(User):
is_staff: bool | None = False
gender: str | None = "0"
role_ids: list[int] = []
dept_ids: list[int] = []
class UserSimpleOut(User):
@ -76,6 +78,7 @@ class UserOut(UserSimpleOut):
model_config = ConfigDict(from_attributes=True)
roles: list[RoleSimpleOut] = []
depts: list[DeptSimpleOut] = []
class ResetPwd(BaseModel):

View File

@ -40,7 +40,7 @@ class OpenAuth(AuthValidation):
try:
telephone = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
return await self.validate_user(request, user, db)
return await self.validate_user(request, user, db, is_all=True)
except CustomException:
return Auth(db=db)
@ -65,7 +65,7 @@ class AllUserAuth(AuthValidation):
return Auth(db=db)
telephone = self.validate_token(request, token)
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
return await self.validate_user(request, user, db)
return await self.validate_user(request, user, db, is_all=True)
class FullAdminAuth(AuthValidation):
@ -94,9 +94,9 @@ class FullAdminAuth(AuthValidation):
if not settings.OAUTH_ENABLE:
return Auth(db=db)
telephone = self.validate_token(request, token)
options = [joinedload(VadminUser.roles).subqueryload(VadminRole.menus)]
options = [joinedload(VadminUser.roles).subqueryload(VadminRole.menus), joinedload(VadminUser.depts)]
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True, v_options=options, is_staff=True)
result = await self.validate_user(request, user, db)
result = await self.validate_user(request, user, db, is_all=False)
permissions = self.get_user_permissions(user)
if permissions != {'*.*.*'} and self.permissions:
if not (self.permissions & permissions):

View File

@ -14,11 +14,14 @@ from apps.vadmin.auth.models import VadminUser
from core.exception import CustomException
from utils import status
from datetime import timedelta, datetime
from apps.vadmin.auth.crud import UserDal
class Auth(BaseModel):
user: VadminUser = None
db: AsyncSession
data_range: int | None = None
dept_ids: list | None = []
class Config:
# 接收任意类型
@ -80,9 +83,14 @@ class AuthValidation:
return telephone
@classmethod
async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession) -> Auth:
async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession, is_all: bool = True) -> Auth:
"""
验证用户信息
:param request:
:param user:
:param db:
:param is_all: 是否所有人访问不加权限
:return:
"""
if user is None:
raise CustomException(msg="未认证,请您重新登陆", code=cls.error_code, status_code=cls.error_code)
@ -95,12 +103,17 @@ class AuthValidation:
request.scope["body"] = await request.body()
except RuntimeError:
request.scope["body"] = "获取失败"
return Auth(user=user, db=db)
if is_all:
return Auth(user=user, db=db)
data_range, dept_ids = await cls.get_user_data_range(user, db)
return Auth(user=user, db=db, data_range=data_range, dept_ids=dept_ids)
@classmethod
def get_user_permissions(cls, user: VadminUser) -> set:
"""
获取员工用户所有权限列表
:param user: 用户实例
:return:
"""
if user.is_admin():
return {'*.*.*'}
@ -110,3 +123,36 @@ class AuthValidation:
if menu.perms and not menu.disabled:
permissions.add(menu.perms)
return permissions
@classmethod
async def get_user_data_range(cls, user: VadminUser, db: AsyncSession) -> tuple:
"""
获取用户数据范围
0 仅本人数据权限 create_user_id 查询
1 本部门数据权限 部门 id 左连接查询
2 本部门及以下数据权限 部门 id 左连接查询
3 自定义数据权限 部门 id 左连接查询
4 全部数据权限
:param user:
:param db:
:return:
"""
if user.is_admin():
return 4, ["*"]
data_range = max([i.data_range for i in user.roles])
dept_ids = set()
if data_range == 0:
pass
elif data_range == 1:
for dept in user.depts:
dept_ids.add(dept.id)
elif data_range == 2:
# 递归获取部门列表
dept_ids = await UserDal(db).recursion_get_dept_ids(user)
elif data_range == 3:
for role_obj in user.roles:
for dept in role_obj.depts:
dept_ids.add(dept.id)
elif data_range == 4:
dept_ids.add("*")
return data_range, list(dept_ids)

View File

@ -13,13 +13,22 @@ from core.database import redis_getter
from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models
from core.dependencies import IdList
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
from apps.vadmin.auth.utils.validation.auth import Auth
from .params import UserParams, RoleParams
from .params import UserParams, RoleParams, DeptParams
app = APIRouter()
###########################################################
# 接口测试
###########################################################
@app.get("/test", summary="接口测试")
async def test(auth: Auth = Depends(FullAdminAuth())):
print(auth)
return SuccessResponse()
###########################################################
# 用户管理
###########################################################
@ -29,7 +38,7 @@ async def get_users(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.list"]))
):
model = models.VadminUser
options = [joinedload(model.roles)]
options = [joinedload(model.roles), joinedload(model.depts)]
schema = schemas.UserOut
datas, count = await crud.UserDal(auth.db).get_datas(
**params.dict(),
@ -70,7 +79,7 @@ async def get_user(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.view", "auth.user.update"]))
):
model = models.VadminUser
options = [joinedload(model.roles)]
options = [joinedload(model.roles), joinedload(model.depts)]
schema = schemas.UserOut
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
@ -189,7 +198,7 @@ async def get_role(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.view", "auth.role.update"]))
):
model = models.VadminRole
options = [joinedload(model.menus)]
options = [joinedload(model.menus), joinedload(model.depts)]
schema = schemas.RoleOut
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
@ -254,3 +263,46 @@ async def get_role_menu_tree(
tree_data = await crud.MenuDal(auth.db).get_tree_list(mode=3)
role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id)
return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": tree_data})
###########################################################
# 部门管理
###########################################################
@app.get("/depts", summary="获取部门列表")
async def get_depts(
params: DeptParams = Depends(),
auth: Auth = Depends(FullAdminAuth())
):
datas = await crud.DeptDal(auth.db).get_tree_list(1)
return SuccessResponse(datas)
@app.get("/dept/tree/options", summary="获取部门树选择项,添加/修改部门时使用")
async def get_dept_options(auth: Auth = Depends(FullAdminAuth())):
datas = await crud.DeptDal(auth.db).get_tree_list(mode=2)
return SuccessResponse(datas)
@app.get("/dept/user/tree/options", summary="获取部门树选择项,添加/修改用户时使用")
async def get_dept_treeselect(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.DeptDal(auth.db).get_tree_list(mode=3))
@app.post("/depts", summary="创建部门信息")
async def create_dept(data: schemas.Dept, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.DeptDal(auth.db).create_data(data=data))
@app.delete("/depts", summary="批量删除部门", description="硬删除, 如果存在用户关联则无法删除")
async def delete_depts(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
await crud.DeptDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功")
@app.put("/depts/{data_id}", summary="更新部门信息")
async def put_dept(
data_id: int,
data: schemas.Dept,
auth: Auth = Depends(FullAdminAuth())
):
return SuccessResponse(await crud.DeptDal(auth.db).put_data(data_id, data))