0、前言
使用umi3和antd pro5从零实现全栈中后台管理系统
0-1、涉及技术栈
前端: TS 、 React、React Hooks、 umi3、antd-pro5后端: express、mongodb、jwt**
0-2、实现的功能
- 后端用户鉴权
- 前端权限管理
- 用户密码加密
- 封装一套通用弹窗表单组件,实现新建、修改、详情功能
- 用户登录注册(首次需要后端自己添加一条用户登录信息)
0-1、涉及技术栈
前端: TS 、 React、React Hooks、 umi3、antd-pro5后端: express、mongodb、jwt**
0-2、实现的功能
- 后端用户鉴权
- 前端权限管理
- 用户密码加密
- 封装一套通用弹窗表单组件,实现新建、修改、详情功能
- 用户登录注册(首次需要后端自己添加一条用户登录信息)
1、初始化前端项目
yarn create umi myapp npm i npm run dev
1、设置config下的proxy代理
dev: { '/api/': { target: "http://localhost:3000", changeOrigin: true, pathRewrite: { '^': '' }, }, },
2、登录
修改src/service/login.ts 接口改为/api/user/login
export async function fakeAccountLogin(params: LoginParamsType) { return request<API.LoginStateType>('/api/user/login', { method: 'POST', data: params, }); }
存储token pages/user/login/index.tsx
localStorage.setItem('token' , msg.token)
使用token services/user.ts
export async function queryCurrent() { return request<API.CurrentUser>('/api/currentUser', headers: { Authorization : 'Bearer ' + `${localStorage.getItem('token') }` } }
每次请求都带上token src/app.tsx
export const request: RequestConfig = { errorHandler, headers: { Authorization : 'Bearer ' + `${localStorage.getItem('token')}` } };
退出 RightContent/AvatarDropdown.tsx
localStorage.removeItem('token')
3、pro5参考文档
procomponents.ant.design/components/…
4、实现一个用户管理
5、列表页
pages/ListTableList/index.tsx
import { PlusOutlined } from '@ant-design/icons'; import { Button, Divider, message, Avatar } from 'antd'; import React, { useState, useRef } from 'react'; import { PageContainer, FooterToolbar } from '@ant-design/pro-layout'; import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; import HandleForm from './components/HandleForm'; import { TableListItem } from './data.d'; import { queryRule, updateRule, addRule, removeRule } from './service'; import moment from 'moment'; /** * 操作提交 * @param fields */ const handleSubmit = async (_id?: string, fields?: TableListItem) => { let title = _id ? '修改' : '新增'; const hide = message.loading(`正在${title}`); try { if (_id) { await updateRule({ _id, ...fields, }); } else { await addRule({ ...fields }); } hide(); message.success(`${title}成功`); return true; } catch (error) { hide(); message.error(`${title}失败`); return false; } }; /** * 删除节点 * @param selectedRows */ const handleRemove = async (selectedRows: string[], _id: string) => { // console.log(selectedRows,_id,'selectedRows>>>>') const hide = message.loading('正在删除'); // return try { await removeRule({ _id: _id ? _id : selectedRows, }); hide(); message.success('删除成功'); return true; } catch (error) { hide(); message.error('删除失败'); return false; } }; const TableList: React.FC<{}> = () => { const [modalVisible, handleModalVisible] = useState<boolean>(false); const [currentInfo, handleSaveCurrentInfo] = useState<TableListItem | null>(null); const [isDetail, setDetail] = useState<boolean>(false); const actionRef = useRef<ActionType>(); const [selectedRowsState, setSelectedRows] = useState<any>([]); const columns: ProColumns<TableListItem>[] = [ { title: '用户名', dataIndex: 'username', }, { title: '密码', dataIndex: 'password', hideInDescriptions: true, //详情页不显示 hideInTable: true, }, { title: '角色', dataIndex: 'access', search: false, filters: [ { text: '普通用户', value: 'user' }, { text: '管理员', value: 'admin' }, ], valueEnum: { user: { text: '普通用户' }, admin: { text: '管理员' }, }, }, { title: '_id', dataIndex: '_id', sorter: true, hideInForm: true, search: false, }, { title: '头像', dataIndex: 'avatar', search: false, hideInForm: true, render: (dom, entity) => { return <Avatar src={entity.avatar} alt="" />; }, }, { title: '邮箱', dataIndex: 'email', }, { title: '更新时间', dataIndex: 'updatedAt', sorter: true, hideInForm: true, search: false, renderText: (val: string) => { if (!val) return ''; return moment(val).fromNow(); // 绝对时间转化成相对时间 }, }, { title: '创建时间', dataIndex: 'createdAt', sorter: true, hideInForm: true, search: false, valueType: 'dateTime', }, { title: '操作', dataIndex: 'option', valueType: 'option', render: (_, record) => ( <> <a href="javascript:;" onClick={() => { handleModalVisible(true), handleSaveCurrentInfo(record); }} > 修改 </a> <Divider type="vertical" /> <a href="javascript:;" onClick={() => { handleModalVisible(true), handleSaveCurrentInfo(record), setDetail(true); }} > 详情 </a> <Divider type="vertical" /> <a href="javascript:;" onClick={async () => { await handleRemove([], record._id as 'string'); // 刷新 actionRef.current?.reloadAndRest?.(); }} > 删除 </a> </> ), }, ]; return ( <PageContainer> <ProTable<TableListItem> headerTitle="用户管理" actionRef={actionRef} rowKey="_id" search={{ labelWidth: 120, }} toolBarRender={() => [ <Button type="primary" onClick={() => handleModalVisible(true)}> <PlusOutlined /> 新增 </Button>, ]} request={(params, sorter, filter) => queryRule({ ...params, sorter, filter })} columns={columns} form={{ submitter: false, }} pagination={{ defaultPageSize: 5 }} rowSelection={{ onChange: (selected, selectedRows) => { setSelectedRows(selected); }, }} /> <HandleForm onCancel={() => { handleModalVisible(false), handleSaveCurrentInfo({}), setDetail(false); }} modalVisible={modalVisible} values={currentInfo} isDetail={isDetail} onSubmit={async (values) => { const success = await handleSubmit(currentInfo?._id, values); if (success) { handleModalVisible(false); if (actionRef.current) { actionRef.current.reload(); } } }} ></HandleForm> {selectedRowsState?.length > 0 && ( <FooterToolbar extra={ <div> 已选择 <a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a> 项 </div> } > <Button onClick={async () => { await handleRemove(selectedRowsState, ''); setSelectedRows([]); actionRef.current?.reloadAndRest?.(); }} > 批量删除 </Button> </FooterToolbar> )} </PageContainer> ); }; export default TableList;
pages/ListTableList/data.d.ts
export interface TableListItem { _id?: string; username?: string; password?: string; avatar?: string; access?: string; email?: string; } export interface TableListPagination { total: number; pageSize: number; current: number; } export interface TableListData { list: TableListItem[]; pagination: Partial<TableListPagination>; } export interface TableListParams { _id?: string; username?: string; password?: string; avatar?: string; access?: string; email?: string; pageSize?: number; currentPage?: number; filter?: { [key: string]: any[] }; sorter?: { [key: string]: any }; }
pages/ListTableList/service.ts
import { request } from 'umi'; import { TableListParams } from './data.d'; export async function queryRule(params?: TableListParams) { return request('/api/user/account', { params, }); } export async function removeRule(params: { _id: string|string[] }) { return request('/api/user/account', { method: 'DELETE', data: params }); } export async function addRule(params: TableListParams) { return request('/api/user/account', { method: 'POST', data: { ...params }, }); } export async function updateRule(params: TableListParams) { return request(`/api/user/account?_id=${params._id}`, { method: 'PUT', data: { ...params }, }); }