从零实现一个React+Antd5.0后台管理系统-系统管理模块实现

前言

封装完页面通用模块后,现在我们正式进入页面的构建。搜索栏、按钮栏和表格就不再多做赘述了,都大同小异只是传入参数不同。我们主要来讲按钮的功能。可以分为以下两类

  • 弹窗表单类:通用的新增、编辑,还有特定的重置用户密码、分配角色权限等
  • 确认弹框类:删除

用户管理

现在主要把三个模块的按钮功能都完善下

搜索栏按钮

之前搜索栏组件中父组件传递了一个getSearchParams的函数获取搜索参数,如下:

scss 复制代码
// 搜索事件
const onFinish = (values) => {
 getSearchParams(values)
}
// 重置事件
const onReset = () => {
 form.resetFields()
 getSearchParams({})
}

搜索参数修改

由于表格进行了封装,我们在父组件中传递的事件及参数要修改下。searchParams参数弃用,统一用传给表格的requestParam参数

src/pages/System/User/index.jsx

javascript 复制代码
​
// 请求参数重置、修改
const onParamChange = (searchParams) => {
if (!Object.keys(searchParams).length)
  setRequestParam({
    ...requestParam,
    username: undefined,
    nickname: undefined,
    email: undefined,
    status: undefined
  })
else setRequestParam({ ...requestParam, ...searchParams })
}
​
return(
  <>
    <SearchBar formItemList={formItemList} getSearchParams={onParamChange} />
  </>
)

这样就能正常进行搜索与重置的操作

按钮栏按钮与表格操作列按钮

按钮栏目前只有新增、批量删除两个按钮,大家有需要的话可自行扩展。

表格操作列有编辑、删除、重置密码按钮,主要是针对表格当前行的数据进行操作。

新增、编辑、重置密码

这部分功能的话还是采用类似个人中心的弹窗+表单的模式。我们先完成表单组件。

我们先来梳理一下各个按钮功能需要的表单项

  • 新增:用户名、角色、状态、密码、确认密码
  • 编辑:用户名、角色、状态
  • 重置密码:旧密码、密码、确认密码

为了区分功能,需要在父组件传入editType字段指明功能类型,例如新增:add。编辑、重置密码时还需传入操作的用户id。现在我们来梳理一下表单组件需要的字段。

传入字段 是否必填 字段名称 类型 字段说明
editType 必填 编辑类型 String 表示当前弹窗功能
user_id 选填 用户ID String 编辑、重置唯一标识
dict 选填 字典 Object 表单所用到的所有字典值
onRefreshTable 必填 表格刷新事件 Function 使父组件表格刷新
toggleModalStatus 必填 弹窗关闭事件 Function 使外层弹窗关闭

然后就可以根据传入的editType显示对应的表单项,部分表单项如下图。

src/pages/System/User/components/UserEditForm.jsx

获取表单项数据和编辑回显值

那么当我们下拉框或者单选框组需要数据时,还有编辑时需要回显值时,就需要在useEffect钩子函数中进行。注意依赖项是editTypeuser_id,即当编辑类型和用户id改变时重新请求。

scss 复制代码
  // 表单数据
  const [allRoleArr, setAllRoleArr] = useState([])
  useEffect(() => {
    // 进来先重置表单
    form && form.resetFields()
    // 请求角色选项数组
    if (editType !== 'reset' && !allRoleArr.length) {
      const fetchAllRole = async () => {
        const { data } = await roleApi.manage.queryAll()
        const filterArr = data.map((item) => ({ role_id: item.role_id, role_name: item.role_name }))
        setAllRoleArr(filterArr)
      }
      fetchAllRole()
    }
    // 请求当前用户id的信息
    if (editType === 'edit') {
      const fetchCurrentData = async () => {
        const {
          data: { username, roles, status }
        } = await userApi.manage.queryById(user_id)
        // 回显值
        form.setFieldsValue({
          username,
          role_ids: roles.map((item) => item.role_id),
          status
        })
      }
      fetchCurrentData()
    }
  }, [editType, user_id])
取消、提交按钮事件

取消按钮就是关闭弹窗,调用一下父组件传递的弹窗关闭事件即可。提交按钮需要根据编辑类型,请求对应的接口,然后当非重置密码时需要刷新一下父组件表格(传入的刷新方法onRefreshTable,这里设置传入空对象即为重置表格,即将搜索数据置空)。

javascript 复制代码
// 取消按钮事件
const onCancel = () => {
  toggleModalStatus(false)
}
// 提交按钮事件
const onFinish = async (value) => {
// 根据不同类型编辑类型,请求不同接口
  switch (editType) {
    case 'add':
      await userApi.manage.add(value)
      message.success('添加用户成功')
      break
    case 'edit':
      await userApi.manage.update({ user_id, ...value })
      message.success('修改信息成功')
      break
    case 'reset':
      await userApi.manage.reset({ user_id, ...value })
      message.success('重置密码成功')
      break
    default:
      console.log(editType)
  }
  onCancel()
  // 刷新表格
  if (editType !== 'reset') onRefreshTable({})
}

然后父组件中用自定义弹窗组件包裹上述组件传入对应参数。新增时重新赋值为add并将user_id置为空,点击编辑按钮时重新赋值编辑类型为edit和user_id为当前行用户id,重置密码与编辑类似。

src/pages/System/User/index.jsx

ini 复制代码
    // 功能类型
  const [editType, setEditType] = useState()
  // 当前行用户id
  const [user_id, setUserId] = useState()
  // 添加用户
  const addRow = () => {
    setEditType('add')
    setUserId('')
    toggleModalStatus(true)
  }
  // 弹窗显隐切换
  const toggleModalStatus = (status) => {
    userModalRef.current.toggleShowStatus(status)
  }
  ...
  <CustomModal title="用户编辑" ref={userModalRef}>
    <UserEditForm
      editType={editType}
      user_id={user_id}
      dict={{ statusDict }}
      onRefreshTable={onParamChange}
      toggleModalStatus={toggleModalStatus}
    />
  </CustomModal>

批量删除

批量删除是按钮栏的按钮,一般是表格每行左侧有个复选框,有勾选时出现此按钮。在表格选择的配置项为rowSelection,里面有个onChange事件,我们用一个数组接收选择项,改变时重设数组即可。

src/pages/System/User/index.jsx

scss 复制代码
// 勾选的用户
const [selectedRowKeys, setSelectedRowKeys] = useState()
// 选择用户
const rowSelection = {
  onChange: (selectedRowKeys) => {
    setSelectedRowKeys(selectedRowKeys)
}
}
<CustomTable
  rowSelection={{
    type: 'checkbox',
    ...rowSelection
  }}
  ...
/>
批量删除按钮显示逻辑
ini 复制代码
{selectedRowKeys && selectedRowKeys.length > 0 && (
<Popconfirm title="删除用户" description="确定要删除吗?" onConfirm={() => deleteRow(selectedRowKeys)}>
  <AuthComponent permission="system:user:del" danger>
    批量删除
  </AuthComponent>
</Popconfirm>
)}
删除方法

删除方法和表格操作列删除按钮共用一个。表格删除时为字符串类型,批量删除时为数组类型

javascript 复制代码
  // 删除
const deleteRow = async (user_ids) => {
  await userApi.manage.del({ user_ids: user_ids })
  message.success('删除成功')
  // 重新请求表格
  onParamChange({})
}

最终效果

用户管理页面

用户管理新增功能

角色管理

角色管理总体与用户管理类似,表格操作列多了一个分配权限的按钮,我们先完成基本的三个模块及新增、编辑、删除按钮。

然后我们着手完成分配权限的功能。

分配权限

分配权限是弹窗加弹窗内部组件。弹窗内部组件主要由树形控件Tree构成,我们先罗列一下我们需要用到的Tree`控件的属性。

| 属性 | 属性类型 | 属性说明 |
|--------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------|------------------------------|
| checkable | boolean | 节点前添加 Checkbox 复选框 |
| defaultExpandAll | boolean | 默认展开所有树节点 |
| checkStrictly | boolean | checkable 状态下节点选择完全受控(父子节点选中状态不再关联) |
| treeData | array<{key, title, children, [disabled, selectable]}> | treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点(key 在整个树范围内唯一) |
| checkedKeys | string[] | {checked: string[], halfChecked: string[]} | (受控)选中复选框的树节点(父子节点的选中与否不再关联) |
| onCheck | function(checkedKeys, e:{checked: boolean, checkedNodes, node, event, halfCheckedKeys}) | 点击复选框触发 |

然后我们开始封装弹窗内部组件RoleAuthTree

步骤有三步

1.获取所有权限列表,并将其数据结构转换为array<{key, title, children}>类型(这里后端接口直接返回的就是这个格式,无需转换)

src\pages\System\Role\components\RoleAuthTree.jsx

javascript 复制代码
import authApi from '@/api/auth'
const RoleAuthTree = () => {
 // 权限树的数据
  const [treeData, setTreeData] = useState([])
  // 勾选的复选框数组
  const [checkedKeys, setCheckedKeys] = useState([])
  // 勾选复选框回调
  const onCheck = (checkedKeysValue) => {
    setCheckedKeys(checkedKeysValue)
  }
​
  const onCancel = () => {
    setCheckedKeys({ checked: [], halfChecked: [] })
    toggleAssignStatus(false)
  }
  
  const onSubmit = async () => {
    toggleAssignStatus(false)
  }
​
  useEffect(() => {
    const fetchAuthTree = async () => {
      const { data } = await authApi.options.getAuth()
      setTreeData(data)
    }
    fetchAuthTree()
  }, [])
  return (
    <>
      {treeData.length > 0 && (
        <Tree
          checkable
          defaultExpandAll={true}
          onCheck={onCheck}
          checkedKeys={checkedKeys}
          checkStrictly
          treeData={treeData}
        />
      )}
      <div style={{ textAlign: 'center' }}>
        <Button onClick={onCancel}>取消</Button>
        <Button type="primary" style={{ marginLeft: 32 }} onClick={onSubmit}>
          确认
        </Button>
      </div>
    </>
  )
}
export default RoleAuthTree

2.获取当前角色所拥有权限数据的数组,这个我们在父组件请求接口获取后传进来

src\pages\System\Role\index.jsx

javascript 复制代码
...
// 表格列配置
const columns = [
      {
      title: '操作',
      key: 'action',
      render: (text, row) => (
        <Space
          style={{
            cursor: 'pointer',
            color: '#2378f7',
            fontSize: '15px'
          }}>
          ...
          <span onClick={() => assignRoleAuth(row.role_id)}>
            <AuthComponent permission="system:role:assignAuth" title="分配权限">
              分配权限
            </AuthComponent>
          </span>
        </Space>
      ),
      align: 'center'
    }
]
/** 分配角色权限 */
  const [assignRoleId, setAssignRoleId] = useState()
  const roleAuthRef = useRef()
  // 当前行角色权限数据
  const [roleAuths, setRoleAuths] = useState([])
  const toggleAssignStatus = (status) => {
    roleAuthRef.current.toggleShowStatus(status)
  }
  const assignRoleAuth = async (role_id) => {
   // 获取该角色id的权限数据
    const { data } = await roleApi.resource.getRes(role_id)
    setAssignRoleId(role_id)
    setRoleAuths(data)
    toggleAssignStatus(true)
  }
  <CustomModal title="分配角色权限" ref={roleAuthRef}>
    <RoleAuthTree
      role_id={assignRoleId}
      roleAuths={roleAuths}
      toggleAssignStatus={toggleAssignStatus}></RoleAuthTree>
  </CustomModal>

src\pages\System\Role\components\RoleAuthTree.jsx

javascript 复制代码
const RoleAuthTree = ({ role_id, roleAuths, toggleAssignStatus }) => {
  const fetchAuthTree = async () => {
    const { data } = await authApi.options.getAuth()
    setTreeData(data)
    // 设置勾选复选框为传入角色权限数据
    setCheckedKeys((checkedKeys) => ({ ...checkedKeys, checked: roleAuths }))
  }
}

3.勾选完复选框提交数据,也是请求接口提交当前已勾选角色的权限数据

javascript 复制代码
 const onSubmit = async () => {
   await roleApi.resource.updRes(role_id, { all_ids: checkedKeys.checked })
   message.success('分配角色权限成功')
   toggleAssignStatus(false)
 }

最终效果

权限管理

权限管理页面由于要展示菜单+按钮,所以需要层级嵌套的组件结构,在antd中树形表格可以实现。由于不需要分页,我们直接用antdTable组件。

树形表格

Table组件的dataSource属性只要数据中有children字段会自动展示为树形表格

我们只需要传给Table组件的dataSource如下图所示的数据

展示效果如下

表单编辑

表单编辑按照不同的类别会展示不同的表单项让用户填写

  • 共有:父级菜单(下拉框)、权限名称、权限类型(目录、菜单、按钮)、排序、状态(显示、隐藏)
  • 目录类型:路由路径(即React Router的path字段)、图标(自定义选择svg图标组件)、重定向路径(访问路径时重定向哪个子路由)
  • 菜单类型:路由路径(即React Router的path字段)、图标(自定义选择svg图标组件)、组件路径(组件在项目文件夹中的路径)
  • 按钮类型:权限标识(按钮的唯一标识)

然后我们封装一下选择图标的组件

封装选择图标组件

之前我们已经封装过SvgIcon组件,如下图

现在图标已经全部引入,并挂载到body上了。我们现在只需要传入图中name就能显示不同的svg图标。那么我们只要获取所有的svg图标名称,存在数组里并且遍历渲染 出来不就有svg图标列表,再添加一下选择事件就可以了。

之前其实已经给大家提供了这个方法

就是全量导入svg图标后,按正则表达式去匹配就可以得到['404','bug'...]这样的图标名称列表。

然后我们在选择图标组件中导入这个方法调用赋值给一个变量,这个变量就是图标数组,我们遍历展示即可。

src/components/IconSelect/index.jsx

表单编辑组件中使用

把上图的iconSelectVDOM放在表单项中即可

获取数据

从表格页进来新增、编辑有三种情况

  • 直接点击按钮栏的新增(无id),那么我们只要清空表单即可
  • 点击表格操作列的添加(有id),那么我们除清空表单外,还要给父级菜单的下拉框回显当前id的值作为父级菜单
  • 表格操作列编辑(有id),根据id请求接口获取信息回显值

父级下拉框的选项是树形结构 ,除了我们从接口获取的菜单数据,我们还应该在顶层添加一条作为顶层菜单:值为0,children配置再添加接口传递的菜单项数据。获取数据的代码如下图

这里我们系统管理模块就结束了。

代码

上述实现的代码都放在react-antd5-admin,大家可自行查阅

相关推荐
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱0012 小时前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
Rattenking4 小时前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
熊的猫5 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
小牛itbull9 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
FinGet20 小时前
那总结下来,react就是落后了
前端·react.js
王解1 天前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
AIoT科技物语2 天前
免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏
前端·物联网·react.js·开源·echarts
初遇你时动了情2 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router