前言
封装完页面通用模块后,现在我们正式进入页面的构建。搜索栏、按钮栏和表格就不再多做赘述了,都大同小异只是传入参数不同。我们主要来讲按钮的功能。可以分为以下两类
- 弹窗表单类:通用的新增、编辑,还有特定的重置用户密码、分配角色权限等
- 确认弹框类:删除
用户管理
现在主要把三个模块的按钮功能都完善下
搜索栏按钮
之前搜索栏组件中父组件传递了一个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
钩子函数中进行。注意依赖项是editType
和user_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
中树形表格可以实现。由于不需要分页,我们直接用antd
的Table
组件。
树形表格
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,大家可自行查阅