上篇实现的用户管理功能实现,主要是通过表单发送请求携带参数访问接口,然后服务器去对数据进行筛选,部门管理以及菜单管理同理,也是通过表单发送请求携带参数访问对应的接口,然后服务器处理数据返回,前端展示。
1.部门(菜单)管理页面


本质上和用户管理页面是一样的,只不过部门需要嵌套上级部门,但是对于前端来说只需要携带参数访问接口就可以了。这里是代码。
javascript
import React, { useEffect, useRef, useState } from 'react'
import { Button, Form, Input, message, Modal, Space, Table } from 'antd'
import { useForm } from 'antd/es/form/Form'
import api from '@/api'
import type { Dept } from '@/types/api'
import CreateDept from './CreateDept'
import type { TableColumnsType } from 'antd'
import type { IAction } from '@/types/modal'
import { toLocalDate } from '@/utils'
export default function DeptList() {
const [form] = useForm()
const deptRef = useRef<{
open: (type: IAction, data?: Dept.EditParams | { parentId: string }) => void
}>(null)
const [data, setData] = useState<Dept.DeptItem[]>([])
useEffect(() => {
getDeptList()
}, [])
const getDeptList = async () => {
const data = await api.getDeptList(form.getFieldsValue())
setData(data)
}
//重置功能
const handleReset = () => {
form.resetFields()
}
//创建部门
const handleCreate = () => {
deptRef.current?.open('create')
}
//创建部门
const handleSubCreate = (id: string) => {
deptRef.current?.open('create', { parentId: id })
}
//编辑部门
const handleEdit = (record: Dept.DeptItem) => {
deptRef.current?.open('edit', record)
}
//删除部门
const handleDelete = (id: string) => {
Modal.confirm({
title: '确认删除吗',
content: '确认删除该部门吗?',
okText: '确定',
cancelText: '取消',
onOk() {
handleDelSubmit(id)
}
})
}
//删除提交
const handleDelSubmit = async (id: string) => {
await api.deleteDept({
id: id
})
message.success('删除成功')
getDeptList()
}
const columns: TableColumnsType<Dept.DeptItem> = [
{
title: '部门名称',
dataIndex: 'deptName',
key: 'deptName',
width: 200
},
{
title: '负责人',
dataIndex: 'userName',
key: 'userName',
width: 150
},
{
title: '更新事件',
dataIndex: 'updateTime',
key: 'updateTime',
render(updateTime) {
return toLocalDate(updateTime)
}
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
render(createTime) {
return toLocalDate(createTime)
}
},
{
title: '操作',
dataIndex: 'action',
width: 200,
//新增的按钮没办法直接渲染 需要用render手动添加
render(, record) {
return (
<Space>
<Button
type='text'
onClick={() => {
handleSubCreate(record._id)
}}
>
新增
</Button>
<Button
type='text'
onClick={() => {
handleEdit(record)
}}
>
编辑
</Button>
<Button
type='text'
onClick={() => {
handleDelete(record._id)
}}
>
删除
</Button>
</Space>
)
}
}
]
return (
<div>
<Form className='search-form' layout='inline' form={form}>
<Form.Item label='部门名称' name='deptName'>
<Input placeholder='部门名称' />
</Form.Item>
<Form.Item>
<Button type='primary' className='mr10' onClick={getDeptList}>
搜索
</Button>
</Form.Item>
<Form.Item>
<Button type='default' onClick={handleReset}>
重置
</Button>
</Form.Item>
</Form>
<div className='base-table'>
<div className='header-wrapper'>
<div className='title'>部门列表</div>
<div className='action'>
<Button type='primary' onClick={handleCreate}>
新增
</Button>
</div>
</div>
<Table
bordered
rowKey='_id'
columns={columns}
dataSource={data}
pagination={false}
/>
</div>
<CreateDept mRef={deptRef} update={getDeptList} />
</div>
)
}
这是主页面,下面的弹出框
javascript
import type { Dept, User } from '@/types/api'
import type { IAction, ImodalProp } from '@/types/modal'
import { Form, Input, message, Modal, Select, TreeSelect } from 'antd'
import { useForm } from 'antd/es/form/Form'
import React, { useEffect, useImperativeHandle } from 'react'
import api from '@/api'
import { useState } from 'react'
export default function CreateDept(props: ImodalProp) {
const [form] = useForm()
const [action, setAction] = useState<IAction>('create')
const [visible, setVisible] = useState(false)
const [userList, setUserList] = useState<User.UserItem[]>([])
const [deptList, setDeptList] = useState<Dept.DeptItem[]>([])
useEffect(() => {
getDeptList()
getAllUserList()
}, [])
const getDeptList = async () => {
const data = await api.getDeptList()
setDeptList(data)
}
const getAllUserList = async () => {
const data = await api.getAllUserList()
setUserList(data)
}
useImperativeHandle(props.mRef, () => {
return {
open
}
})
//打开弹框函数
const open = async (
type: IAction,
data?: Dept.EditParams | { parentId: string }
) => {
setAction(type)
setVisible(true)
const deptData = await api.getDeptList()
setDeptList(deptData)
if (data) {
form.setFieldsValue(data)
} else {
form.resetFields()
}
}
//部门提交
const handleSubmit = async () => {
const valid = await form.validateFields()
if (valid) {
if (action === 'create') {
await api.createDept(form.getFieldsValue())
} else {
await api.editDept(form.getFieldsValue())
}
message.success('操作成功')
handleCancel()
props.update()
}
}
//关闭和重置弹框表单
const handleCancel = () => {
setVisible(false)
form.resetFields()
}
return (
<Modal
title={action === 'create' ? '创建部门' : '编辑部门'}
width={800}
open={visible}
okText='确定'
cancelText='取消'
onOk={handleSubmit}
onCancel={handleCancel}
>
<Form form={form} labelAlign='right' labelCol={{ span: 4 }}>
{/* 隐藏域 */}
<Form.Item name='_id' hidden>
<Input />
</Form.Item>
<Form.Item label='上级部门' name='parentId'>
<TreeSelect
placeholder='请选择上级部门'
allowClear
treeDefaultExpandAll
//映射
fieldNames={{ label: 'deptName', value: '_id' }}
treeData={deptList}
/>
</Form.Item>
<Form.Item
label='部门名称'
name='deptName'
rules={[{ required: true, message: '请输入部门名称' }]}
>
<Input placeholder='请输入部门名称' />
</Form.Item>
<Form.Item
label='负责人'
name='userName'
rules={[{ required: true, message: '请选择负责人' }]}
>
<Select>
{userList.map(item => {
return (
<Select.Option value={item.userName} key={item.userId}>
{item.userName}
</Select.Option>
)
})}
</Select>
</Form.Item>
</Form>
</Modal>
)
}
菜单管理页面也是一模一样的,只是弹出框有点不同以及访问的接口响应数据不同。只展示一下效果图。

2.部门管理页面的路由设置
实际上最开始生成的数据展示之后,其他的增删改查就是对json文件存储数据的增删改查,跟用户列表一样的,但是不同的是这里需要用到children,也就是数组嵌套数组,这样我们可能增删改查就需要考虑到children[]这个属性,也就是里面还有一层,那么就相当于我们判断外面一层之后,再去循环外层的每一个children调用函数去筛选,用到了递归。
javascript
const express = require('express')
const fs = require('fs/promises')
const path = require('path')
const router = express.Router()
// 读取部门列表
async function getDeptList() {
const data = await fs.readFile(path.resolve(__dirname,'../data/deptList.json'))
return JSON.parse(data)
}
// 递归过滤函数
function filterByDeptName(list, deptName) {
return list
.map(item => {
// 深拷贝防止修改原数据
const newItem = { ...item }
// 递归过滤子部门
newItem.children = filterByDeptName(newItem.children || [], deptName)
// 当前部门匹配 或 子部门中有匹配的
if (newItem.deptName.includes(deptName) || newItem.children.length > 0) {
return newItem
}
return null
})
.filter(item => Boolean(item)) // 去掉 null
}
//重写文件
async function writeDeptList(newList) {
await fs.writeFile(path.resolve(__dirname, '../data/deptList.json'), JSON.stringify({ data: newList }, null, 2))
}
// 递归找父节点,根据 parentId
function findDeptById(list, parentId) {
for (const item of list) {
if (item._id === parentId) return item
if (item.children) {
const found = findDeptById(item.children, parentId)
if (found) return found
}
}
return null
}
// 递归查找并更新指定_id的部门
function updateDeptById(list, id, updates) {
for (let item of list) {
if (item._id === id) {
//Object.assign(item, updates)
item.deptName = updates.deptName
item.userName = updates.userName
item.updateTime = updates.updateTime
return true // 找到并更新,结束递归
}
if (item.children && item.children.length>0) {
const updated = updateDeptById(item.children, id, updates)
if (updated) return true
}
}
return false // 没找到
}
//递归遍历删除指定_id的部门
// 递归遍历删除指定_id的部门,返回新数组(过滤掉指定_id节点)
function delDeptById(list, id) {
// 过滤当前层,剔除id匹配的节点
const filtered = list.filter(item => item._id !== id)
// 对每个节点的children递归调用删除
return filtered.map(item => {
if (item.children && item.children.length > 0) {
return {
...item,
children: delDeptById(item.children, id)
}
}
return item
})
}
router.use((req,res,next)=>{
if(req.user){
next()
}else{
res.status(201).send({
code:500001,
data:{
},
msg:'token失效了'
})
}
})
router.get('/list',async(req,res)=>{
const data = await getDeptList()
const {deptName} = req.query
let list = data.data
//console.log(deptName)
if(deptName){
list = filterByDeptName(list,deptName)
}
res.send({
code: 0,
data:list,
msg: 'success'
})
})
//创建部门路由
router.post('/create',async(req,res)=>{
try {
const { deptName, userName, parentId } = req.body
// 读原始部门数据
const deptData = await getDeptList()
const list = deptData.data || []
// 新部门对象,假设你用 _id 作为唯一id,这里用时间戳
const newDept = {
_id: Date.now().toString(),
deptName,
userName,
updateTime:new Date().toLocaleString(),
createTime:new Date().toLocaleString(),
children: []
}
if (parentId) {
// 找到对应父部门
const parentDept = findDeptById(list, parentId)
// 确保父部门有children数组
if (!parentDept.children) parentDept.children = []
parentDept.children.push(newDept)
} else {
// 顶级部门直接 push
list.push(newDept)
}
// 写回文件
await writeDeptList(list)
res.send({
code: 0,
msg: '创建成功',
data: newDept
})
} catch (error) {
console.error(error)
res.status(500).send({
code: 500,
msg: '服务器错误'
})
}
})
router.post('/edit', async (req, res) => {
try {
const { _id, deptName, userName } = req.body
// 读文件
const deptData = await getDeptList()
const list = deptData.data || []
const updates = {
updateTime: new Date().toLocaleString(),
deptName:deptName,
userName:userName
}
// 更新
const updated = updateDeptById(list, _id, updates)
if (!updated) {
return res.status(404).send({
code: 0,
msg: '部门未找到',
})
}
// 写文件
await writeDeptList(list)
res.send({
code: 0,
msg: '编辑成功',
data: updates,
})
} catch (error) {
console.error(error)
res.status(500).send({
code: 500,
msg: '服务器错误',
})
}
})
//删除列表
router.post('/delete', async (req, res) => {
try {
const { _id } = req.body
const data = await getDeptList()
const list = data.data || []
const newList = delDeptById(list, _id)
await writeDeptList(newList)
res.send({
code: 0,
msg: '删除成功',
data: null
})
} catch (error) {
console.error(error)
res.status(500).send({
code: 500,
msg: '服务器错误'
})
}
})
module.exports =router
递归还是很难用的,至少我只是有思路但是不通过ai写不出来,但好在思路是很简单的,就是本质上增删改查就是对数据进行筛选。