从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参数中拿到数据进行增删改查然后响应给前端。

相关推荐
三小河8 分钟前
借用数组非破坏性方法来优化react的状态更新
前端
海拥16 分钟前
AI编程实践:使用Trae快速开发“躲避陨石”HTML小游戏
前端·trae
无业哥30 分钟前
Vue&ElementPlus 按需导入
vue.js
tanxiaomi32 分钟前
✨ 基于 JsonSerialize 实现接口返回数据的智能枚举转换(优雅告别前端硬编码!)
java·前端·spring·spring cloud·mybatis
好好好明天会更好33 分钟前
vue中template的使用
前端·html
快起来别睡了39 分钟前
虚拟滚动:前端长列表性能优化的“魔法”
前端
XiongLiding1 小时前
12KB 的 Excel 导出库 sheetex 是怎么来的
前端
前端老鹰1 小时前
CSS accent-color:一键定制表单元素的主题色,告别样式冗余
前端·css·html
蓝胖子的小叮当1 小时前
JavaScript基础(十二)高阶函数、高阶组件
前端·javascript
xyccstudio1 小时前
鸿蒙动态共享包HSP
前端·harmonyos