# react_admin
**Repository Path**: ofkangk/react_admin
## Basic Information
- **Project Name**: react_admin
- **Description**: React的后台管理项目
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-01-18
- **Last Updated**: 2022-02-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 1. Login组件的背景问题
1. login.less
```less
.login {
width: 100%;
height: 100%;
background-image: url("./images/bg.jpg");
background-size: 100% 100%;
.login-header {
}
.login-content {
}
}
```
使用后,body、root节点的高度为0,需要在public/css/reset.css中增加
```css
html, body {
width:100%;
height:100%;
}
#root {
width:100%;
height:100%;
}
```
2. 上述修改后,Login组件高度还是0.是由于App组件中的BrowserRouter外层有一个div,去掉即可
# 2. antd4.18 Form表单的验证
1. 声明式验证:在`Form.Item`中使用rules属性。rules值是一个列表,列表中是规则对象。
2. 自定义验证:在rules列表中使用对象属性`validator`,其值是一个函数(rules,value)=>Promise。
3. 当某一规则校验不通过时,是否停止剩下的规则的校验:`validateFirst`:Boolean|parallel(parallel开启并行校验)
4. `Form`的`onFinish`和`onFinishFailed`:校验成功和失败后的回调,用于发送ajax请求
# 3. 封装axios异步请求
1. 封装`ajax.js`模块:
能发送异步请求ajax的函数模块
- 封装axios库
- 函数的返回值是Promise对象
2. 包含应用中所有请求接口函数的模块,采用分别暴露
3. 使用代理解决跨域问题:脚手架自带的代理服务器,仅需要配置对应地址即可。(在package.json中添加`proxy`)
4. 使用async和await:
- 作用:简化promise对象:不再使用`.then()`在指定成功/失败的回调
以同步编码方式(没有了回调函数)实现异步流程
- await的使用位置:在返回promise对象的表达式左侧使用await,可以得到promise请求成功的value数据
- async使用的位置:await所在函数(最近的)定义的左侧写async
5. 优化:统一处理请求异常
- 在封装的ajax请求中统一处理
- 在外层包一个自己创建的promise
- 请求出错时,不reject(),直接提示出错
# 4. 登录成功后跳转到管理界面
1. 登录成功的判断
2. 跳转界面:登录界面不应该可以回退,所以应该使用replace(v6不在使用histroy,这里回退到v5)
3. 优化ajax请求:请求成功时直接resolve(response.data)
4. 将登录的用户信息保存到内存中:新建`/utils/memoryUtil.js`,用于保存数据
# 5. 维持登录和自动登录
1. 登录后,刷新依然是登录状态(维持登录)
- 利用浏览器的localstorage,将用户信息保存到local(local可以保存多个信息)
- 新建/utils/storageUtil.js:
实现以下功能:localStorage的参数是:key、value都是字符串。而实际value——user是对象,需要用到JSON
1. 保存用户信息
2. 读取用户信息
3. 删除用户信息
4. 可以使用`store`库便捷实现
- 在`Login`组件中,请求成功时将user保存到local
- 每次刷新都是重新加载`index.js`,要维持登录,需要在`index.js`中,将local中的数据同步到内存中
2. 登录后,关闭浏览器后打开浏览器访问依然是登录状态(自动登录)
- 完成1中内容,即可实现。
3. 登录后,访问登录路径自动跳转到管理界面
- 在`Login`组件的`render`中,增加判断:
- 如果local中有信息,则直接跳转到`Admin`组件
# 6. 管理界面
## 6.1 管理界面布局
1. 布局:使用`antd`的`layout`布局,并创建导航组件`LeftNav`和头部组件`Header`
2. 在导航组件`LeftNav`中添加`Menu`导航菜单,注意菜单、菜单项、子菜单的关系。
3. 创建路由组件
```
/home # 首页路由组件
- home.jsx
/category # 商品分类路由组件
- category.jsx
/ product # 商品管理路由组件
- product.jsx
/ role # 角色路由组件
- role.jsx
/ user # 用户路由组件
- user.jsx
/ charts # 图形路由
- bar.jsx # 柱状图
- line.jsx # 折线图
- pie.jsx # 饼图
```
4. 将路由组件映射成路由:在`Admin`组件的`Content`标签中配置路由
5. 在导航栏建立路由链接
## 6.2菜单项的生成
6. 使用数组动态生成菜单
- 抽取每个菜单项的关键信息,建立对象列表
- 二级子菜单项作为嵌套列表
```js
menuList = [
{
key,
icon,
title
},
{
key,
icon,
title,
children:[]
}
]
```
- 使用数组map或者reduce函数,将数据数组转换为标签数组
## 6.3 选中菜单项
7. 自动选中当前菜单项
- 获取当前路径
1. 需要先将`LeftNav`组件包装成路由组件
2. `const path = this.props.location.pathName`
- `defaultSelectedKeys`设置菜单默认选中项:仅在第一次`render`时生效。直接请求根路径时,先跳转到`/`,再跳转到`/home`,因此失效
- `selectedKeys`当前选中项:每次`render`都会生效
## 6.4 打开子菜单
8. 自动打开选中了子菜单项的子菜单
- 如果选中了子菜单项,才需要打开相应的子菜单:判断是否打开了子菜单项
> 用路由路径path与子菜单children中的key进行对比,返回子菜单的key
- 使用`defaultOpenKeys`初始展开的 `SubMenu` 菜单项 `key` 数组
- 要在初始展开之前,获取子菜单的`key`:必须在render前项生成菜单节点
# 7. Header组件
1. 划分区域:分为上下等大的两部分,中间有分界线
- 上半部分:表示欢迎以及退出登录
- 下半部分:表示当前菜单项标题和天气
2. 动态显示时间和天气、
- 创建获取天气的jsonp请求
- 在`Header`组件中是创建`state`保存当前时间、天气
- 在`DidMount`钩子内,定时更新时间和天气
3. 动态显示当前标题
- 获取当前路由路径path
- 遍历menuList,根据path获取相应的title
4. 退出登录
- `antd`的对话框
- 卸载组件前,清除定时器
- 路由跳转
5. 用封装的LinkButton代替a标签
# 8. 完善导航栏的路由组件
## 8.1 Home路由组件
1. 简化的`Home`组件:居中显示欢迎
- `less`样式:
```less
display:flex; // 水平布局
align-items:center;// 垂直居中
jusitify-content:center;// 水平居中
```
## 8.2 category组件
1. 静态的`category`组件
- 添加`antd`中的`Card`组件
1. `Card`组件头部的左侧为标题,右侧为链接按钮
2. `Card`组件内部为`Table`组件
2. 请求qpi:
- 获取一、二级分类
- 添加分类
- 更新品类名称
3. 异步显示一级分类列表
- 使用`state`驱动组件更新
- 在DidMount钩子中,发送异步请求,将返回的数据保存到`state`中
- 配置`Table`组件的`loading`和分页器`Pagination`(分页器的文字无法改中文)
4. 异步显示二级分类列表
- 点击链接按钮获取二级分类列表数据
- 异步更新状态重新render
- 控制子列表操作列的按钮显示
5. 完善`Category`组件
- 返回上一级:重新获取一级列表
- 添加功能:使用`antd`中的`Madol`确认框
1. `AddForm`中的下拉框选项:由父组件将`categories`传给子组件
2. 子列表中添加时,表单默认值为父分类的名称
3. 只有在当前列表中添加分类时,才需要刷新列表
- 修改功能:
1. 点击Ok是隐藏确认框
2. 准备数据,发送ajax请求
- 使用实例变量保存数据
- 子组件的Form表单值传递给父组件:使用refs传递Form对象
3. 重新发送`reqCategoryList`请求获取最新的分类列表
4. 点击确认和取消时,清除`Input`中默认值
- 表单验证:只有表单验证通过的时候才能按下确认键
## 8.3 Product组件
### 1. 搭建整体路由
1. 创建三个子路由:
- Home
- Detail
- AddUpdate
2. 搭建路由:
```jsx
{/* 开启精准匹配 */}
```
3. 精准匹配路由和重定向
- 路由默认是逐层匹配:当前面一层匹配到时,后面就不再将继续匹配
- 重定向:将匹配不到的路由重新定位到指定路由
### 2. Home子路由组件
#### 2.1 静态界面
1. 划分界面:
- 整体是一个`Card`组件
- `Card`组件头部左侧:title属性
- `Select`
- `Input`
- `Button`
- `Card`组件头部右侧:extra属性
- 带图标的`Button`
- `Card`组件内部:`Table`
2. 填充组件
- title、extra属性的参数是一个ReactNode
- Table属性:
1. bordered:边框
2. dataSource:行数据。对象数组,每个对象代表一个行数据
3. columns: 列样式数据。对象数据,每个对象描述表格中一列样式。
#### 2.2. 分页列表
| | 纯前台的分页 | 基于后台的分页 |
| ---------- | ----------------------------------------------------- | --------------------------------------------------- |
| 请求的数据 | 一次性获取所有数据,翻页时不需要再请求 | 每次只获取当前页的数据,翻页时要发请求 |
| 请求的接口 | 不需要指定请求参数:页码(pageNum)和每页数量(pageSize) | 需要指定请求参数:页码(pageNum)和每页数量(pageSize) |
| 响应的数据 | 所有数据的数组 | 当前页数据的数组+总记录数(total) |
#### 2.3. 异步分页列表
1. 接口请求函数`reqProducts`
2. 在组件完成挂载后异步请求商品列表
3. 设置`Table`组件的分页器属性`pagination`
#### 2.4. 搜索分页
1. 测试api:同一个请求可以根据不同的参数(`productName`/`productDesc`)可以实现两种搜索请求
2. 编写请求函数:
- 两个请求函数:简单
- 一个请求函数:多接受一个控制请求类型的参数
3. 将`Select`和`Input`组件变成受控组件(实时获取数据)
- `Option`的value属性调整为与`searchtype`对应
- 受控组件:
1. 在`state`中添加收集数据的属性
2. 实时监控组件变化:`onChange={value=>{this.setState({searchType:value})}}`
### 3. Detail子路由组件
#### 3.1 静态页面
1. 整体是一个`Card`
2. 下面是一个`List`
3. ``React`标签的`dangerouslySetInnerHTML`属性:允许使用原生的`html`标签
#### 3.2 动态显示商品详情
1. 路由跳转:使用`push`、`replace`进行路由跳转时,是可以携带对象参数传递给目标路由的(位于目标路由的`location.state`中)
2. 取出`Home`路由传递的对象属性,替换掉节点中的值
3. 异步显示商品分类
1. 编写根据ID获取商品分类的Api
2. 当`Detail`组件完成挂载时,发送请求获取分类信息
3. 将响应的数据保存到`state`中。(因为数据是异步请求获取的,在完成一次`render`后才能获取数据。如果仅适用类变量保存,那么当得到数据后,无法再驱动节点刷新;而保存在`state`中,可以使用`setState`再得到响应数据后再刷新节点)
3. 使用`await`进行多个`ajax`请求时,下一个请求会等待上一个请求完成后才会进行。想要进行同时进行多个`ajax`请求,可以使用`Promise.all([请求列表])`,返回响应列表
#### 3.3 动态更新商品状态(上架/下架)
1. 根据商品状态显示不同的节点
2. 编写更新商品状态的api
3. 点击按钮时,根据所在行的当前状态发送请求,获取当前页码对应的商品列表
4. 每次请求商品列表时,保存页码,方便其他方法使用
#### 3.4 进入Product子路由时导航栏的问题
1. 导航栏`LeftNav`组件不自动选中
2. 导航栏`LeftNav`组件不自动展开
3. 问题原因:因为是原先是按照完全的路由匹配的`Menu`中的`selectedKeys={[**path**]}`和`defaultOpenKeys`
4. 解决方法:只要`path`的开头包含`/product`即可(`path.indexOf('/product'===0)`)
### 4. AddUpdate组件
路由跳转:点击添加按钮跳转到`AddUpdate`组件
#### 4.1 静态页面
1. 布局
- `Card`
- `Icon`
- `Form`
- `Input`
- `Input.TextArea`
#### 4.2 表单验证
1. 声明式验证:`rules={[验证规则配置对象]}`
2. 自定义验证:`validator:(rule,value)=>Promise`
3. 提交时验证:
- 按钮绑定表单:在按钮中添加属性`htmlType="submit"`(一般表单提交时会接收event参数,并默认跳转页面,需要阻止跳转。但是antd4.x中Form只需要绑定`onFinish`即可,当所有验证通过时,按下按钮就会执行。)
- 按钮不绑定表单:此时不需要阻止页面跳转,但是需要使用`form`属性收集表单信息(antd4.x中使用`refs`获取表单实例信息)
4. 验证数字输入框中的数字大小:使用自定义验证
```jsx
validateNum = (rule, value) => {
if (value * 1 > 0) {
return Promise.resolve() // 验证通过的时的回调
} else {
return Promise.reject('商品价格不能低于0') // 验证失败时的回调
}
}
```
5. 当某一规则校验不通过时,是否停止剩下的规则的校验。:`Form.Item`的`validateFirst`属性
#### 4.3 静态的商品分类项
使用`Cascader`级联选择,选择动态加载项
1. `options`属性
1. `option`对象:
1. `value`
2. `label`
3. `isLeaf`
4. `children`
5. `disabled`
2. `loadData`属性:点击选项时的加载回调
- 接收参数:`selectedOptions`
- 回调中完成以下事情:
1. 获取当前选项对象:`const targetOption = selectedOptions[0]`
2. 显示加载动画:`targetOption.loading = true`
3. 完成异步任务
4. 取消加载动画:`targetOption.loading = false`
5. 设置`targetOption`的子选项列表(实际上修改了`options`属性值)
6. 更新状态,驱动页面更新
#### 4.4 商品分类显示一级分类列表
1. 需要异步请求数据:
- 使用`state`保存数据
- 在`componentDidMount`中发送请求
2. 根据返回的数据,生成所需的`options`数组
- `map`
- `reduce`
3. 更新状态,刷新页面
#### 4.5 显示二级分类列表
1. 异步请求子列表:
- 准备请求参数
- 发送请求
- 重新调用接口请求函数
- 复用`getCategories`方法(因为getCategories方法被`async`修饰,也是异步函数,返回的也是Promised对象)
2. 判断请求结果
- 如果有子列表 :为目标选项设置`children`属性
- 如果没有子列表:将目标选项的`isLeaf`属性改为`true`
3. 更新状态,刷新页面:
- 准备新的options数据:1. 从`state`中取出;2. 重组装`options`:`[...this.state.options]`
- 调用`setState`,更新状态
#### 4.6 更新时显示默认值
1. 修改时的路由跳转:携带数据跳转
2. `AddUpdate`组件实现添加、更新功能:
在组件挂载前,
1. 保存`Home`组件携带的数据:如果是添加,则Home不携带数据,将this.product设为空对象;如果是更新,接收Home路由携带的数据。
`this.product = props.location.state.product || {}`
2. 设置标志变量`isUpdate`:判断`this.product`为空对象
- 如果`isUpdate`为`true`表示更新
- 如果`isUpdate`为`false`表示添加
3. `Form`表单设置默认值
- 一般项
- 分类的级联选择:初始值为数组
4. 默认分类:
- 初始值类型为数组
- 有可能仅有一级分类:数组中只有一个元素`categoryId`,此时`pCategoryId==='0'`
- 有可能有二级分类:数组中含有两个元素`pCategoryId`和`categoryId`
#### 4.7 UpLoad组件分析
1. 照片墙的使用
2. `Upload`是受控组件
3. 常用参数:
- 上传时发送请求的参数名称:`name`
- 预览功能:`onPreview`,可以使用`Modal`实现
- 上传地址:`action`
- 图片展示方式:`listType`,*text, picture 和 picture-card*
- 接收文件类型:`accept`
- 上传、删除功能:`onChange`
3. 上传按钮:`{fileList.length >= 8 ? null : uploadButton}`
3. `onChange`的回调参数:`{file,fileList,event}`
- `file`:当前文件对象
- `fileList`:文件对象列表
- `event`:事件对象
#### 4.8 修改上传的图片属性
1. 在发送添加商品的api中,需要使用到图片的名称,且是上传返回的图片名称
2. 在`Upload`中收集图片名称
#### 4.9 提交时收集图片名称
1. 在子组件中调用父组件方法:将父组件方法以函数形式通过`props`传递给子组件
2. 在父组件中调用子组件方法:使用`refs`标记子组件标签的实例对象
#### 4.10 删除上传的图片
如果不擅长,图片会越来越多
1. 在`onChange`的回调中处理删除
2. 发送删除图片的api
#### 4.11 更新商品时显示图片
1. 将`AddUpdate`组件中的`product`的`imgs`属性传递给`PicturesWall`组件
1. `PicturesWall`组件中接收`imgs`属性,并在`constructor`钩子中初始化`state`中的`fileList`
#### 4.12 使用富文本编辑器
1. 安装富文本组件:安装`react-draft-wysiwyg`和`draft-js`
```jsx
import { Editor } from 'react-draft-wysiwyg';
} // 包裹的样式
editorStyle={} // 编辑器的样式
toolbarStyle={} // 工具栏的样式
/>
```
2. 修改布局:
- 导入样式:`import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'`
- 更改`Form`中的商品详情`Item`的布局:将水平布局变长——`wrapperCol={{span:20}}`
- 设置编辑器样式:`editorStyle={{border:'1px solid black',height:200,padding:10}}`
- 调整`Admin`整体界面布局:因为`AddUpdate`组件高度超出`100%`,导致`Footer`组件看不到。
- 将`Admin`组件中的``设置为最小高度
3. 点击提交按钮时,在`AddUpdate`父组件中获取子组件`RichTextEditor`的值
- 将富文本中的`editorState`值转化为`html`字符串
1. 安装转换工具库:`draftjs-to-html`
2. 调用方法转换:`draftToHtml(convertToRaw(editorState.getCurrentContent()`
- 在子组件创建获取值的方法:
```jsx
getDetail = ()=>draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()
```
- 在父组件`AddUpdate`中使用`ref`标记编辑器标签对象,在提交按钮时调用子组件方法获取`html`值
4. 更新时,子组件中显示父组件传入的`html`字符串
- 将商品详情以`props`的方法传给子组件
- 子组件声明接收,并在组件挂载前设置状态
- 如果接收到非`undefined`,说明是更新功能
- 将`html`字符串转换为富文本后,赋值给`editorState`
- 如果接收到`undefined`,说明是添加功能,将`editorState`设置为`EditorState.createEmpty()`空对
5. 添加本地文件
- 配置工具栏属性:
```jsx
toolbar={{
image: {
previewImage: true,
uploadCallback: this.uploadImageCallBack,
alt: { present: true, mandatory: false },
}
}}
```
- 上传监听:
```jsx
uploadImageCallBack = (file) => {
return new Promise(
(resolve, reject) => {
const xhr = new XMLHttpRequest(); // 创建xml请求
xhr.open('POST', '/manage/img/upload'); // 发送请求
xhr.setRequestHeader('Authorization', 'Client-ID 8d26ccd12712fca');
const data = new FormData(); // eslint-disable-line no-undef
data.append('image', file);
xhr.send(data);
// 请求监听
xhr.addEventListener('load', () => {
// 请求响应
const response = JSON.parse(xhr.responseText);
// 提取响应的图片url
const url = response.data.url
resolve({ data: { link: url } }); // 成功时返回的配置对象(格式固定)
});
xhr.addEventListener('error', () => {
const error = JSON.parse(xhr.responseText);
reject(error);
});
},
);
}
```
#### 4.13 添加和更新商品功能
1. 编写api:将添加和更新写成一个函数
2. 准备数据
3. 判断请求结果
# 9. 角色与用户管理
## 9.1 需求分析
### 1. 角色管理
1. 添加、删除角色
2. 分配角色权限
3. 数据对象
- `menu`:权限对应的菜单
- `_id`:唯一标识
- `name`:角色名称
- `auth_time`:授权时间
- `auth_name`:授权对象名称
- `create_time`:创建时间
### 2. 用户管理
## 9.2 角色路由功能
### 1. 角色管理
1. 结构:`Card`、`Table`
2. `Table`的可选性:`rowSelection`
3. `Table`对行的监听
```jsx
{
return { // 返回一个监听配置对象
onClick: event => {}, // 点击行
onDoubleClick: event => {},
onContextMenu: event => {},
onMouseEnter: event => {}, // 鼠标移入行
onMouseLeave: event => {},
};
}}
onHeaderRow={(columns, index) => {
return {
onClick: () => {}, // 点击表头行
};
}}
/>
```
### 2. 动态显示角色列变
1. 编写获取角色列表的api
2. 在`DidMount`中发送请求
3. 点击行,自动选中
- 在`state`中记录当前选中角色
- `rowSelection`中配置`selectedRowKeys`:指定选中项的 key 数组
4. 选中角色时,设置角色权限按钮可用
### 3. 显示添加角色确认框
1. 创建`AddForm`组件:只有一个输入框`Input`的表单`Form`
2. 将`form`属性传递给父组件`Role`
3. 在`Role`中判断表单是否验证通过
- 验证通过:隐藏确认框、发送请求、清除输入框数据
- 验证失败:清除输入框数据、隐藏对话框
### 4. 显示权限设置对话框
1. 使用`Tree`组件
1. 根据`menuList`生成权限树
1. 根据`role.menus`选中默认节点:
- `checkedKeys`:受控
- 使用`state`保存已选中节点,并在`constructor`中初始化为`role.menus`
- `onChecked`:选中节点时的回调,选中时更新状态
### 5. 更新角色权限
1. 在父组件`Role`中获取子组件`AuthForm`中获取子组件中`state.checkedKeys`
2. 使用`ref`调用子组件实例方法获取
3. 编写更新角色api
4. 准备数据,发送请求
### 6. 权限列表显示bug
1. 每次点击设置权限按钮,显示对话框时,之后显示第一个点击的角色的权限
- 问题原因:
1. 第一显示,即为挂载,先执行构造器,初始化状态,接着render渲染组件
2. checkedKeys属性根据state来决定选中的节点
3. 关闭对话框后,组件并未卸载,仅仅是隐藏,再次显示时,不再执行构造器,因此state不会更新,选中的节点也不会更新。
2. 在一个角色中,改变一个权限后,点击取消,然后查看权限,发现改变的权限仍然存在
- 问题原因:因为已经缓存数据
3. 解决方法:
1. react16中可以使用`componentWillReceiveProps(nextProps){}`,根据新传入的props,更新state
- 同时解决问题1,2
2. react17中已经弃用上述方法,可以`UNSAFE_componentWillReceiveProps`
3. 有 key 的非可控组件,将`Tree`的key设置为根据props改变的值(比如:role._id),然后使用defaultCheckedKeys。如此以来无需使用state保存数据,但是也不方便将数据提交给父组件
- 使用实例变量保存每次选中时的节点:`this.checkedKeys = checkedKeys`
- 将key改为时间,解决问题2
### 7. 格式化时间和授权人
更新权限时,给`role`对象中添加属性
1. `role.auth_name = memoryUtil**.user.username`
2. 格式化`Table`中的时间:在列属性中使用`render`
## 9.3 用户管理
### 1. 静态页面
1. `Card`、`Table`
1. 准备`Card`的`title`
1. 准备`Table`的列
1. 配置分页器
### 2. 动态显示用户列表
1. api接口
2. 发送请求,接收数据`users`、`roles`,更新状态
3. 创建角色id和name对象
### 3. 显示添加/更新对话框
1. 使用`Modal`
2. 使用`state`保存控制状态
3. 表单验证,设置`Item`规则:
- 用户名规则:
- 密码规则:
- 手机号码规则
- 邮箱规则:
### 4. 删除用户功能
1. 编写api
2. 点击删除按钮,实现删除功能
3. 使用`Modal.confirm({配置对象})`来显示对话框
```jsx
const {getUsers} = this
Modal.confirm({
title:'标题',
async onOk(){
// 发送请求
const result = await reqDeleteUser(user._id) // 离得最近的方法是onOk
// 判断响应结果
if(result.status===0){
// 更新用户列表,此时不能直接用this,因为离得最近的是函数onOk
// 提前获取getUsers
getUsers()
}
},
// 不需要使用,点击取消会自动关闭
onCancle(){
}
})
```
### 5. 添加用户
1. 静态页面:`Modal`中添加`Form`表单,表单中包含:`Item`和`Select`
- 从父组件`User`中获取`roles`数组,然后使用`map`方法生成`Option`
2. 编写api
3. 从子组件`UserForm`中收集数据,封装成`user`对象,发送请求
### 6. 更新用户
1. 与添加用户共用一个组件:
- 请求区别:更新时需要多提交一个`user._id`
- 功能区别:点击修改时,可以获取所在行的user对象。使用实例变量`this.user`保存`user`对象
- 在`User`组件的`render`取出`user`对象,如果是`undefined`则设为空:`const user = this.user || {}`
2. 点击修改按钮时,新增`showUpdate`的回调:接收参数`user`
- 用`this.user`保存`user`
- 更新状态,显示对话框
3. 修改`Modal`:
- 标题:如果`user`非空,则是`更新用户`,否则是`创建用户`
- 给子组件`UserForm`传入`user={user}`
4. 修改`UserForm`组件:
- 声明props接收对象
- 将`Form`的初始值设为`user`
- `Form`中增加`key={user._id}`:==为了解决render时,props未更新的问题==
- 修改的对话框中不显示密码:如果`user._id`为非空,则不显示,否则显示
```jsx
{
user._id ? null : (
-
)
}
```
5. 修改点击`OK`按钮的回调:仅修改表单验证通过后的部分
- 判断表单是否被修改过,如果没有直接结束
- 取出表单数据`user`后,判断`this.user`是否为空,如果非空,则为更新,在`user`中新增`_id`
6. 点击添加用户按钮时,新增`showAdd`的回调:
- 将`this.user`设空:==为了解决点击修改按钮后,再点击添加按钮,表单中有值的问题==
- 更新状态,显示对话框
## 9.4 菜单栏的显示
根据用户权限,显示菜单
### 1. 显示菜单时,匹配当前用户的菜单
1. 当前登录的用户信息保存在本地中`memoryUtil.user`和浏览器的本地`storage`中
- 权限菜单位于`user.role.menus`中
2. 修改`LeftNav`组件中的`getMenuNodes`(生成菜单节点):
- 递归遍历`menuList`,生成的菜单节点
- 生成节点前,判断一下`menuList`中的菜单项是否在`user.role.menus`中
- 如果不在则不生成
3. 编写判断是否生成菜单节点的方法:hasMenu(item)
```jsx
hasMenu = (item) => {
// 1. 如果是admin账号:即本地存储的用户的username是admin
// 2. 如果item的key在本地user.role.menus中
// 3. 至少允许用户可以查看首页,将首页设为公开的:增加isPublic属性
// 4. 如果当前用户,拥有此item的某个子item的权限:menus数组中的某一个项位于item.children数组中
const { user } = memoryUtil
if (user.username === 'admin' || user.role.menus.indexOf(item.key) !== -1 || item.isPublic) {
return true
} else if (item.children && !!item.children.find(c => user.role.menus.indexOf(c.key) !== -1)) { // 先判断是否有子item
// item.children.find(c => user.role.menus.indexOf(c.key) !== -1) 如果非空说明存在,应该返回true
return true
}
return false
}
```
### 2. 当修改当前用户权限时,重新登录
1. 删除当前用户:退出登录、清除本地和浏览器存储
- 先清除登录用户
- 再跳转到登录界面
2. 修改当前用户权限时:退出登录
- 修改角色权限
1. 如果打开的是修改当前用户角色的对话框,提示会重新登录
2. 在点击Ok的回调中,修改成功后,增加判断是否是当前用户角色:`role._id === memoryUtil.user.role_id`
- 修改用户所属角色
1. 点击修改时,提示如果修改了所属角色,将会退出登录
2. 在修改用户的回调中,如果修改成功,增加判断返回的数据中的角色id是否与当前用户的角色id一致:
- 如果不一致,说明修改了角色,清除本地用户,跳转到登录界面
- 如果一致,则提示更新成功,刷新用户列表
# 10. 两个bug
1. 选择角色时,点击`radio`无法选中
- 原因:没有设置`radio`的回调
- 解决:在`Table`的`rowSelection`中增加`onSelect`配置
```jsx
rowSelection={{
type: 'radio',
selectedRowKeys: [role._id],
onSelect: (role) => {
this.setState({ role })
}
}}
```
2. 在商品管理中,查询商品时,分页器的页码问题
- 原因:查询时,会重新更新商品列表,但是分页器页面没有改变
- 解决:给分页器配置对象中添加当前页项(current)
>>>>>>> dev2