feat(dept): 实现部门管理前端页面

dept.ts API: CRUD + buildTree 树形构建;
DeptManagement 页面: 树形表格 + 新增/编辑/删除 + 权限守卫;
修复 validation-rules.ts 的 as const 导致 readonly 类型不兼容问题。
This commit is contained in:
张翔
2026-05-06 16:23:49 +08:00
committed by zhangxiang
parent 5aefb8ca44
commit 778b846fb3
3 changed files with 245 additions and 11 deletions
+75
View File
@@ -0,0 +1,75 @@
import request from '@/utils/request'
export interface DeptItem {
id: number
parentId: number
deptName: string
orderNum: number
leader: string
phone: string
email: string
status: number
createdAt: string
updatedAt: string
children?: DeptItem[]
}
export interface CreateDeptRequest {
parentId?: number
deptName: string
orderNum?: number
leader?: string
phone?: string
email?: string
status?: number
}
export interface UpdateDeptRequest {
parentId?: number
deptName?: string
orderNum?: number
leader?: string
phone?: string
email?: string
status?: number
}
function buildTree(list: DeptItem[]): DeptItem[] {
const map = new Map<number, DeptItem>()
const roots: DeptItem[] = []
for (const item of list) {
map.set(item.id, { ...item, children: [] })
}
for (const item of list) {
const node = map.get(item.id)!
if (item.parentId === 0 || !map.has(item.parentId)) {
roots.push(node)
} else {
const parent = map.get(item.parentId)
parent?.children?.push(node)
}
}
return roots
}
export const deptApi = {
getAll: async (): Promise<DeptItem[]> => {
const res = await request.get<DeptItem[]>('/depts')
const list = Array.isArray(res) ? res : []
return buildTree(list)
},
getById: async (id: number): Promise<DeptItem> => {
const res = await request.get<DeptItem>(`/depts/${id}`)
return res as unknown as DeptItem
},
create: (data: CreateDeptRequest) =>
request.post<DeptItem>('/depts', data),
update: (id: number, data: UpdateDeptRequest) =>
request.put<DeptItem>(`/depts/${id}`, data),
delete: (id: number) =>
request.delete<void>(`/depts/${id}`),
}
@@ -21,7 +21,7 @@ function typeNumberMinRule(min: number, message: string): Rule {
return { type: 'number' as const, min, message }
}
export const VALIDATION = {
export const VALIDATION: Record<string, FieldValidation> = {
username: {
rules: [
requiredRule('请输入用户名'),
@@ -154,7 +154,7 @@ export const VALIDATION = {
],
initialValue: 0,
},
} as const
}
export type ValidationField = keyof typeof VALIDATION
@@ -1,18 +1,177 @@
import { Result, Button } from 'antd'
import { ToolOutlined } from '@ant-design/icons'
import { useNavigate } from 'react-router'
import { useState, useEffect } from 'react'
import { Table, Button, Modal, Form, Input, InputNumber, Select, Tag, Space, message, Popconfirm } from 'antd'
import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import { deptApi } from '@/api/dept'
import type { DeptItem, CreateDeptRequest, UpdateDeptRequest } from '@/api/dept'
import { VALIDATION } from '@/constants/validation-rules'
import PermissionGuard from '@/components/PermissionGuard'
const statusOptions = [
{ label: '正常', value: 1 },
{ label: '停用', value: 0 },
]
export default function DeptManagement() {
const navigate = useNavigate()
const [depts, setDepts] = useState<DeptItem[]>([])
const [loading, setLoading] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [editingDept, setEditingDept] = useState<DeptItem | null>(null)
const [form] = Form.useForm()
useEffect(() => {
loadDepts()
}, [])
async function loadDepts() {
setLoading(true)
try {
const res = await deptApi.getAll()
setDepts(res)
} catch {
message.error('加载部门列表失败')
} finally {
setLoading(false)
}
}
function handleAdd(parentId = 0) {
setEditingDept(null)
form.resetFields()
form.setFieldsValue({ parentId, orderNum: VALIDATION.deptSort.initialValue, status: 1 })
setModalOpen(true)
}
function handleEdit(record: DeptItem) {
setEditingDept(record)
form.setFieldsValue({
parentId: record.parentId,
deptName: record.deptName,
orderNum: record.orderNum,
leader: record.leader,
phone: record.phone,
email: record.email,
status: record.status,
})
setModalOpen(true)
}
async function handleDelete(id: number) {
try {
await deptApi.delete(id)
message.success('删除成功')
loadDepts()
} catch {
message.error('删除失败')
}
}
async function handleSubmit() {
try {
const values = await form.validateFields()
if (editingDept) {
const data: UpdateDeptRequest = { ...values }
await deptApi.update(editingDept.id, data)
message.success('更新成功')
} else {
const data: CreateDeptRequest = { ...values }
await deptApi.create(data)
message.success('创建成功')
}
setModalOpen(false)
loadDepts()
} catch { /* ignored */ }
}
const columns: ColumnsType<DeptItem> = [
{ title: '部门名称', dataIndex: 'deptName', key: 'deptName', width: 200 },
{ title: '排序', dataIndex: 'orderNum', key: 'orderNum', width: 80 },
{ title: '负责人', dataIndex: 'leader', key: 'leader', width: 100 },
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 },
{ title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 80,
render: (status: number) => (
<Tag color={status === 1 ? 'green' : 'red'}>{status === 1 ? '正常' : '停用'}</Tag>
),
},
{
title: '操作',
key: 'action',
width: 220,
render: (_, record) => (
<Space>
<PermissionGuard permission="system:dept:add">
<Button type="link" size="small" onClick={() => handleAdd(record.id)}></Button>
</PermissionGuard>
<PermissionGuard permission="system:dept:edit">
<Button type="link" icon={<EditOutlined />} size="small" onClick={() => handleEdit(record)} />
</PermissionGuard>
<PermissionGuard permission="system:dept:delete">
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
<Button type="link" danger icon={<DeleteOutlined />} size="small" />
</Popconfirm>
</PermissionGuard>
</Space>
),
},
]
return (
<div style={{ padding: 24 }}>
<Result
icon={<ToolOutlined />}
title="部门管理"
subTitle="该功能正在开发中,敬请期待"
extra={<Button type="primary" onClick={() => navigate('/dashboard')}></Button>}
<div style={{ marginBottom: 16 }}>
<Space>
<PermissionGuard permission="system:dept:add">
<Button type="primary" icon={<PlusOutlined />} onClick={() => handleAdd()}></Button>
</PermissionGuard>
<Button icon={<ReloadOutlined />} onClick={loadDepts}></Button>
</Space>
</div>
<Table<DeptItem>
rowKey="id"
columns={columns}
dataSource={depts}
loading={loading}
expandable={{ defaultExpandAllRows: true }}
pagination={false}
/>
<Modal
title={editingDept ? '编辑部门' : '新增部门'}
open={modalOpen}
onOk={handleSubmit}
onCancel={() => setModalOpen(false)}
destroyOnHidden
width={600}
>
<Form form={form} layout="vertical">
<Form.Item name="parentId" label="上级部门" initialValue={0}>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="deptName" label="部门名称" rules={VALIDATION.deptName.rules}>
<Input />
</Form.Item>
<Form.Item name="orderNum" label="排序" initialValue={VALIDATION.deptSort.initialValue} rules={VALIDATION.deptSort.rules}>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="leader" label="负责人">
<Input />
</Form.Item>
<Form.Item name="phone" label="手机号">
<Input />
</Form.Item>
<Form.Item name="email" label="邮箱">
<Input />
</Form.Item>
<Form.Item name="status" label="状态" initialValue={1}>
<Select options={statusOptions} />
</Form.Item>
</Form>
</Modal>
</div>
)
}