基本路由

1
2
3
4
5
6
7
<Switch>
<Route path='/product' component={ProductHome} exact />
{/*路径完全匹配*/}
<Route path='/product/addupdate' component={ProductAddUpdate} />
<Route path='/product/detail' component={ProductDetail} />
<Redirect to='/product' />
</Switch>

两种分页技术

  1. 前台分页

    后台直接返回全部数据

  2. 后台分页

    后台返回给前台当前页数与总页数

    前台需要传递当前页码数与每页数量

跳转路由时传递参数

跳转路由

1
this.props.history.push('/product/detail', { product })

接受参数

1
const { pCategoryId, categoryId } = this.props.location.state.product

React中输出HTML标签

1
<span dangerouslySetInnerHTML={{ __html: detail }}></span>

级联选择器

1
2
3
4
5
6
<Cascader
options={this.state.options}
loadData={this.loadData}
onChange={this.onChange}
changeOnSelect
/>
  • options

    数据源

  • loadData

    加载数据的回调函数

  • changeOnSelect

    当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示

  • onChange

    选择完成后的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
loadData = (selectedOptions) => {
// 的到选择的option对象
const targetOption = selectedOptions[selectedOptions.length - 1]
// loading效果
targetOption.loading = true

// load options lazily
setTimeout(() => {
targetOption.loading = false
// 某一项的下一级列表
targetOption.children = [
{
label: `${targetOption.label} Dynamic 1`,
value: 'dynamic1',
isLeaf: true
},
{
label: `${targetOption.label} Dynamic 2`,
value: 'dynamic2',
isLeaf: true
}
]
this.setState({
options: [...this.state.options]
})
}, 1000)
}

获取分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
export default class ProductAddUpdate extends Component {
formRef = React.createRef()
state = {
options: []
}
initOptions = (categorys) => {
// 根据categorys数组生成options数组
const options = categorys.map((c) => ({
value: c._id,
label: c.name,
isLeaf: false
}))
// 更新状态
this.setState({ options })
}
// 获取一级/二级分类列表
getCategorys = async (parentId) => {
parentId = parentId || '0'
const result = await reqCategorys(parentId)
if (result.status !== 0) return message.error('分类获取失败')
const categorys = result.data
// 如果是一级分类
if (parentId === '0') {
this.initOptions(categorys)
} else {
// 二级列表
return categorys
}
}
loadData = async (selectedOptions) => {
// 的到选择的option对象
const targetOption = selectedOptions[selectedOptions.length - 1]
// loading效果
targetOption.loading = true

// 根据选中的分类请求获取下一级分类
const subCategorys = await this.getCategorys(targetOption.value)
if (subCategorys && subCategorys.length > 0) {
// 生成二级列表options
const cOptions = subCategorys.map((c) => ({
value: c._id,
label: c.name,
isLeaf: true
}))
// 某一项的下一级列表
targetOption.children = cOptions
} else {
targetOption.isLeaf = true
}
targetOption.loading = false
this.setState({
options: [...this.state.options]
})
}
componentDidMount() {
this.getCategorys()
}
}

默认分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
initOptions = async (categorys) => {
// 根据categorys数组生成options数组
const options = categorys.map((c) => ({
value: c._id,
label: c.name,
isLeaf: false
}))
// 如果是一个二级分类商品的更新
const { isUpdate, product } = this
const { pCategoryId, categoryId } = product
if (isUpdate && pCategoryId !== '0') {
// 获取对应的二级分类列表
const subCategorys = await this.getCategorys(pCategoryId)
// 生成二级下拉列表的options
const childOptions = subCategorys.map((c) => ({
value: c._id,
label: c.name,
isLeaf: true
}))
// 找到当前商品对应的option对象
const targetOption = options.find(
(option) => option.value === pCategoryId
)
// 关联到对应的一级options
targetOption.children = childOptions
}

// 更新状态
this.setState({ options })
}

上传图片

上传组件:https://ant.design/components/upload-cn/

上传图片时所触发的回调函数中filefileList最后一个元素不是同一个。因此修改的并不是file,而是fileList最后一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// file:当前操作的图片文件
// fileList:所有已上传的文件对象
handleChange = ({ file, fileList }) => {
// 一旦上传成功,将当前上传的file的信息修正
if (file.status === 'done') {
const result = file.response
if (result.status === 0) {
message.success('成功了!')
const { name, url } = result.data
file = fileList[fileList.length - 1]
file.name = name
file.url = url
} else {
message.error('失败了!')
}
}
this.setState({ fileList })
}

父组件收集子组件的数据

为子组件添加ref引用。类似于antd4中的表单。例如

1
2
3
4
5
6
7
8
9
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}

子组件定义方法返回需要的列表,父组件调用此方法即可。

编辑时显示图片

编辑时显示图片涉及到fileList图片列表的初始值。因此需要接受数据来判断是否存在。如果存在则生成有图片的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
constructor(props) {
super(props)
const { imgs } = this.props
let fileList = []
if (imgs && imgs.length > 0) {
fileList = imgs.map((img, index) => ({
uid: -index,
name: img,
status: 'done',
url: BASE_IMG_URL + img
}))
}
this.state = {
previewVisible: false, // 是否显示大图预览
previewImage: '', // 图片地址
previewTitle: '',
fileList
}
}

父组件只需要将图片列表传递给子组件即可。

富文本编辑器

安装插件

1
yarn add react-draft-wysiwyg draftjs-to-html draft-js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Editor
editorState={editorState}
editorStyle={{
border: '1px solid black',
minHeight: 200,
maxHeight: 500,
paddingLeft: 10
}}
toolbar={{
image: {
uploadCallback: this.uploadImageCallBack,
alt: { present: true, mandatory: true }
}
}}
onEditorStateChange={this.onEditorStateChange}
/>

图片上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 图片上传
uploadImageCallBack = (file) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', '/manage/img/upload')
const data = new FormData()
data.append('image', file)
xhr.send(data)
xhr.addEventListener('load', () => {
const response = JSON.parse(xhr.responseText)
const url = response.data.url
resolve({ data: { link: url } })
})
xhr.addEventListener('error', () => {
const error = JSON.parse(xhr.responseText)
reject(error)
})
})
}