From 778b846fb341165612b83a01f8644de0aefd484b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 6 May 2026 16:23:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(dept):=20=E5=AE=9E=E7=8E=B0=E9=83=A8?= =?UTF-8?q?=E9=97=A8=E7=AE=A1=E7=90=86=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dept.ts API: CRUD + buildTree 树形构建; DeptManagement 页面: 树形表格 + 新增/编辑/删除 + 权限守卫; 修复 validation-rules.ts 的 as const 导致 readonly 类型不兼容问题。 --- novalon-manage-web/src/api/dept.ts | 75 ++++++++ .../src/constants/validation-rules.ts | 4 +- .../src/pages/system/dept/index.tsx | 177 +++++++++++++++++- 3 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 novalon-manage-web/src/api/dept.ts diff --git a/novalon-manage-web/src/api/dept.ts b/novalon-manage-web/src/api/dept.ts new file mode 100644 index 0000000..19eb4eb --- /dev/null +++ b/novalon-manage-web/src/api/dept.ts @@ -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() + 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 => { + const res = await request.get('/depts') + const list = Array.isArray(res) ? res : [] + return buildTree(list) + }, + + getById: async (id: number): Promise => { + const res = await request.get(`/depts/${id}`) + return res as unknown as DeptItem + }, + + create: (data: CreateDeptRequest) => + request.post('/depts', data), + + update: (id: number, data: UpdateDeptRequest) => + request.put(`/depts/${id}`, data), + + delete: (id: number) => + request.delete(`/depts/${id}`), +} diff --git a/novalon-manage-web/src/constants/validation-rules.ts b/novalon-manage-web/src/constants/validation-rules.ts index 56fa82c..d7a5d82 100644 --- a/novalon-manage-web/src/constants/validation-rules.ts +++ b/novalon-manage-web/src/constants/validation-rules.ts @@ -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 = { username: { rules: [ requiredRule('请输入用户名'), @@ -154,7 +154,7 @@ export const VALIDATION = { ], initialValue: 0, }, -} as const +} export type ValidationField = keyof typeof VALIDATION diff --git a/novalon-manage-web/src/pages/system/dept/index.tsx b/novalon-manage-web/src/pages/system/dept/index.tsx index f21178e..8445bf6 100644 --- a/novalon-manage-web/src/pages/system/dept/index.tsx +++ b/novalon-manage-web/src/pages/system/dept/index.tsx @@ -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([]) + const [loading, setLoading] = useState(false) + const [modalOpen, setModalOpen] = useState(false) + const [editingDept, setEditingDept] = useState(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 = [ + { 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) => ( + {status === 1 ? '正常' : '停用'} + ), + }, + { + title: '操作', + key: 'action', + width: 220, + render: (_, record) => ( + + + + + + } +
+ + + + + + +
+ + + rowKey="id" + columns={columns} + dataSource={depts} + loading={loading} + expandable={{ defaultExpandAllRows: true }} + pagination={false} /> + + setModalOpen(false)} + destroyOnHidden + width={600} + > +
+ + + + + + + + + + + + + + + + + + + +