455 lines
12 KiB
Markdown
455 lines
12 KiB
Markdown
# *coder station* 后台管理系统笔记
|
||
|
||
|
||
|
||
## 项目准备
|
||
|
||
这一次项目会有一些前置知识需要学习:
|
||
|
||
- *Antd pro*
|
||
- *Dva*
|
||
- *UmiJS 4.0*
|
||
|
||
|
||
|
||
## 项目笔记
|
||
|
||
|
||
|
||
1. 如何修改项目的端口号
|
||
|
||
在项目根目录下面创建一个 *.env* 文件,之后就可以配置端口号之类的内容
|
||
|
||
参阅文档:*https://umijs.org/docs/guides/directory-structure#env*
|
||
|
||
|
||
|
||
2. 关于路由的配置,需要参阅 *Antd pro* 这个文档的“新增页面”部分的内容
|
||
|
||
文档:*https://pro.ant.design/zh-CN/docs/new-page*
|
||
|
||
如果想要某个页面不出现在左侧的导航栏中,可以配置 *hideInMenu:true*
|
||
|
||
|
||
|
||
3. 配置代理服务器直接在 *umirc.js* 中进行配置
|
||
|
||
文档:*https://umijs.org/docs/api/config#proxy*
|
||
|
||
例如:
|
||
|
||
```js
|
||
proxy : {
|
||
"/api" : {
|
||
target : "http://127.0.0.1:7001",
|
||
changeOrigin : true,
|
||
},
|
||
"/static" : {
|
||
target : "http://127.0.0.1:7001",
|
||
changeOrigin : true,
|
||
},
|
||
"/res" : {
|
||
target : "http://127.0.0.1:7001",
|
||
changeOrigin : true,
|
||
}
|
||
},
|
||
```
|
||
|
||
|
||
|
||
4. 当我们使用 *antd* 里面的表格的时候,和 *Element-ui* 不同的是,在 *antd* 中的表格需要配置每一列
|
||
|
||
例如:
|
||
|
||
```js
|
||
// 对应表格每一列的配置
|
||
const columns = [{
|
||
title : "登录账号",
|
||
dataIndex : "loginId",
|
||
key : "loginId",
|
||
align : "center"
|
||
}];
|
||
```
|
||
|
||
具体请参阅文档:*https://ant.design/components/table-cn/#Column*
|
||
|
||
注意,在配置列的时候,有一些列选项是输属于 *procomponents* 新增的,所以有些属性我们需要参阅 *procomponents* 的文档
|
||
|
||
文档:
|
||
|
||
- *columns* 列定义:*https://procomponents.ant.design/components/table#columns-%E5%88%97%E5%AE%9A%E4%B9%89*
|
||
|
||
- *valueType* 对应的值:*https://procomponents.ant.design/components/schema#valuetype-%E5%88%97%E8%A1%A8*
|
||
|
||
如果是单纯的渲染某一个值,那么直接配置 *dataIndex* 即可,但是很多时候,我们是根据数据对应的值渲染成其他的东西,例如:
|
||
|
||
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-11-18-064046.png" alt="image-20221118144046443" style="zoom:50%;" />
|
||
|
||
那么这个时候,咱们就需要使用到 *render*。
|
||
|
||
如果不要搜索,可以将搜索选项关闭:*search:false*
|
||
|
||
|
||
|
||
5. 设置全局的 *CSS* 样式
|
||
|
||
在 *src* 目录下面创建一个 *global.css* 的文件,该 *CSS* 文件就是一个全局的样式:
|
||
|
||
*https://umijs.org/docs/guides/directory-structure#globalcsslesssassscss*
|
||
|
||
|
||
|
||
6. 如何回填表单的值
|
||
|
||
我们在修改的时候,经常会涉及到回填表单的值,在 *ant design* 里面,使用 *setFieldsValue*
|
||
|
||
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-11-22-074057.png" alt="image-20221122154057171" style="zoom:50%;" />
|
||
|
||
|
||
|
||
7. 关于发送请求获取数据
|
||
|
||
有两种处理方式:
|
||
|
||
- 通过 *dispatch* 一个 *action* 到状态仓库,然后状态仓库来发请求,请求回来的数据放入到数据仓库中(管理员模块)
|
||
- 适用于数据量不多
|
||
- 多个组件要共享某一块数据
|
||
- 直接在组件里面请求数据
|
||
- 数据量很大,在向服务器发送请求的时候,只能分页请求
|
||
- 不需要和其他组件共享
|
||
|
||
|
||
|
||
8. 关于在 *ProTable* 组件中使用 *request* 发送请求
|
||
|
||
*ProTable* 有一个重要的属性叫做 *request*,该属性对应的值一般是一个异步函数,该异步函数自动接受一个 *params*,*params* 中会有默认的当前页码(*current*)和每一页的条数(*pageSize*),这两个值会有默认值,*current* 默认为 *1*,*pageSize* 默认为 *20*,可以通过配置 *pagination* 属性来修改 *current* 和 *pageSize* 的值
|
||
|
||
```js
|
||
<ProTable
|
||
headerTitle="用户列表"
|
||
pagination={{
|
||
showQuickJumper: true,
|
||
showSizeChanger: true,
|
||
pageSizeOptions: [5, 10, 15, 20],
|
||
...pagination,
|
||
onChange: handlePageChange
|
||
}}
|
||
request={async (params) => {
|
||
console.log(params);
|
||
}}
|
||
/>
|
||
```
|
||
|
||
|
||
|
||
9. 刷新表格
|
||
|
||
获取到表格的实例(通过 *ref*),注意这里是 *actionRef*,然后调用 *reload* 方法
|
||
|
||
```react
|
||
<ProTable
|
||
actionRef={tableRef}
|
||
...
|
||
/>
|
||
```
|
||
|
||
```js
|
||
tableRef.current.reload();
|
||
```
|
||
|
||
请参阅:*https://procomponents.ant.design/components/table/#actionref-%E6%89%8B%E5%8A%A8%E8%A7%A6%E5%8F%91*
|
||
|
||
|
||
|
||
10. 如何新增不再导航栏显示的页面
|
||
|
||
只需要一个配置项即可
|
||
|
||
```js
|
||
hideInMenu : true
|
||
```
|
||
|
||
更多的配置项,请参阅:*https://pro.ant.design/zh-CN/docs/new-page*
|
||
|
||
|
||
|
||
11. *Warning: Cannot update a component (`InternalFormItem`) while rendering a different component (`UserForm`).*
|
||
|
||
该警告出现的原因,是因为在初次渲染组件的时候,我们设置了数据的回填,导致组件初次还没有渲染完毕,又在更新
|
||
|
||
如何解决,也非常简单,我们等待第一次渲染完毕后再进行数据的回填,所以我们将回填的代码放入 *useEffect*
|
||
|
||
```js
|
||
useEffect(() => {
|
||
if (formRef.current) {
|
||
formRef.current.setFieldsValue(userInfo);
|
||
}
|
||
}, [userInfo]);
|
||
```
|
||
|
||
|
||
|
||
12. 关于使用 *markdown* 编辑器做修改操作时光标跳转的问题
|
||
|
||
该问题的出现是因为对应的组件在重新渲染时,*markdown* 编辑器回填了数据多次
|
||
|
||
要解决这个问题也很简单,我们只需要设置一个状态值,第一次 *markdown* 回填了数据后,之后就不再让编辑器回填数据
|
||
|
||
```js
|
||
useEffect(()=>{
|
||
if(formRef.current && firstIn && bookInfo){
|
||
formRef.current.setFieldsValue(bookInfo);
|
||
// 关键就是关于编辑器的回填
|
||
editorRef.current.getInstance().setHTML(bookInfo?.bookIntro);
|
||
// 将 firstIn 设置为 false
|
||
setFirstIn(false);
|
||
}
|
||
if(formRef.current){
|
||
formRef.current.setFieldsValue(bookInfo);
|
||
}
|
||
},[bookInfo])
|
||
```
|
||
|
||
|
||
|
||
13. 关于登录页面的 Canvas 动画,使用到的是一个第三方库,叫做 *react-canvas-nest*
|
||
|
||
*https://www.npmjs.com/package/react-canvas-nest*
|
||
|
||
```react
|
||
<ReactCanvasNest
|
||
config={{
|
||
pointColor: '255, 0, 0',
|
||
count: 66,
|
||
follow: false,
|
||
}}
|
||
style={{ zIndex: 1 }}
|
||
/>
|
||
```
|
||
|
||
|
||
|
||
14. 配置初始化数据
|
||
|
||
在 umi 的运行时配置(*app.js/ts*)中,有一个叫做 *getInitialState* 方法,该方法可以配置一些初始化的数据,回头在其他组件中通过 *useModel* 来获取你返回的初始化数据
|
||
|
||
*https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81*
|
||
|
||
后台管理系统导航守卫逻辑如下:
|
||
|
||
```js
|
||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||
// 更多信息见文档:https://next.umijs.org/docs/api/runtime-config#getinitialstate
|
||
export async function getInitialState() {
|
||
if (location.pathname === '/login') {
|
||
// 强行跳登录页
|
||
// 判断是否有有效的 token
|
||
const token = localStorage.getItem('adminToken');
|
||
if (token) {
|
||
const result = await AdminController.getInfo();
|
||
console.log(result, 'result');
|
||
if (result.data) {
|
||
// 不仅有 token,而且 token 是有效的
|
||
// 不允许你去 login
|
||
message.warning('请先退出后在登录');
|
||
history.go(-1);
|
||
}
|
||
}
|
||
} else {
|
||
// 强行要跳内部页面
|
||
const result = await AdminController.getInfo();
|
||
if (result.data) {
|
||
// 说明有 token,并且 token 有效
|
||
// 获取该 id 对应的管理员信息
|
||
const { data } = await AdminController.getAdminById(result.data._id);
|
||
// 这里返回的就是一个全局的初始化数据
|
||
// 之后各个组件都可以通过 useModel 获取到该初始数据
|
||
return {
|
||
name: data.nickname,
|
||
avatar: data.avatar,
|
||
adminInfo: data,
|
||
};
|
||
} else {
|
||
// token 验证失败,跳转至登录
|
||
// 失效可能是因为 token 过期,也有可能是因为压根儿就没有 token,不管有没有,删除掉原有的
|
||
localStorage.removeItem("adminToken");
|
||
location.href = "/login";
|
||
message.warning('请重新登录');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
15. 配置请求和响应拦截器也是在 *app.js/ts* 运行时配置中进行配置
|
||
|
||
*https://umijs.org/docs/api/runtime-config#request*
|
||
|
||
```js
|
||
export const request = {
|
||
timeout : 3000,
|
||
// 请求拦截器
|
||
requestInterceptors: [function(url, options){
|
||
// 从本地获取 token
|
||
const token = localStorage.getItem("adminToken");
|
||
if(token){
|
||
options.headers['Authorization'] = "Bearer " + token;
|
||
}
|
||
return {url, options};
|
||
}]
|
||
}
|
||
```
|
||
|
||
|
||
|
||
16. 退出登录
|
||
|
||
退出登录只需要在运行时配置文件 *app.js/ts* 的 *layout* 里面书写 *logout* 对应的回调函数逻辑即可:
|
||
|
||
```js
|
||
logout : ()=>{
|
||
// 删除本地 token
|
||
localStorage.removeItem("adminToken");
|
||
// 跳转到登录页面
|
||
location.href = "/login";
|
||
message.success('退出登录成功');
|
||
}
|
||
```
|
||
|
||
|
||
|
||
17. 关于权限
|
||
|
||
*https://umijs.org/docs/max/access*
|
||
|
||
首先需要在构建时配置文件 *umirn.js/ts* 中启动 *access*,之后在 *src* 目录下面创建一个 *access.js/ts* 文件
|
||
|
||
接下来在路由配置中,为每一个路由配置对应权限,例如:
|
||
|
||
```js
|
||
{
|
||
name: '首页',
|
||
path: '/home',
|
||
component: './Home',
|
||
icon : "HomeOutlined",
|
||
access : "NormalAdmin" // 普通管理员能够访问
|
||
},
|
||
{
|
||
name : "管理员",
|
||
path : "/admin",
|
||
icon: 'UserOutlined',
|
||
access : "SuperAdmin", // 超级管理员能够访问
|
||
...
|
||
},
|
||
```
|
||
|
||
最后在 *access.js* 文件中,根据登录的账户的 *permission* 来确定返回的对象
|
||
|
||
```js
|
||
// 在该函数中,我们需要返回一个对象,对象里面对应一个一个权限项目,每个权限项目对应的值是一个布尔值
|
||
// true 代表有权限 false 代表没有权限
|
||
|
||
// 假设现在是超管登录 adminInfo.permission ---> 1
|
||
// { SuperAdmin : true, NormalAdmin : true}
|
||
// 假设现在登录的是普通管理员 adminInfo.permission ---> 2
|
||
// { SuperAdmin : false, NormalAdmin : true}
|
||
|
||
if (initialState) {
|
||
return {
|
||
SuperAdmin: initialState.adminInfo.permission === 1,
|
||
NormalAdmin:
|
||
initialState.adminInfo.permission === 1 ||
|
||
initialState.adminInfo.permission === 2,
|
||
};
|
||
} else {
|
||
return {
|
||
SuperAdmin : false,
|
||
NormalAdmin : false
|
||
}
|
||
}
|
||
```
|
||
|
||
针对页面中某一块区域如果要设置权限,那么可以通过 *useAccess* *hook* 函数获取到当前的权限对象(*access.js* 中我们返回的对象)
|
||
|
||
之后通过 *Access* 组件包裹有权限的区域,设置 *accessible* 属性即可
|
||
|
||
```react
|
||
<Access accessible={access.SuperAdmin}>
|
||
//...
|
||
</Access>
|
||
```
|
||
|
||
|
||
|
||
18. 在页面中获取全局初始化数据
|
||
|
||
可以通过 *useModel* 来进行获取,示例如下:
|
||
|
||
```js
|
||
const { initialState } = useModel("@@ininitialState");
|
||
```
|
||
|
||
|
||
|
||
19. 关于首页的 Echarts
|
||
|
||
*https://umijs.org/docs/max/charts*
|
||
|
||
首先安装 *Echart* 相关的依赖:
|
||
|
||
```js
|
||
npm install @ant-design/charts
|
||
```
|
||
|
||
之后引入对应的图表,做好数据配置,直接使用即可。
|
||
|
||
具体可以使用的图表类型可以参阅:*https://charts.ant.design/*
|
||
|
||
|
||
|
||
20. *mf-dep____vendor.0d0d1aca.js:389066 Warning: [antd: Dropdown] `overlay` is deprecated. Please use `menu` instead.*
|
||
|
||
该问题是 *umi.js* 内部的问题,并非我们的代码,随着 *umi.js* 的升级,期望后期官方能够修复这个问题。
|
||
|
||
可以参阅:*https://github.com/ant-design/pro-components/issues/6162*
|
||
|
||
关于运行时配置中,*layout* 能够配置的项目,可以参阅:*https://procomponents.ant.design/components/layout/#prolayout*
|
||
|
||
|
||
|
||
## 项目总结
|
||
|
||
- *dva*
|
||
- *antd* 组件库以及 *antd pro* 后台集成方案
|
||
- *umi.js 4.x*
|
||
|
||
整个项目使用到的是 *react* 技术栈,前端路由使用到了 *react-router*,数据流方案采用的是基于 *redux* 和 *redux-saga* 的 *dva*,整体项目框架采用 *umijs.4.x* 搭建,里面使用到了 *antd pro* 后台集成解决方案。
|
||
|
||
|
||
|
||
|
||
|
||
## 后期课程安排
|
||
|
||
- 入门篇
|
||
- *React* 核心知识(你必须要掌握的)
|
||
- 基础语法
|
||
- *Hooks*
|
||
- 周边库
|
||
- *router*
|
||
- *redux*
|
||
- **课程短、学习快、只讲最必要的知识**
|
||
- 就业篇
|
||
- 类似于三阶段的笔面试题
|
||
- 大致目录如下
|
||
- 官方文档的进阶部分
|
||
- 高级概念
|
||
- 事件系统
|
||
- 渲染流程
|
||
- *React* 架构
|
||
- *v18* 的一些新特性
|
||
- 常见应用场景
|
||
- 就业篇会长期保持更新
|
||
- 源码篇
|