从0开始的中后台管理系统-5(userList页面功能实现)

本篇实现了一个完整的用户列表的增删改查以及搜索和重置功能,主要是引入一个ahoos插件库然后引入了一个useAntdTable()钩子来帮助我们调用获取用户列表的请求以及在配置对象绑定form对象后可以帮我们生成search方法(本质上也是调用获取用户列表请求然后携带查询字符串参数的方式让服务器对数据进行筛选)。也会去展示后端代码。

1.前端代码

javascript 复制代码
import React, { useState, useEffect, useRef } from 'react'
import type { PageParams, User } from '@/types/api'
import type { TableColumnsType } from 'antd'
import { Button, Table, Form, Input, Select, Space, Modal, message } from 'antd'
import api from '@/api/index'
import { toLocalDate } from '@/utils'
import CreateUser from './CreateUser'
import type { IAction } from '@/types/modal'
import { useAntdTable } from 'ahooks'
export default function UserList() {
  const userRef = useRef<{
    open: (type: IAction, data?: User.UserItem) => void
  }>(null)
  //获取表单对象 用里面的方法 getFieldsValue获取表单中填写的内容
  const [form] = Form.useForm()
  const [userIds, setUserIds] = useState<number[]>([])
  //创建
  const handleCreate = () => {
    userRef.current?.open('create')
  }
  //编辑
  const handleEdit = (recoad: User.UserItem) => {
    userRef.current?.open('edit', recoad)
  }
  //删除用户
  const handleDel = (userId: number) => {
    Modal.confirm({
      title: '删除确认',
      content: <span>确认删除改用户吗</span>,
      onOk: () => {
        handleUserDelSubmit([userId])
      }
    })
  }
  //批量删除确认
  const handlePatchConfirm = () => {
    if (userIds.length === 0) {
      message.error('请选择要删除掉用户')
      return
    }
    Modal.confirm({
      title: '删除确认',
      content: <span>确认删除改批量用户吗</span>,
      onOk: () => {
        handleUserDelSubmit(userIds)
      }
    })
  }
  //公共的删除接口
  const handleUserDelSubmit = async (ids: number[]) => {
    try {
      const data = await api.delUser({
        userId: ids
      })
      message.success('删除成功')
      setUserIds([])
      search.reset()
    } catch (e) {}
  }
  /*
    先写一个函数接口调用请求
对,这就是 getTableData,负责发请求并返回 Promise,且 resolve { total, list }。

把表单数据作为 Promise 实例返回
 对,formData 参数就是表单的值,useAntdTable 会帮你自动传进来。

钩子的第一个参数是接口函数,第二个参数是配置对象
对,配置对象里你用了 form(useForm 的表单对象)和 defaultPageSize。

form 绑定到表格中,配置项绑定分页和搜索
 对,传了 form 以后,表单和表格就能联动搜索了。

search.submit() 和 search.reset() 分别触发搜索和重置
 对,搜索和分页都是重新调用接口,只是参数不同。
  */
  const getTableData = (
    {
      current,
      pageSize
    }: {
      current: number
      pageSize: number
    },
    formData: User.Params
  ) => {
    return api
      .getUserList({
        ...formData,
        pageNum: current,
        pageSize: pageSize
      })
      .then(data => {
        return {
          total: data.page.total,
          list: data.list
        }
      })
  }
  const { tableProps, search } = useAntdTable(getTableData, {
    form,
    defaultPageSize: 5
  })
  const columns: TableColumnsType<User.UserItem> = [
    {
      title: '用户ID',
      dataIndex: 'userId',
      key: 'userId'
    },
    {
      title: '用户名称',
      dataIndex: 'userName',
      key: 'userName'
    },
    {
      title: '用户邮箱',
      dataIndex: 'userEmail',
      key: 'userEmail'
    },
    {
      title: '用户角色',
      dataIndex: 'role',
      key: 'role',
      render(role: number | string) {
        return {
          0: '超级管理员',
          1: '管理员',
          2: '体验管理员',
          3: '普通用户'
        }[role]
      }
    },
    {
      title: '用户状态',
      dataIndex: 'state',
      key: 'state',
      render(state: number) {
        return {
          1: '在职',
          2: '离职',
          3: '试用期'
        }[state]
      }
    },
    {
      title: '注册时间',
      dataIndex: 'create', // 你的字段是 create,不是 createTime
      key: 'create',
      render(createTime: string) {
        return toLocalDate(createTime)
      }
    },
    {
      title: '操作',
      key: 'adress',
      render(record) {
        return (
          <Space>
            <Button
              type='text'
              onClick={() => {
                handleEdit(record)
              }}
            >
              编辑
            </Button>
            <Button type='text' danger onClick={() => handleDel(record.userId)}>
              删除
            </Button>
          </Space>
        )
      }
    }
  ]
  return (
    <div className='user-list'>
      <Form
        form={form}
        className='search-form'
        layout='inline'
        initialValues={{ state: 0 }}
      >
        <Form.Item name='userId' label='用户ID'>
          <Input placeholder='请输入用户ID' />
        </Form.Item>
        <Form.Item name='userName' label='用户名称'>
          <Input placeholder='请输入用户名称' />
        </Form.Item>
        <Form.Item name='state' label='状态'>
          <Select style={{ width: 120 }}>
            <Select.Option value={0}>所有</Select.Option>
            <Select.Option value={1}>在职</Select.Option>
            <Select.Option value={2}>离职</Select.Option>
            <Select.Option value={3}>试用期</Select.Option>
          </Select>
        </Form.Item>
        <Form.Item>
          <Space>
            <Button onClick={search.submit} type='primary' className='mr10'>
              搜索
            </Button>
            <Button onClick={search.reset} type='default'>
              重置
            </Button>
          </Space>
        </Form.Item>
      </Form>

      <div className='base-table'>
        <div className='header-wrapper'>
          <div className='title'>用户列表</div>
          <div className='action'>
            <Button type='primary' onClick={handleCreate}>
              新增
            </Button>
            <Button
              type='primary'
              danger
              onClick={() => {
                handlePatchConfirm()
              }}
            >
              批量删除
            </Button>
          </div>
        </div>
        <Table
          rowKey='userId' // 保证每行有唯一 key
          {...tableProps}
          bordered
          //rowSelection多选
          rowSelection={{
            type: 'checkbox',
            selectedRowKeys: userIds,
            onChange: (selecterRowKeys: React.Key[]) => {
              setUserIds(selecterRowKeys as number[])
            }
          }}
          columns={columns}
        />
      </div>
      <CreateUser
        mRef={userRef}
        updata={() => {
          search.reset()
        }}
      />
    </div>
  )
}

增删改查实际上就是调用不同的api,然后把对应请求需要的数据传递过去,比如删除实际上需要传递一个userId参数,服务器拿到之后筛选,增加实际上是把在表单中填写的所有数据放在请求体中传递给服务器服务器push进数组。然后修改就是userId拿到对应的数组然后覆盖掉传过去不同的值。仅此而已。

这里用ahoos插件库里面的useAntdTable钩子用法是1.先写一个函数接口调用请求,这就是 getTableData,负责发请求并返回 Promise,且 resolve { total, list }。2.把表单数据作为 Promise 实例返回3.formData 参数就是表单的值,useAntdTable 会帮你自动传进来。4.钩子的第一个参数是接口函数,第二个参数是配置对象5.配置对象里你用了 form(useForm 的表单对象)和 defaultPageSize。6.form 绑定到表格中,配置项绑定分页和搜索,传了 form 以后,表单和表格就能联动搜索了。7.search.submit() 和 search.reset() 分别触发搜索和重置,搜索和分页都是重新调用接口,只是参数不同。

2.后端代码

前后端分离就是处理数据基本放在服务端,服务端通过前端请求携带的参数进行处理。

这是服务器的结构目录,没有搭建数据库连接为了持久化存储放在了json文件中。然后userList路由存放对userslist.json中存放的数据进行增删改查的路由设置。

javascript 复制代码
const express = require('express')
const fs = require('fs/promises')
const path = require('path')
const multer = require('multer')
const router = express.Router()

// 读取单个用户信息
async function getUserList() {
    const data = await fs.readFile(path.resolve(__dirname,'../data/userList.json'))
    return JSON.parse(data)
}

// 读取用户列表信息
async function getUsersList() {
    const data = await fs.readFile(path.resolve(__dirname,'../data/usersList.json'))
    return JSON.parse(data)
}
//将新数组重新写入文件中并且将新数据读取出来
async function updataUsersList(newUsers) {
  await fs.writeFile(
    path.resolve(__dirname, '../data/usersList.json'),
    JSON.stringify(newUsers, null, 2) // 转成 JSON 并美化
  )
  console.log('写入成功')
  const data = await fs.readFile(path.resolve(__dirname, '../data/usersList.json'))
  return JSON.parse(data)
}

/* ------------------ 图片上传相关配置 ------------------ */

// 配置 multer 存储位置和文件名
const storage = multer.diskStorage({
  // 设置文件保存的目录
  destination: (req, file, cb) => {
    // 将图片存储到 /public/images 目录
    // path.join 确保路径兼容不同系统
    cb(null, path.join(__dirname, '../public/images'))
  },
  // 设置文件的命名规则
  filename: (req, file, cb) => {
    // 取出文件原始的后缀名,比如 .jpg / .png
    const ext = path.extname(file.originalname)
    // 使用当前时间戳作为文件名,避免重名覆盖
    cb(null, Date.now() + ext)
  }
})

// 使用 multer 并绑定存储配置
const upload = multer({ storage })
router.use((req,res,next)=>{
  if(req.user){
    next()
  }else{
    res.send({
        code: 50001,
        data: {},
        msg: 'token过期了'
    })
  }
})
// 上传接口
// upload.single('file') 表示只接收一个文件,字段名是 'file'
router.post('/upload', upload.single('file'), (req, res) => {
  // 生成前端可访问的图片路径(这里是相对路径)
  const fileUrl = http://localhost:3000/images/${req.file.filename}
  // 返回上传成功的响应
  res.send({
    code: 0,
    msg: '上传成功',
    url: fileUrl // 前端可以用这个 URL 来显示图片
  })
})
/* ------------------------------------------------------- */

// 获取单个用户信息接口
router.get('/getUserInfo', async (req, res) => {
    const data = await getUserList()
    res.send(data)
})

// 获取用户列表接口
router.get('/list', async (req, res) => {
    const data = await getUsersList()
    let list = data.data.list
    // 获取 query 参数
  const { state, userId, userName } = req.query
  console.log(state,userId,userName)
  // 按状态筛选(0 表示全部,不筛选)
  if (state && state !== '0') {
    list = list.filter(item =>String(item.state) === String(state))
  }

  // 按用户ID筛选
  if (userId) {
    list = list.filter(item => String(item.userId).includes(String(userId)))
  }

  // 按用户名称模糊搜索
  if (userName) {
    list = list.filter(item =>
      item.userName && item.userName.includes(userName)
    )
  }
   res.send({
    code: 0,
    data: {
      list,
      page: {
        ...data.page,
        total:list.length
      }
    },
    msg: 'success'
  })
})
//创建用户接口
router.post('/create',async(req,res)=>{
   // console.log(req.body)
    const data = await getUsersList()
    const newUser={userId:Date.now(),...req.body}
    data.data.list.push(newUser)
  const newData= await  updataUsersList(data)
    res.send(newData)
})
//编辑用户接口
router.post('/edit',async(req,res)=>{
    console.log(req.body)
    const data = await getUsersList()
    let index = data.data.list.findIndex((item)=> item.userId===req.body.userId)
    if(index!==-1){
      data.data.list[index]={
        ...data.data.list[index],
        ...req.body
      }
    }
    const newData = await updataUsersList(data)
    res.send(newData)
})
//删除用户
router.post('/delete',async(req,res)=>{
    console.log(req.body)
    const { userId } = req.body
    if(userId &&userId.length!==0){
      const data = await getUsersList()
      data.data.list = data.data.list.filter((item)=>!userId.includes(item.userId))
      const newData = await updataUsersList(data)
      res.send(newData)
    }else{
      res.status(400).send({
        code:0,
        data:{},
        msg:'请输入要删除的数组userId'
      })
    }
})
module.exports = router

首先中间件是判断是否有token,然后就是增删改查路由,前面的异步函数都是读取文件的数据,然后userslist数据也是统一处理。比如传入新数据重写到文件然后重新读取转化为对象,让服务器发送给前端,剩下的就是根据前端请求携带的请求体或者query参数中拿到数据进行增删改查然后响应给前端。

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax