mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-18 03:39:58 +08:00
feat: 增加部门管理
This commit is contained in:
parent
53282f9453
commit
82933cfa22
@ -49,6 +49,9 @@ const propOverrides = {
|
|||||||
labelPlacement: 'left',
|
labelPlacement: 'left',
|
||||||
preset: 'card',
|
preset: 'card',
|
||||||
},
|
},
|
||||||
|
ProDataTable: {
|
||||||
|
paginateSinglePage: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -5,3 +5,4 @@ export * from './system/user'
|
|||||||
export * from './system/menu'
|
export * from './system/menu'
|
||||||
export * from './system/role'
|
export * from './system/role'
|
||||||
export * from './system/dict'
|
export * from './system/dict'
|
||||||
|
export * from './system/dept'
|
||||||
|
51
src/api/system/dept.ts
Normal file
51
src/api/system/dept.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { request } from '../../utils/alova'
|
||||||
|
|
||||||
|
export type SearchQuery = Partial<Pick<Entity.Dept, 'deptName' | 'status'>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建部门
|
||||||
|
* POST /dept
|
||||||
|
*/
|
||||||
|
export function createDept(data: Partial<Entity.Dept>) {
|
||||||
|
return request.Post<Api.Response<Entity.Dept>>('/dept', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询部门
|
||||||
|
* GET /dept
|
||||||
|
*/
|
||||||
|
export function getDeptList(params?: SearchQuery) {
|
||||||
|
return request.Get<Api.Response<Entity.Dept[]>>('/dept', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部门下拉选项
|
||||||
|
* GET /dept/options
|
||||||
|
*/
|
||||||
|
export function getDeptOptions() {
|
||||||
|
return request.Get<Api.Response<Entity.Dept[]>>('/dept/options')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询部门详情
|
||||||
|
* GET /dept/{id}
|
||||||
|
*/
|
||||||
|
export function getDeptById(id: number) {
|
||||||
|
return request.Get<Api.Response<Entity.Dept>>(`/dept/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新部门信息
|
||||||
|
* PUT /dept/{id}
|
||||||
|
*/
|
||||||
|
export function updateDept(id: number, data: Partial<Entity.Dept>) {
|
||||||
|
return request.Put<Api.Response<Entity.Dept>>(`/dept/${id}`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除部门
|
||||||
|
* DELETE /dept/{id}
|
||||||
|
*/
|
||||||
|
export function deleteDept(id: number) {
|
||||||
|
return request.Delete<Api.Response<boolean>>(`/dept/${id}`)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { request } from '../../utils/alova'
|
import { request } from '../../utils/alova'
|
||||||
|
|
||||||
|
export type SearchQuery = Partial<Pick<Entity.Menu, 'title' | 'status'>>
|
||||||
/**
|
/**
|
||||||
* 创建菜单
|
* 创建菜单
|
||||||
* POST /menu
|
* POST /menu
|
||||||
@ -12,8 +13,8 @@ export function createMenu(data: Partial<Entity.Menu>) {
|
|||||||
* 分页查询菜单
|
* 分页查询菜单
|
||||||
* GET /menu
|
* GET /menu
|
||||||
*/
|
*/
|
||||||
export function getMenuList() {
|
export function getMenuList(params?: SearchQuery) {
|
||||||
return request.Get<Api.Response<Entity.Menu[]>>('/menu')
|
return request.Get<Api.Response<Entity.Menu[]>>('/menu', { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,7 @@ export const i18n = createI18n({
|
|||||||
enUS,
|
enUS,
|
||||||
},
|
},
|
||||||
// 缺失国际化键警告
|
// 缺失国际化键警告
|
||||||
// missingWarn: false,
|
missingWarn: false,
|
||||||
|
|
||||||
// 缺失回退内容警告
|
// 缺失回退内容警告
|
||||||
fallbackWarn: false,
|
fallbackWarn: false,
|
||||||
|
@ -331,7 +331,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'setting',
|
name: 'setting',
|
||||||
path: '/setting',
|
path: '/system',
|
||||||
title: '系统设置',
|
title: '系统设置',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:setting',
|
icon: 'icon-park-outline:setting',
|
||||||
@ -342,44 +342,54 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'accountSetting',
|
name: 'accountSetting',
|
||||||
path: '/setting/user',
|
path: '/system/user',
|
||||||
title: '用户设置',
|
title: '用户设置',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:every-user',
|
icon: 'icon-park-outline:every-user',
|
||||||
component: '/setting/user/index.vue',
|
component: '/system/user/index.vue',
|
||||||
id: 701,
|
id: 701,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'roleSetting',
|
name: 'roleSetting',
|
||||||
path: '/setting/role',
|
path: '/system/role',
|
||||||
title: '角色设置',
|
title: '角色设置',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:every-user',
|
icon: 'icon-park-outline:every-user',
|
||||||
component: '/setting/role/index.vue',
|
component: '/system/role/index.vue',
|
||||||
id: 702,
|
id: 702,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dictionarySetting',
|
name: 'dictionarySetting',
|
||||||
path: '/setting/dict',
|
path: '/system/dict',
|
||||||
title: '字典设置',
|
title: '字典设置',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:book-one',
|
icon: 'icon-park-outline:book-one',
|
||||||
component: '/setting/dict/index.vue',
|
component: '/system/dict/index.vue',
|
||||||
id: 703,
|
id: 703,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'menuSetting',
|
name: 'menuSetting',
|
||||||
path: '/setting/menu',
|
path: '/system/menu',
|
||||||
title: '菜单设置',
|
title: '菜单设置',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:application-menu',
|
icon: 'icon-park-outline:application-menu',
|
||||||
component: '/setting/menu/index.vue',
|
component: '/system/menu/index.vue',
|
||||||
id: 704,
|
id: 704,
|
||||||
parentId: 7,
|
parentId: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'deptSetting',
|
||||||
|
path: '/system/dept',
|
||||||
|
title: '部门管理',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:application-menu',
|
||||||
|
component: '/system/dept/index.vue',
|
||||||
|
id: 705,
|
||||||
|
parentId: 7,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'about',
|
name: 'about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
|
44
src/typings/entities/dept.d.ts
vendored
Normal file
44
src/typings/entities/dept.d.ts
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/// <reference path="../global.d.ts"/>
|
||||||
|
|
||||||
|
/* 数据库表字段 */
|
||||||
|
namespace Entity {
|
||||||
|
interface Dept {
|
||||||
|
/**
|
||||||
|
* 祖级列表
|
||||||
|
*/
|
||||||
|
ancestors?: string
|
||||||
|
/**
|
||||||
|
* 部门名称
|
||||||
|
*/
|
||||||
|
deptName: string
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
email?: string
|
||||||
|
/**
|
||||||
|
* 负责人
|
||||||
|
*/
|
||||||
|
leader?: string
|
||||||
|
/**
|
||||||
|
* 父部门ID
|
||||||
|
*/
|
||||||
|
parentId?: number
|
||||||
|
/**
|
||||||
|
* 联系电话
|
||||||
|
*/
|
||||||
|
phone?: string
|
||||||
|
/**
|
||||||
|
* 备注信息
|
||||||
|
*/
|
||||||
|
remark?: string
|
||||||
|
/**
|
||||||
|
* 显示顺序
|
||||||
|
*/
|
||||||
|
sort?: number
|
||||||
|
/**
|
||||||
|
* 部门状态
|
||||||
|
*/
|
||||||
|
status?: number
|
||||||
|
[property: string]: any
|
||||||
|
}
|
||||||
|
}
|
2
src/typings/entities/dict.d.ts
vendored
2
src/typings/entities/dict.d.ts
vendored
@ -61,7 +61,7 @@ namespace Entity {
|
|||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
status?: number
|
status: number
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
122
src/views/system/dept/columns.tsx
Normal file
122
src/views/system/dept/columns.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||||
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
|
import { renderProCopyableText, renderProDateText } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
interface DeptColumnActions {
|
||||||
|
onEdit: (row: Entity.Dept) => void
|
||||||
|
onDelete: (id: number) => void
|
||||||
|
onAdd: (row: Entity.Dept) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deptSearchColumns: ProSearchFormColumns<Entity.Dept> = [
|
||||||
|
{
|
||||||
|
title: '部门名称',
|
||||||
|
path: 'deptName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
path: 'status',
|
||||||
|
field: 'select',
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 0 },
|
||||||
|
{ label: '停用', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function createDeptColumns(actions: DeptColumnActions): DataTableColumns<Entity.Dept> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'deptName',
|
||||||
|
title: '部门名称',
|
||||||
|
align: 'left',
|
||||||
|
minWidth: 150,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'sort',
|
||||||
|
title: '排序',
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
title: '状态',
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
render: (row: Entity.Dept) => (
|
||||||
|
<NTag type={row.status === 0 ? 'success' : 'error'} size="small">
|
||||||
|
{row.status === 0 ? '正常' : '停用'}
|
||||||
|
</NTag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'leader',
|
||||||
|
title: '负责人',
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
render: (row: Entity.Dept) => row.leader || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phone',
|
||||||
|
title: '联系电话',
|
||||||
|
align: 'center',
|
||||||
|
render: (row: Entity.Dept) => row.phone ? renderProCopyableText(row.phone) : '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'email',
|
||||||
|
title: '邮箱',
|
||||||
|
align: 'center',
|
||||||
|
render: (row: Entity.Dept) => row.email ? renderProCopyableText(row.email) : '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
align: 'center',
|
||||||
|
width: 180,
|
||||||
|
render: (row: Entity.Dept) => {
|
||||||
|
return row.createTime ? renderProDateText(row.createTime) : '-'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
align: 'center',
|
||||||
|
width: 200,
|
||||||
|
render: (row: Entity.Dept) => (
|
||||||
|
<NSpace justify="center">
|
||||||
|
<NButton
|
||||||
|
type="info"
|
||||||
|
text
|
||||||
|
onClick={() => actions.onAdd(row)}
|
||||||
|
>
|
||||||
|
新增
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
onClick={() => actions.onEdit(row)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</NButton>
|
||||||
|
<NPopconfirm
|
||||||
|
onPositiveClick={() => actions.onDelete(row.id!)}
|
||||||
|
v-slots={{
|
||||||
|
trigger: () => (
|
||||||
|
<NButton type="error" text>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
),
|
||||||
|
default: () => `确定删除部门"${row.deptName}"吗?`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
182
src/views/system/dept/components/DeptModal.vue
Normal file
182
src/views/system/dept/components/DeptModal.vue
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBoolean } from '@/hooks'
|
||||||
|
import { createDept, getDeptById, getDeptOptions, updateDept } from '@/api'
|
||||||
|
import { createProModalForm } from 'pro-naive-ui'
|
||||||
|
import { Regex } from '@/constants'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modalName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
modalName = '',
|
||||||
|
} = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
|
|
||||||
|
// 部门选项
|
||||||
|
const deptOptions = ref<Entity.Dept[]>([])
|
||||||
|
|
||||||
|
const modalForm = createProModalForm<Partial<Entity.Dept>>({
|
||||||
|
omitEmptyString: false,
|
||||||
|
initialValues: {
|
||||||
|
sort: 0,
|
||||||
|
status: 0,
|
||||||
|
},
|
||||||
|
onSubmit: submitModal,
|
||||||
|
})
|
||||||
|
|
||||||
|
type ModalType = 'add' | 'edit'
|
||||||
|
const modalType = shallowRef<ModalType>('add')
|
||||||
|
const modalTitle = computed(() => {
|
||||||
|
const titleMap: Record<ModalType, string> = {
|
||||||
|
add: '添加',
|
||||||
|
edit: '编辑',
|
||||||
|
}
|
||||||
|
return `${titleMap[modalType.value]}${modalName}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载部门选项
|
||||||
|
async function loadDeptOptions() {
|
||||||
|
try {
|
||||||
|
const { data } = await getDeptOptions()
|
||||||
|
deptOptions.value = data
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
console.error('加载部门选项失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Dept>) {
|
||||||
|
modalType.value = type
|
||||||
|
modalForm.open()
|
||||||
|
loadDeptOptions()
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
if (data?.id) {
|
||||||
|
modalForm.values.value.parentId = data.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
if (!data?.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: dept } = await getDeptById(data.id)
|
||||||
|
modalForm.values.value = dept
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
window.$message.error('获取部门信息失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await handlers[type]()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitModal(fieldValues: Partial<Entity.Dept>) {
|
||||||
|
const handlers = {
|
||||||
|
async add() {
|
||||||
|
try {
|
||||||
|
await createDept(fieldValues)
|
||||||
|
window.$message.success('部门创建成功')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('创建部门失败', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async edit() {
|
||||||
|
try {
|
||||||
|
await updateDept(modalForm.values.value.id!, fieldValues)
|
||||||
|
window.$message.success('部门更新成功')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('更新部门失败', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
startLoading()
|
||||||
|
const success = await handlers[modalType.value]()
|
||||||
|
endLoading()
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
emit('success')
|
||||||
|
modalForm.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pro-modal-form
|
||||||
|
:title="modalTitle"
|
||||||
|
:form="modalForm"
|
||||||
|
:loading="submitLoading"
|
||||||
|
width="700px"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<pro-input
|
||||||
|
required
|
||||||
|
title="部门名称"
|
||||||
|
path="deptName"
|
||||||
|
/>
|
||||||
|
<pro-tree-select
|
||||||
|
title="上级部门"
|
||||||
|
path="parentId"
|
||||||
|
:field-props="{
|
||||||
|
options: deptOptions,
|
||||||
|
clearable: true,
|
||||||
|
keyField: 'value',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<pro-digit
|
||||||
|
title="显示排序"
|
||||||
|
path="sort"
|
||||||
|
:field-props="{ min: 0, max: 999 }"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="负责人"
|
||||||
|
path="leader"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="联系电话"
|
||||||
|
path="phone"
|
||||||
|
:field-props="{ maxlength: 11 }"
|
||||||
|
/>
|
||||||
|
<pro-input
|
||||||
|
title="邮箱"
|
||||||
|
path="email"
|
||||||
|
:rule="[
|
||||||
|
{
|
||||||
|
pattern: new RegExp(Regex.Email),
|
||||||
|
message: '请输入正确的邮箱格式',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<pro-switch
|
||||||
|
title="部门状态"
|
||||||
|
path="status"
|
||||||
|
:field-props="{ checkedValue: 0, uncheckedValue: 1 }"
|
||||||
|
/>
|
||||||
|
<div />
|
||||||
|
<pro-textarea
|
||||||
|
class="col-span-2"
|
||||||
|
title="备注"
|
||||||
|
path="remark"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</pro-modal-form>
|
||||||
|
</template>
|
103
src/views/system/dept/index.vue
Normal file
103
src/views/system/dept/index.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBoolean } from '@/hooks'
|
||||||
|
import { deleteDept, getDeptList } from '@/api'
|
||||||
|
import type { SearchQuery } from '@/api'
|
||||||
|
import { createDeptColumns, deptSearchColumns } from './columns'
|
||||||
|
import DeptModal from './components/DeptModal.vue'
|
||||||
|
import arrayToTree from 'array-to-tree'
|
||||||
|
import { createProSearchForm } from 'pro-naive-ui'
|
||||||
|
|
||||||
|
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
|
|
||||||
|
const deptModalRef = ref<InstanceType<typeof DeptModal>>()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = createProSearchForm<SearchQuery>({
|
||||||
|
initialValues: {},
|
||||||
|
onSubmit: getAllDepts,
|
||||||
|
onReset: getAllDepts,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 删除部门
|
||||||
|
async function deleteDeptItem(id: number) {
|
||||||
|
try {
|
||||||
|
await deleteDept(id)
|
||||||
|
window.$message.success('部门删除成功')
|
||||||
|
searchForm.submit()
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
window.$message.error('部门删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门表格列配置
|
||||||
|
const deptColumns = createDeptColumns({
|
||||||
|
onAdd: (row: Entity.Dept) => deptModalRef.value?.openModal('add', row),
|
||||||
|
onEdit: (row: Entity.Dept) => deptModalRef.value?.openModal('edit', row),
|
||||||
|
onDelete: deleteDeptItem,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableData = ref<Entity.Dept[]>([])
|
||||||
|
|
||||||
|
// 获取所有部门数据并构建树形结构
|
||||||
|
async function getAllDepts(params?: SearchQuery) {
|
||||||
|
startLoading()
|
||||||
|
try {
|
||||||
|
const { data } = await getDeptList(params)
|
||||||
|
|
||||||
|
tableData.value = arrayToTree(data, {
|
||||||
|
parentProperty: 'parentId',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
window.$message.error('获取部门列表失败')
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
endLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
searchForm.submit()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full">
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<n-card class="mb-4">
|
||||||
|
<pro-search-form
|
||||||
|
:form="searchForm"
|
||||||
|
:columns="deptSearchColumns"
|
||||||
|
:collapse-button-props="false"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<pro-data-table
|
||||||
|
row-key="id"
|
||||||
|
:columns="deptColumns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
@click="deptModalRef?.openModal('add')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-park-outline-plus />
|
||||||
|
</template>
|
||||||
|
新增部门
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</pro-data-table>
|
||||||
|
|
||||||
|
<!-- 部门弹窗 -->
|
||||||
|
<DeptModal
|
||||||
|
ref="deptModalRef"
|
||||||
|
modal-name="部门"
|
||||||
|
@success="searchForm.submit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -1,7 +1,7 @@
|
|||||||
import type { DataTableColumns } from 'naive-ui'
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
|
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||||
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
import { renderProCopyableText, renderProTags } from 'pro-naive-ui'
|
import { renderProCopyableText } from 'pro-naive-ui'
|
||||||
|
|
||||||
interface DictDataColumnActions {
|
interface DictDataColumnActions {
|
||||||
onEdit: (row: Entity.DictData) => void
|
onEdit: (row: Entity.DictData) => void
|
||||||
@ -13,18 +13,14 @@ export const dictDataSearchColumns: ProSearchFormColumns<Entity.DictData> = [
|
|||||||
title: '数据名称',
|
title: '数据名称',
|
||||||
path: 'name',
|
path: 'name',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '数据键值',
|
|
||||||
path: 'value',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
path: 'status',
|
path: 'status',
|
||||||
field: 'select',
|
field: 'select',
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
options: [
|
options: [
|
||||||
{ label: '正常', value: 1 },
|
{ label: '正常', value: 0 },
|
||||||
{ label: '停用', value: 0 },
|
{ label: '停用', value: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -53,7 +49,7 @@ export function createDictDataColumns(actions: DictDataColumnActions): DataTable
|
|||||||
key: 'dictType',
|
key: 'dictType',
|
||||||
title: '字典类型',
|
title: '字典类型',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: row => renderProTags(row.dictType),
|
render: row => renderProCopyableText(row.dictType),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sort',
|
key: 'sort',
|
||||||
@ -61,11 +57,27 @@ export function createDictDataColumns(actions: DictDataColumnActions): DataTable
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
title: '状态',
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
render: (row: Entity.DictData) => (
|
||||||
|
<NTag type={row.status === 0 ? 'success' : 'error'} bordered={false}>
|
||||||
|
{row.status === 0 ? '正常' : '停用'}
|
||||||
|
</NTag>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'remark',
|
key: 'remark',
|
||||||
title: '备注',
|
title: '备注',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'updateTime',
|
||||||
|
title: '更新时间',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
title: '操作',
|
title: '操作',
|
@ -130,7 +130,7 @@ defineExpose({
|
|||||||
<pro-switch
|
<pro-switch
|
||||||
title="状态"
|
title="状态"
|
||||||
path="status"
|
path="status"
|
||||||
:field-props="{ checkedValue: 1, uncheckedValue: 0 }"
|
:field-props="{ checkedValue: 0, uncheckedValue: 1 }"
|
||||||
/>
|
/>
|
||||||
<pro-textarea
|
<pro-textarea
|
||||||
class="col-span-2"
|
class="col-span-2"
|
@ -2,7 +2,7 @@
|
|||||||
import type { DataTableColumns } from 'naive-ui'
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
|
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
|
||||||
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
import { createProSearchForm, renderProCopyableText, useNDataTable } from 'pro-naive-ui'
|
import { createProSearchForm, useNDataTable } from 'pro-naive-ui'
|
||||||
import { deleteDictType, getDictTypeList } from '@/api'
|
import { deleteDictType, getDictTypeList } from '@/api'
|
||||||
import DictTypeModal from './DictTypeModal.vue'
|
import DictTypeModal from './DictTypeModal.vue'
|
||||||
|
|
||||||
@ -22,10 +22,6 @@ const dictTypeSearchColumns: ProSearchFormColumns<Entity.DictType> = [
|
|||||||
title: '字典名称',
|
title: '字典名称',
|
||||||
path: 'name',
|
path: 'name',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '字典类型',
|
|
||||||
path: 'type',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// 使用 useNDataTable
|
// 使用 useNDataTable
|
||||||
@ -80,11 +76,6 @@ const columns: DataTableColumns<Entity.DictType> = [
|
|||||||
tooltip: true,
|
tooltip: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'type',
|
|
||||||
title: '字典类型',
|
|
||||||
render: row => renderProCopyableText(row.type),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@ -131,7 +122,7 @@ const columns: DataTableColumns<Entity.DictType> = [
|
|||||||
:columns="dictTypeSearchColumns"
|
:columns="dictTypeSearchColumns"
|
||||||
:collapse-button-props="false"
|
:collapse-button-props="false"
|
||||||
v-bind="dictTypeSearchProps"
|
v-bind="dictTypeSearchProps"
|
||||||
:cols="3"
|
:cols="2"
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
@ -11,7 +11,6 @@ const dictTypeListRef = ref<InstanceType<typeof DictTypeList>>()
|
|||||||
const dictDataSearchForm = createProSearchForm<Partial<Entity.DictData>>({
|
const dictDataSearchForm = createProSearchForm<Partial<Entity.DictData>>({
|
||||||
initialValues: {},
|
initialValues: {},
|
||||||
})
|
})
|
||||||
const { values } = dictDataSearchForm
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
table: {
|
table: {
|
||||||
@ -79,7 +78,7 @@ async function getDictDataPage({ current, pageSize }: any, formData: Partial<Ent
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-full gap-2">
|
<div class="flex h-full gap-2">
|
||||||
<!-- 左侧字典类型列表 -->
|
<!-- 左侧字典类型列表 -->
|
||||||
<div class="w-1/3">
|
<div class="w-1/5">
|
||||||
<DictTypeList
|
<DictTypeList
|
||||||
ref="dictTypeListRef"
|
ref="dictTypeListRef"
|
||||||
@select="handleDictTypeSelect"
|
@select="handleDictTypeSelect"
|
||||||
@ -103,9 +102,9 @@ async function getDictDataPage({ current, pageSize }: any, formData: Partial<Ent
|
|||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<n-button
|
<n-button
|
||||||
v-if="values.dictType"
|
v-if="currentDictType?.type"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="dictDataModalRef?.openModal('add', { dictType: values.dictType })"
|
@click="dictDataModalRef?.openModal('add', { dictType: currentDictType.type })"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-park-outline-plus />
|
<icon-park-outline-plus />
|
@ -2,6 +2,8 @@ import type { DataTableColumns } from 'naive-ui'
|
|||||||
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||||
import { createIcon } from '@/utils'
|
import { createIcon } from '@/utils'
|
||||||
import { renderProCopyableText } from 'pro-naive-ui'
|
import { renderProCopyableText } from 'pro-naive-ui'
|
||||||
|
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||||
|
import type { SearchQuery } from '@/api'
|
||||||
|
|
||||||
// 菜单管理columns配置函数
|
// 菜单管理columns配置函数
|
||||||
interface MenuColumnActions {
|
interface MenuColumnActions {
|
||||||
@ -10,6 +12,23 @@ interface MenuColumnActions {
|
|||||||
onAdd: (row: Entity.Menu) => void
|
onAdd: (row: Entity.Menu) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const searchColumns: ProSearchFormColumns<SearchQuery> = [
|
||||||
|
{
|
||||||
|
title: '菜单名称',
|
||||||
|
path: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
path: 'status',
|
||||||
|
field: 'select',
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 0 },
|
||||||
|
{ label: '停用', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<Entity.Menu> {
|
export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<Entity.Menu> {
|
||||||
const { onEdit, onDelete, onAdd } = actions
|
const { onEdit, onDelete, onAdd } = actions
|
||||||
|
|
||||||
@ -23,7 +42,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
title: '图标',
|
title: '图标',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'icon',
|
key: 'icon',
|
||||||
width: '6em',
|
width: '100px',
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return row.icon && createIcon(row.icon, { size: 20 })
|
return row.icon && createIcon(row.icon, { size: 20 })
|
||||||
},
|
},
|
||||||
@ -85,7 +104,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
{row.menuType !== 'permission' && (
|
{row.menuType !== 'permission' && (
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
type="primary"
|
type="info"
|
||||||
onClick={() => onAdd(row)}
|
onClick={() => onAdd(row)}
|
||||||
>
|
>
|
||||||
新增
|
新增
|
||||||
@ -93,6 +112,7 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
|||||||
)}
|
)}
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
|
type="primary"
|
||||||
onClick={() => onEdit(row)}
|
onClick={() => onEdit(row)}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
@ -1,14 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBoolean } from '@/hooks'
|
import { useBoolean } from '@/hooks'
|
||||||
import { deleteMenu, getMenuList } from '@/api'
|
import { deleteMenu, getMenuList } from '@/api'
|
||||||
import { createMenuColumns } from './columns'
|
import type { SearchQuery } from '@/api'
|
||||||
|
import { createMenuColumns, searchColumns } from './columns'
|
||||||
import MenuModal from './components/MenuModal.vue'
|
import MenuModal from './components/MenuModal.vue'
|
||||||
import arrayToTree from 'array-to-tree'
|
import arrayToTree from 'array-to-tree'
|
||||||
|
import { createProSearchForm } from 'pro-naive-ui'
|
||||||
|
|
||||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
|
|
||||||
const menuModalRef = ref()
|
const menuModalRef = ref()
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = createProSearchForm<SearchQuery>({
|
||||||
|
initialValues: {},
|
||||||
|
onSubmit: getAllRoutes,
|
||||||
|
onReset: getAllRoutes,
|
||||||
|
})
|
||||||
|
|
||||||
// 菜单管理columns配置
|
// 菜单管理columns配置
|
||||||
const columns = createMenuColumns({
|
const columns = createMenuColumns({
|
||||||
onEdit: row => menuModalRef.value.openModal('edit', row),
|
onEdit: row => menuModalRef.value.openModal('edit', row),
|
||||||
@ -20,40 +29,22 @@ async function deleteData(id: number) {
|
|||||||
try {
|
try {
|
||||||
await deleteMenu(id)
|
await deleteMenu(id)
|
||||||
window.$message.success('删除菜单成功')
|
window.$message.success('删除菜单成功')
|
||||||
getAllRoutes() // 重新加载列表
|
searchForm.submit()
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('删除菜单失败', error)
|
console.error('删除菜单失败', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 递归排序菜单树结构
|
|
||||||
function sortMenuTree(menus: Entity.Menu[]): Entity.Menu[] {
|
|
||||||
// 对当前级别的菜单按sort值排序(升序)
|
|
||||||
const sortedMenus = menus.sort((a, b) => {
|
|
||||||
const sortA = a.sort || 0
|
|
||||||
const sortB = b.sort || 0
|
|
||||||
return sortA - sortB
|
|
||||||
})
|
|
||||||
|
|
||||||
// 递归排序子菜单
|
|
||||||
return sortedMenus.map(menu => ({
|
|
||||||
...menu,
|
|
||||||
children: menu.children ? sortMenuTree(menu.children) : undefined,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<Entity.Menu[]>([])
|
const tableData = ref<Entity.Menu[]>([])
|
||||||
async function getAllRoutes() {
|
async function getAllRoutes(params?: SearchQuery) {
|
||||||
startLoading()
|
startLoading()
|
||||||
try {
|
try {
|
||||||
const { data } = await getMenuList()
|
const { data } = await getMenuList(params)
|
||||||
const treeData = arrayToTree(data, {
|
|
||||||
|
tableData.value = arrayToTree(data, {
|
||||||
parentProperty: 'parentId',
|
parentProperty: 'parentId',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 对树形结构按sort值排序
|
|
||||||
tableData.value = sortMenuTree(treeData)
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
window.$message.error('获取菜单列表失败')
|
window.$message.error('获取菜单列表失败')
|
||||||
@ -64,12 +55,21 @@ async function getAllRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getAllRoutes()
|
searchForm.submit()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<n-card class="mb-4">
|
||||||
|
<pro-search-form
|
||||||
|
:form="searchForm"
|
||||||
|
:columns="searchColumns"
|
||||||
|
:collapse-button-props="false"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
<pro-data-table
|
<pro-data-table
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@ -84,17 +84,8 @@ onMounted(() => {
|
|||||||
新增
|
新增
|
||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary" secondary @click="getAllRoutes">
|
|
||||||
<template #icon>
|
|
||||||
<icon-park-outline-refresh />
|
|
||||||
</template>
|
|
||||||
刷新
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</pro-data-table>
|
</pro-data-table>
|
||||||
|
|
||||||
<MenuModal ref="menuModalRef" modal-name="菜单" @success="getAllRoutes" />
|
<MenuModal ref="menuModalRef" modal-name="菜单" @success="searchForm.submit" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
@ -9,7 +9,7 @@ export const searchColumns: ProSearchFormColumns<Entity.Role> = [
|
|||||||
path: 'roleName',
|
path: 'roleName',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '权限标识',
|
title: '角色标识',
|
||||||
path: 'roleKey',
|
path: 'roleKey',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -46,7 +46,7 @@ export function createRoleColumns(actions: RoleColumnActions): DataTableColumns<
|
|||||||
key: 'roleName',
|
key: 'roleName',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '权限标识',
|
title: '角色标识',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
key: 'roleKey',
|
key: 'roleKey',
|
||||||
render: row => renderProCopyableText(row.roleKey),
|
render: row => renderProCopyableText(row.roleKey),
|
@ -151,14 +151,7 @@ defineExpose({
|
|||||||
<pro-input
|
<pro-input
|
||||||
title="手机号"
|
title="手机号"
|
||||||
path="phone"
|
path="phone"
|
||||||
placeholder="11位手机号"
|
max-length="11"
|
||||||
:rule="[
|
|
||||||
{
|
|
||||||
pattern: new RegExp(Regex.Phone),
|
|
||||||
message: '请输入正确的手机号格式',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
<pro-select
|
<pro-select
|
||||||
title="角色"
|
title="角色"
|
Loading…
x
Reference in New Issue
Block a user