从0开始的中后台管理系统-5(部门管理以及菜单管理页面功能实现)

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

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写不出来,但好在思路是很简单的,就是本质上增删改查就是对数据进行筛选。