首先从逻辑上,用户管理只限制admin用户显示
一、路由限制用户管理的访问权限
config/routes.ts添加access:admin权限限制
{
name: 'userManage',
icon: 'table',
access: 'canAdmin',
path: '/userManage',
component: './userManage',
}
二、页面水印处理
水印是 PageContainer 的功能,layout 只是透传给 PageContainer,屏蔽方法,找到文件src/app.tsx,直接注释代码
waterMarkProps: {
content: initialState?.currentUser?.username
},
三、列表的增删改查功能接口联调以及页面代码修改
1.1、node开发查询用户接口,找到node项目router/user.js,添加查询用户列表接口代码。然后重启项目
//获取用户列表数据
router.get('/getUserList',commonFunc.authMiddle,(req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const name = req.query.username || '';
userModel.find({ username: { $regex: name, $options: 'i' }}).countDocuments().then((allTotal)=>{
userModel.find({ username: { $regex: name, $options: 'i' }}).skip((page - 1) * limit).limit(limit).then((data)=>{
res.send({
code: 200,
msg: "获取成功!",
data,
total:allTotal
});
}).catch((err)=>{
res.send(err)
})
})
})
//改变用户名称或者密码
router.post('/changeUser',commonFunc.authMiddle,(req, res) => {
const token = req.headers.authorization
if(token){
var { _id,username,password } = req.body;
userModel.find({ username: { $regex: username, $options: 'i' }}).then((data)=>{
if (data.length != 0) { //功能存在
userModel.updateOne(
{
_id: _id,
},
{
$set: {
username: username,
password: password
}
}
).then(() => {
res.send({
code: 200,
msg: "修改成功!",
});
})
.catch(err => {
res.send(err)
});
}else{
res.send({ code: 400, msg: "用户不存在,修改失败!" });
}
}).catch((err)=>{
res.send(err)
})
}else{
res.send({
code: 401,
msg: "请先登录!"
});
}
})
//删除用户
router.delete('/delUser',commonFunc.authMiddle,(req, res) => {
const token = req.headers.authorization
if(token){
//req 请求对象
var { ids } = req.body;
// 将字符串ID转换为ObjectId
// const objectIds = ids.map(id => new ObjectId(id));
userModel.deleteMany({ _id: { $in: ids } }).then(() => {
res.send({
code: 200,
msg: "删除成功!",
});
})
.catch(err => {
res.send(err)
});
}else{
res.send({
code: 401,
msg: "请先登录!"
});
}
})
1.2、改造用户管理前端代码src/pages/userManage/index.tsx
import { register, delUser, getUserList, updateUser } from '@/services/custom/api';
import { PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components';
import {
FooterToolbar,
ModalForm,
PageContainer,
ProDescriptions,
ProFormText,
ProTable,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import { Button, Drawer,message,Form } from 'antd';
import React, { useRef, useState,useEffect } from 'react';
import type { FormValueType } from './components/UpdateForm';
import UpdateForm from './components/UpdateForm';
/**
* @en-US Add node
* @zh-CN 添加节点
* @param fields
*/
const handleAdd = async (fields: API.RuleListItem) => {
const hide = message.loading('正在添加');
try {
await register({ ...fields });
hide();
message.success('添加成功');
return true;
} catch (error) {
hide();
message.error('添加失败,请重试!');
return false;
}
};
/**
* @en-US Update node
* @zh-CN 更新节点
*
* @param fields
*/
const handleUpdate = async (fields: FormValueType) => {
const hide = message.loading('Configuring');
try {
await updateUser({
username: fields.username,
password: fields.password,
_id: fields._id,
});
hide();
message.success('修改成功');
return true;
} catch (error) {
hide();
message.error('修改失败,请稍后再试');
return false;
}
};
/**
* Delete node
* @zh-CN 删除节点
*
* @param selectedRows
*/
const handleRemove = async (selectedRows: API.RuleListItem[],actionRef:any) => {
const hide = message.loading('正在删除');
if (!selectedRows) return true;
try {
await delUser({
ids: selectedRows.map((row) => row._id),
});
hide();
message.success('删除成功');
if(actionRef){
actionRef.current?.reloadAndRest?.();
}
return true;
} catch (error) {
hide();
message.error('删除失败,请重试');
return false;
}
};
const TableList: React.FC = () => {
/**
* @en-US Pop-up window of new window
* @zh-CN 新建窗口的弹窗
* */
const [createModalOpen, handleModalOpen] = useState<boolean>(false);
/**
* @en-US The pop-up window of the distribution update window
* @zh-CN 分布更新窗口的弹窗
* */
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
const [showDetail, setShowDetail] = useState<boolean>(false);
const actionRef = useRef<ActionType>();
const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]);
/**
* @en-US International configuration
* @zh-CN 国际化配置
* */
const intl = useIntl();
const [form] = Form.useForm();
const handleDel = async (selectedRows: API.RuleListItem[],actionRef: any) => {
await handleRemove(selectedRows,actionRef);
}
useEffect(() => {
form.resetFields();
form.setFieldsValue({});
});
const columns: ProColumns<API.RuleListItem>[] = [
{
title: <FormattedMessage id="pages.searchTable.titleDesc" defaultMessage="Description" />,
dataIndex: '_id',
search:false,
valueType: 'textarea'
},
{
title: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameLabel"
defaultMessage="Rule name"
/>
),
dataIndex: 'username',
// render: (dom, entity) => {
// return (
// <a
// onClick={() => {
// setCurrentRow(entity);
// setShowDetail(true);
// }}
// >
// {dom}
// </a>
// );
// },
},
{
title: (
<FormattedMessage
id="pages.searchTable.password"
defaultMessage="Number of service calls"
/>
),
dataIndex: 'password',
sorter: false,
search:false,
hideInForm: true,
renderText: (val: string) =>
`${val}`,
},
{
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="Operating" />,
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
key="config"
onClick={() => {
setCurrentRow(record);
handleUpdateModalOpen(true);
}}
>
<FormattedMessage id="pages.searchTable.config" defaultMessage="Configuration" />
</a>,
<a
key="del"
onClick={() => {
setCurrentRow(record);
handleDel([record],actionRef)
}}
>
<FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
</a>
],
},
];
return (
<PageContainer>
<ProTable<API.RuleListItem, API.PageParams>
headerTitle={intl.formatMessage({
id: 'pages.searchTable.title',
defaultMessage: '用户列表',
})}
actionRef={actionRef}
rowKey="_id"
search={{labelWidth:150}}
toolBarRender={() => [
<Button
type="primary"
key="primary"
onClick={() => {
handleModalOpen(true);
}}
>
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
</Button>
]}
request={getUserList}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows);
},
}}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
<FormattedMessage id="pages.searchTable.chosen" defaultMessage="Chosen" />{' '}
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
<FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
</div>
}
>
<Button
onClick={async () => {
await handleRemove(selectedRowsState,'');
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}}
>
<FormattedMessage
id="pages.searchTable.batchDeletion"
defaultMessage="Batch deletion"
/>
</Button>
</FooterToolbar>
)}
<ModalForm
form = {form}
title={intl.formatMessage({
id: 'pages.searchTable.createForm.newUser',
defaultMessage: '新建用户',
})}
width="400px"
open={createModalOpen}
onOpenChange={handleModalOpen}
onFinish={async (value) => {
const success = await handleAdd(value as API.RuleListItem);
if (success) {
handleModalOpen(false);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
>
<ProFormText
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.ruleName"
defaultMessage="用户名是必填的"
/>
),
},
]}
label="用户名"
placeholder="请输入用户名"
width="md"
name="username"
initialValue={''}
/>
<ProFormText
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.placeholder"
defaultMessage="密码是必填的"
/>
),
},
]}
label="密码"
placeholder="请输入密码"
width="md"
name="password"
initialValue={''}
/>
</ModalForm>
<UpdateForm
onSubmit={async (value) => {
const success = await handleUpdate(value);
if (success) {
handleUpdateModalOpen(false);
setCurrentRow(undefined);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateModalOpen(false);
if (!showDetail) {
setCurrentRow(undefined);
}
}}
updateModalOpen={updateModalOpen}
values={currentRow || {}}
/>
<Drawer
width={600}
open={showDetail}
onClose={() => {
setCurrentRow(undefined);
setShowDetail(false);
}}
closable={false}
>
{currentRow?.name && (
<ProDescriptions<API.RuleListItem>
column={2}
title={currentRow?.name}
request={async () => ({
data: currentRow || {},
})}
params={{
id: currentRow?.name,
}}
columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]}
/>
)}
</Drawer>
</PageContainer>
);
};
export default TableList;
其中编辑组件代码components/UpdateForm
import {
ModalForm,
ProFormText
} from '@ant-design/pro-components';
import { useIntl,FormattedMessage } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { useEffect } from 'react';
export type FormValueType = {
username?: string;
password?: string;
_id?: string;
} & Partial<API.RuleListItem>;
export type UpdateFormProps = {
onCancel: (flag?: boolean, formVals?: FormValueType) => void;
onSubmit: (values: FormValueType) => Promise<void>;
updateModalOpen: boolean;
values: Partial<API.RuleListItem>;
};
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const intl = useIntl();
const [form] = Form.useForm();
useEffect(() => {
form.resetFields();
form.setFieldsValue(props.values);
});
return (
<ModalForm
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleConfig',
defaultMessage: '编辑配置',
})}
form = {form}
width="400px"
open={props.updateModalOpen}
onOpenChange={async (value) => {
if(!value){
props.onCancel(value);
}
}}
onFinish={async (value:any) => {
value._id = props.values._id
props.onSubmit(value)
}}
>
<ProFormText
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.ruleName"
defaultMessage="请输入用户名"
/>
),
}
]}
placeholder="请输入用户名"
label="用户名"
width="md"
name="username"
initialValue={props.values.username}
/>
<ProFormText
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.placeholder"
defaultMessage="请输入密码"
/>
),
}
]}
placeholder="请输入密码"
label="密码"
width="md"
name="password"
initialValue={props.values.password}
/>
</ModalForm>
);
};
export default UpdateForm;
src/service/custom/typings.d.ts代码改造为
type PageParams = {
page?: number;
limit?: number;
};
type UserListItem = {
_id?: string;
username?: string;
password?: string;
};
type UserList = {
data?: UserListItem[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};
src/service/custom/api.ts添加接口请求方法定义
/** 获取用户列表 GET /api/user/getUserList */
export async function getUserList(
params: {
// query
/** 当前的页码 */
page?: number;
/** 页面的容量 */
limit?: number;
},
options?: { [key: string]: any },
) {
return request<API.UserList>('/api/user/getUserList', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 更新用户信息 PUT /api/user/changeUser */
export async function updateUser(options?: { [_id: string]: any }) {
return request<API.UserListItem>('/api/user/changeUser', {
method: 'POST',
data: {
...(options || {}),
},
});
}
/** 删除用户 DELETE /api/user/delUser */
export async function delUser(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/user/delUser', {
method: 'delete',
data: {
...(options || {}),
},
});
}
四、注意要点
1、ModalForm下的ProFormText设置默认值initialValue只更新一次的问题
解决方法:
import { Form } from 'antd';
import { useEffect } from 'react';
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const intl = useIntl();
const [form] = Form.useForm();
useEffect(() => {
form.resetFields();
form.setFieldsValue(props.values);
});
return (
<ModalForm
form = {form}
>
<ProFormText
initialValue={props.values.username}
/>
</ModalForm>
)
}
2、隐藏不想要显示搜索框的列数据
解决方法:
columns中配置search:false
const columns: ProColumns<API.UserListItem>[] = [
{
title: <FormattedMessage id="pages.searchTable.titleDesc" defaultMessage="Description" />,
dataIndex: '_id',
search:false,
valueType: 'textarea'
}
]
五、最终效果如下