feat(dept): 实现部门管理前端页面
dept.ts API: CRUD + buildTree 树形构建; DeptManagement 页面: 树形表格 + 新增/编辑/删除 + 权限守卫; 修复 validation-rules.ts 的 as const 导致 readonly 类型不兼容问题。
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user