0%

《React后台管理系统实战:五》产品管理(四):产品修改、接收商品列表传过来的商品数据、显示商品分类、显示图片、显示对应详情

详情请点阅读全文

一、点修改按钮传值给修改商品页

1.传当前产品对象给add-update组件页home.jsx

【1】传列表页当前产品对象给add-update组件页面:onClick={()=>this.props.history.push('/product/add-update',proObj)}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
width:100,
title:'操作',

render:(proObj)=>{//proObj当前商品对象
return(
<span>
{/*【1】将product对象使用state传递给目标路由组件*/}
<LinkButton onClick={()=>this.props.history.push('/product/detail',{proObj})}>详情</LinkButton>
<LinkButton onClick={()=>this.props.history.push('/product/add-update',proObj)}>修改</LinkButton>
</span>
)
}
},

二、接收传过来的商品对象 add-update.jsx

1.接收传过来的数据,并进行数据的处理

在这里插入图片描述

1
2
3
4
5
6
7
8
componentWillMount(){
//【1】取出产品列表修改按钮传过来的state
const product=this.props.location.state //如果是添加的就没有值,否则就有值
//【2】少用state,此处保存是否更新标识到this
this.isUpdate=!!product //双取反,若product有值,结果为ture
//【3】少用state,保存商品对象到this(如果没有,则保存空对象)
this.product=product || {} //防止添加商品时,展示数据的initialValue:product.name报错undefiend
}

3.解构数据,确定当前页面名(修改商品 | 添加商品)add-update.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//render下return前
//【4】解构需要的数据
const {isUpdate,product}=this
const{pCategoryId,categoryId,imgs,detail}=product

//card左
const title=(
<span>
<LinkButton onClick={()=>this.props.history.goBack()}>
<Icon type='arrow-left' style={{fontSize:20}} />
</LinkButton>
{/* 【5】根据值确定显示内容 */}
<span>{isUpdate?'修改商品':'添加商品'}</span>
</span>
)

效果:

在这里插入图片描述

三、显示商品对应分类

1.商品分类显示解决1

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
//render之下return前
//【分类0】
const{pCategoryId,categoryId}=product
// 【分类2】初始值定义
const categoryIds=[]
//【分类3】如果是一个一级分类商品,则把分类id直接装入数组
if(pCategoryId==='0'){
categoryIds.push(categoryId)
}else{//【分类4】否则商品是一个二级分类的商品,把两级分类id都装入数组
categoryIds.push(pCategoryId)
categoryIds.push(categoryId)
}


//return内
<Item label="商品分类">
{//【分类1】初始值设置为一个变量
getFieldDecorator('categoryIds', {
initialValue: categoryIds,
rules: [
{required: true, message: '必须指定商品分类'},
]
})(<Cascader
placeholder='请指定商品分类'
options={this.state.options} /*需要显示的列表数据数组*/
loadData={this.loadData} /*当选择某个列表项, 加载下一级列表的监听回调*/
/>
)
}
</Item>

初步效果:仅能显示一级分类,二级分类不显示

在这里插入图片描述

2.商品分类显示2级分类

【1-6】

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
//把获取到的categorys解析为options
initOptions= async (categorys)=>{ //【3】
const options = categorys.map((v,k)=>({ //返回一个字典,要额外加一个括号
value: v._id,
label: v.name,
isLeaf: false,
}))

//【1】如果是一个二级分类商品的更新一
const {isUpdate, product} = this
const {pCategoryId} = product
if(isUpdate && pCategoryId!=='0') {//当前功能是商品修改,且,一级分类不为0(父分类为0即表示只有一级分类)
//【2】获取对应的二级分类列表
const subCategorys = await this.getCategorys(pCategoryId)
//【4】生成二级下拉列表的options
const childOptions = subCategorys.map(c => ({
value: c._id,
label: c.name,
isLeaf: true
}))

//【5】找到当前商品对应的一级option对象
const targetOption = options.find(option => option.value===pCategoryId)

//【6】关联对应的一级option上
targetOption.children = childOptions
}

this.setState({
options
})
}

//获取categorys
getCategorys= async (parentId)=>{
const result = await reqCategorys(parentId)
if(result.status===0){
const categorys = result.data
// 如果是一级分类列表
if (parentId==='0') {
this.initOptions(categorys)
} else { // 二级列表
return categorys // 返回二级列表 ==> 当前async函数返回的promsie就会成功且value为categorys
}
}else{
message.error('产品分类获取失败请刷新重试')
}
}

效果:

在这里插入图片描述

三、显示图片

1.解析出要传递给图片显示组件的数据add-update.jsx

1
2
3
//【1】解构需要的数据imgs
const {isUpdate,product}=this
const{pCategoryId,categoryId,imgs,detail}=product

2.传递数据以属性格式给子组件add-update.jsx

1
2
3
4
<Item label='商品图片'>
{/* 【2】imgs传给子组件 PicturesWall */}
<PicturesWall ref={this.pw} imgs={imgs} />
</Item>

3.pictures-wall.jsx接收传过来的图片属性

【0-5】

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
import {BASE_IMG_URL} from '../../../utils/constans' //【0】引入基础图片上传地址即:http://localhost:5000/upload/
import PropTypes from 'prop-types' //【1】引入prop-types用于获取父组件传过来的数据


//【2】接收父组件传过来的值 (非必须)
static propTypes={
imgs:PropTypes.array
}


constructor(props){
super(props)
//【3】定义fileList为空
let fileList=[]
//【4】如果传来了imgs属性值
const {imgs}=this.props //解构imgs
if(imgs && imgs.length>0){//imgs存在且长度大于0
fileList=imgs.map((img,index)=>({//定义一个对象要额外加个括号
uid: -index, // 每个file都有自己唯一的id
name: img, // 图片文件名
status: 'done', // 图片状态: done-已上传, uploading: 正在上传中, removed: 已删除
url: BASE_IMG_URL + img
}))
}

this.state={
previewVisible: false,
previewImage: '',
fileList //【5】
}
}

效果:已自动加载对应图

在这里插入图片描述

四、显示商品对应商品详情

1.add-update.jsx解析及传值给子组件

1
2
3
4
5
6
7
8
9
10
//【1】render下return上解构需要的数据detail
const {isUpdate,product}=this
const{pCategoryId,categoryId,imgs,detail}=product

<Item label='商品详情' labelCol={{span: 2}} wrapperCol={{span: 20}}>
{/**指定把richtext对象装进editor里
* 【2】传detail给子组件RichText
*/}
<RichText ref={this.editor} detail={detail} />
</Item>

2.rich-text.jsx接收传过来的值

1
2
3
4
5
6
import PropTypes from 'prop-types' //【1】引入proptypes用于接收父组件传值

//【2】接收父组件传值
static propTypes={
detail:PropTypes.string
}

3.显示传过来的html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { EditorState, convertToRaw,ContentState } from 'draft-js'; //【1】引入ContentState
import htmlToDraft from 'html-to-draftjs'; //【2】显示现有html需要组件

//【3】rich-text官网拉到底找到把现成的代码来修改,字符串显示到富文本编辑框内
constructor(props) {
super(props)

const html = this.props.detail //解构出传过来的detail内的html数据

if (html) { // 【4】html如果有值, 根据html格式字符串创建一个对应的编辑对象
const contentBlock = htmlToDraft(html)
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
const editorState = EditorState.createWithContent(contentState)
this.state = {
editorState,
}
} else {//【5】没有则让富文本框创建空对象
this.state = {
editorState: EditorState.createEmpty(), // 创建一个没有内容的编辑对象
}
}

}

效果:点修改自动加载对应商品详情内容

在这里插入图片描述

五、附:商品修改功能完整代码

1.home.jsx

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import React,{Component} from 'react'
import {
Card,
Select,
Input,
Table,
Icon,
Button,
message
} from 'antd'
import LinkButton from '../../../components/link-button'
import {reqProducts,reqSearchProducts,reqUpdateStatus} from '../../../api/' //引入入api请求函数
import {PAGE_SIZE} from '../../../utils/constans' //引入常量每页显示产品条数PAGE_SIZE=3


const Option=Select.Option

export default class Home extends Component{
state={
//商品列表
total:0,//商品总数
products:[],
loading:false,
searchName:'', //搜索关键词
searchType:'productName', //按什么搜索:名称/描述 productName/productDesc
}

//【6】更新商品上下架状态
updateStatus = async (productId,status)=>{
const result=await reqUpdateStatus(productId,status)
if(result.status===0){
message.success('商品上下架状态更新成功')
//【8】更新成功后重新获取正确的商品分页此时传入的页码来源于7步存入的页码
this.getProducts(this.pageNum)
}
}


//Table的列名及对应显示的内容渲染
initColumns=()=>{
this.columns=[
{
title:'商品名称',
dataIndex:'name'
},
{
title:'商品描述',
dataIndex:'desc'
},
{
title:'价格',
dataIndex:'price',
render:(price)=>'¥'+price //把price渲染进对应的行,并加上¥符号
},
{
width:100,
title:'商品状态',
//dataIndex:'status',//【1】注释掉
render:(proObj)=>{//【2】传入当前的商品对象
const {_id,status}=proObj //【3】解构商品id和status
const newStatus=status===1?2:1//【4】把商品的状态2换1,1换2
return(
<span>
<Button
type='primary'
/*【5】调用更新状态函数把当前商品id及要更新的状态传过去*/
onClick={()=>this.updateStatus(_id,newStatus)}>
{status===1 ? '下架' : '上架'}</Button>
<span>{status===1 ? '在售':'已下架'}</span>
</span>
)
}
},
{
width:100,
title:'操作',

render:(proObj)=>{//proObj当前商品对象
return(
<span>
{/*将product对象使用state传递给目标路由组件*/}
<LinkButton onClick={()=>this.props.history.push('/product/detail',{proObj})}>详情</LinkButton>
<LinkButton onClick={()=>this.props.history.push('/product/add-update',proObj)}>修改</LinkButton>
</span>
)
}
},
]
}

//请求产品列表放入state,后台分页
getProducts=async(pageNum)=>{//pageNum为请求页码
this.setState({loading:true}) //设置加载动画开始显示
this.pageNum=pageNum //【7】保存pageNum, 让其它方法可以看到

const {searchName,searchType}=this.state //
let result //有两个result因此把result提出来定义
if(searchName){//如果有搜索关键词就是关键词搜索,易错pageSize:PAGE_SIZE
result=await reqSearchProducts({pageNum,pageSize:PAGE_SIZE,searchType,searchName})
}else{//否则就是一般搜索
result = await reqProducts(pageNum,PAGE_SIZE) // 常量:每页显示产品条数,
}

this.setState({loading:false}) //关闭加载动画
if(result.status===0){
console.log(result.data)
const {total,list}=result.data
this.setState({
total,
products:list
})
}else{
message.error('加载产品失败,请刷新页面重试')
}
}

componentWillMount(){
//Table列名初始化函数调用,用于准备表格列名及显示内容
this.initColumns()
}

//获取产品
componentDidMount(){
this.getProducts(1)
}

render(){
//state数据解构,简化使用
const {products,loading,total,searchName,searchType}=this.state

//card左侧内容
const title=(
<span>
<Select
value={searchType} /**/
style={{width:150,}}
onChange={value=>this.setState({searchType:value})}/**/
>
<Option value='productName'>按名称搜索</Option>
<Option value='productDesc'>按描述搜索</Option>
</Select>
<Input placeholder='关键字' style={{width:150,margin:'0 8px'}}
value={searchName}/**/
onChange={event=>this.setState({searchName:event.target.value})}/**/
/>
<Button type='primary'
onClick={()=>this.getProducts(1)} //点击搜索对应产品
>搜索</Button>
</span>
)
//card右侧内容
const extra=(
<Button type='primary' onClick={() => this.props.history.push('/product/add-update')}>
<Icon type='plus'/>
添加商品
</Button>
)
return(
<Card title={title} extra={extra}>
<Table
bordered
rowKey='_id'
dataSource={products}
loading={loading}
columns={this.columns}
pagination={{/*分页配置*/
current: this.pageNum,
total,
defaultPageSize: PAGE_SIZE,
showQuickJumper: true,
onChange: this.getProducts /*onchange是一回调函数,把pageNum传给getproducts,等于:(pageNum)=>{this.getProducts(pageNum)}*/
}}
/>

</Card>
)
}
}

2.add-update.jsx

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import React,{Component} from 'react'
import {
Card,
Icon,
Form,
Input,
Cascader,//级联组件
Button,
message,
} from 'antd'
import LinkButton from '../../../components/link-button'
import {reqCategorys,reqAddUpdatePro} from '../../../api' //引入添加修改产品函数
import PicturesWall from './pictures-wall'
import RichText from './rich-text'

const {Item}=Form
const {TextArea}=Input


class AddUpdate extends Component{
constructor(props){
super(props)
//创建用于存放指定ref标识的标签对象容器
this.pw=React.createRef()
//
this.editor=React.createRef()

this.state={
options:[], //定义状态选项
}
}


//把获取到的categorys解析为options
initOptions= async (categorys)=>{ //
const options = categorys.map((v,k)=>({ //返回一个字典,要额外加一个括号
value: v._id,
label: v.name,
isLeaf: false,
}))

//如果是一个二级分类商品的更新一
const {isUpdate, product} = this
const {pCategoryId} = product
if(isUpdate && pCategoryId!=='0') {//当前功能是商品修改,且,一级分类不为0(父分类为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)

//关联对应的一级option上
targetOption.children = childOptions
}

this.setState({
options
})
}

//获取categorys
getCategorys= async (parentId)=>{
const result = await reqCategorys(parentId)
if(result.status===0){
const categorys = result.data
// 如果是一级分类列表
if (parentId==='0') {
this.initOptions(categorys)
} else { // 二级列表
return categorys // 返回二级列表 ==> 当前async函数返回的promsie就会成功且value为categorys
}
}else{
message.error('产品分类获取失败请刷新重试')
}
}

//自定义验证:商品价格大于0函数
valiPrice=(rule, value, callback)=>{
//console.log(value,typeof(value)) //在价格输入-1即显示是string类型
if(value*1>0){ //字符串*1:将字符串转化为数字类型
callback()
}else{
callback('价格必须大于0')
}
}


onChange = (value, selectedOptions) => {
console.log(value, selectedOptions);
}


//加载二级分类列表函数
loadData = async selectedOptions => {
const targetOption = selectedOptions[0];
targetOption.loading = true

// 根据选中的分类, 请求获取二级分类列表
const subCategorys = await this.getCategorys(targetOption.value)
// 隐藏loading
targetOption.loading = false
// 二级分类数组有数据
if (subCategorys && subCategorys.length>0) {
// 生成一个二级列表的options
const childOptions = subCategorys.map(c => ({
value: c._id,
label: c.name,
isLeaf: true
}))
// 关联到当前option上
targetOption.children = childOptions
} else { // 当前选中的分类没有二级分类
targetOption.isLeaf = true
}

// 更新options状态
this.setState({
options: [...this.state.options],
})
}

//产品表单提交
submit=()=>{
this.props.form.validateFields(async(error,values)=>{


if(!error){
//收集数据, 并封装成product对象
const {name,desc,price,categoryIds}=values
let pCategoryId,categoryId
if(categoryIds.length===1){//如果长度为1说明只有一级产品分类
pCategoryId='0'
categoryId=categoryIds[0]
}else{//否则说明有二级产品分类
pCategoryId=categoryIds[0]
categoryId=categoryIds[1]
}
//获取子组件的相关信息
const imgs=this.pw.current.getImgs()
//获取子组件商品详情的带html标签的字符串数据
const detail=this.editor.current.getDetail()

const product={name,desc,price,imgs,detail,pCategoryId,categoryId}
//输出看看
console.log(product)


//调用接口请求函数去添加/更新
const result=await reqAddUpdatePro(product)
if(result.status===0){//根据结果提示是否添加/更新成功
message.success('添加产品成功')
}else{
message.error('添加产品失败')
}

}else{
console.log('验证失败,请检查产品数据')
}


})





}



componentWillMount(){
//取出产品列表修改按钮传过来的state
const product=this.props.location.state //如果是添加的就没有值,否则就有值
//保存是否更新标识到this
this.isUpdate=!!product //双取反,若product有值,结果为ture
//保存商品到this(如果没有,则保存空对象)
this.product=product || {}
}

componentDidMount(){
this.getCategorys('0') //加载categorys并初始化为
}

render(){
//【1】解构需要的数据detail
const {isUpdate,product}=this
const{pCategoryId,categoryId,imgs,detail}=product

// 初始值定义
const categoryIds=[]
//如果是一个一级分类商品,则把分类id直接装入数组
if(pCategoryId==='0'){
categoryIds.push(categoryId)
}else{//否则商品是一个二级分类的商品,把两级分类id都装入数组
categoryIds.push(pCategoryId)
categoryIds.push(categoryId)
}

//card左
const title=(
<span>
<LinkButton onClick={()=>this.props.history.goBack()}>
<Icon type='arrow-left' style={{fontSize:20}} />
</LinkButton>
{/* 根据值确定显示内容 */}
<span>{isUpdate?'修改商品':'添加商品'}</span>
</span>
)

//form内的Item的布局样式
const formItemLayout = {
labelCol: {span: 2}, //左侧label标签的宽度占2个格栅
wrapperCol: {span: 8 }, //右侧(输入框外面有一层包裹)占8个格栅
};

//获取from的getFieldDecorator
const {getFieldDecorator}=this.props.form




return(
<Card title={title} extra=''>
{/* 使用组件的扩展属性语法 */}
<Form {...formItemLayout}>
{/* label指定商品前面标签名,placeholder指定输入框提示内容 */}
<Item label='商品名称'>
{//商品名规则
getFieldDecorator('name',{
initialValue:product.name,//显示要修改的商品名
rules:[
{required:true,message:'商品名称必须填写'}
]
})(<Input placeholder='输入商品名' />)
}

</Item>

<Item label='商品描述'>
{//autoSize指定文本域最小高度和最大高度
getFieldDecorator('desc',{
initialValue:product.desc, //
rules:[
{required:true,message:'商品描述必须输入'}
]
})(<TextArea placeholder='输入商品描述' autoSize={{ minRows: 2, maxRows: 6 }} />)
}
</Item>

<Item label='商品价格'>
{//validator自定义验证规则要求价格大于0
getFieldDecorator('price',{
initialValue:product.price, //
rules:[
{required:true,message:'价格必须输入'},
{validator:(rule,value,callback)=>{
if(value*1>0){ //字符串*1:将字符串转化为数字类型
callback() //此处必须进行回调函数调用,否则将无法通过验证
}else{
callback('价格必须大于0')
}
}},
]
})(<Input type='number' placeholder='输入商品价格' addonAfter="元" />)
}
</Item>

<Item label="商品分类">
{//初始值设置为一个变量
getFieldDecorator('categoryIds', {
initialValue: categoryIds,
rules: [
{required: true, message: '必须指定商品分类'},
]
})(<Cascader
placeholder='请指定商品分类'
options={this.state.options} /*需要显示的列表数据数组*/
loadData={this.loadData} /*当选择某个列表项, 加载下一级列表的监听回调*/
/>
)
}

</Item>

<Item label='商品图片'>
{/* imgs传给子组件 PicturesWall */}
<PicturesWall ref={this.pw} imgs={imgs} />
</Item>

<Item label='商品详情' labelCol={{span: 2}} wrapperCol={{span: 20}}>
{/**指定把richtext对象装进editor里
* 【2】传detail给子组件RichText
*/}
<RichText ref={this.editor} detail={detail} />
</Item>

<Item >
<Button type='primary' onClick={this.submit}>提交</Button>
</Item>

</Form>
</Card>
)
}
}
export default Form.create()(AddUpdate) //包装当前类使得到form的的强大函数

3.pictures-wall.jsx

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import React,{Component} from 'react'
import { Upload, Icon, Modal,message } from 'antd';
import {reqDeletPic} from '../../../api' //删除图片api
import {BASE_IMG_URL} from '../../../utils/constans' //【0】引入基础图片上传地址即:http://localhost:5000/upload/
import PropTypes from 'prop-types' //【1】引入prop-types用于获取父组件传过来的数据

function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}

export default class PicturesWall extends Component {
//【2】接收父组件传过来的值 (非必须)
static propTypes={
imgs:PropTypes.array
}

/*{fileList: [
uid: '-1', // 每个file都有自己唯一的id
name: 'xxx.png', // 图片文件名
status: 'done', // 图片状态: done-已上传, uploading: 正在上传中, removed: 已删除
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', // 图片地址
},
]*/

constructor(props){
super(props)

//【3】定义fileList
let fileList=[]
//【4】如果传来了imgs属性值
const {imgs}=this.props //解构imgs
if(imgs && imgs.length>0){//imgs存在且长度大于0
fileList=imgs.map((img,index)=>({//定义一个对象要额外加个括号
uid: -index, // 每个file都有自己唯一的id
name: img, // 图片文件名
status: 'done', // 图片状态: done-已上传, uploading: 正在上传中, removed: 已删除
url: BASE_IMG_URL + img
}))
}

this.state={
previewVisible: false,
previewImage: '',
fileList
}
}

/*
获取所有已上传图片文件名的数组
*/
getImgs = () => {
//返回状态中的文件列表中每个文件的文件名
return this.state.fileList.map(file => file.name)
}
// state = {
// previewVisible: false,
// previewImage: '',
// fileList: [
// // {
// // uid: '-1',
// // name: 'image.png',
// // status: 'done',
// // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
// // }

// ],
// };

handleCancel = () => this.setState({ previewVisible: false });

handlePreview = async file => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}

this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
});
};

/*
file: 当前操作的图片文件(上传/删除)
fileList: 所有已上传图片文件对象的数组
官方文档:https://ant.design/components/upload-cn/#onChange
*/
handleChange = async ({ file,fileList }) => { //async
console.log('handlechange:',file.status, fileList.length, file===fileList[fileList.length-1])

// 一旦上传成功, 将当前上传的file的信息修正成最新的(name, url)
if(file.status==='done'){
const result = file.response // {status: 0, data: {name: 'xxx.jpg', url: '图片地址'}}
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('上传错误')
}
}else if(file.status==='removed'){//如果文件的状态为移除,则删除服务器上对应图片名图片
const result=await reqDeletPic(file.name)
if(result.status===0){
message.success('图片删除成功:'+file.name)
}else{
message.error('图片删除失败:'+file.name)
}
}

// 在操作(上传/删除)过程中不断更新fileList状态
this.setState({ fileList })
}

render() {
const { previewVisible, previewImage, fileList } = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div className="clearfix">
<Upload
action="/manage/img/upload" /**上传图片的接口地址 */
accept='image/*' /**只接受图片格式 */
name='image' /**请求参数名,来自api说明上传图片的参数类型 */
listType="picture-card" /*卡片样式:text, picture 和 picture-card*/
fileList={fileList} /*所有已上传图片文件对象的数组*/
onPreview={this.handlePreview} /**显示图片预览函数 */
onChange={this.handleChange} /**上传/删除图片函数 */
>
{//控制图片上传按钮最多5个
fileList.length >= 5 ? null : uploadButton}
</Upload>
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</div>
);
}
}

4.rich-text.jsx

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import React, { Component } from 'react';
import { EditorState, convertToRaw,ContentState } from 'draft-js'; //【3】引入ContentState
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs'; //【0】显示现有html需要组件
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' //引入编辑器样式,否则会乱七八糟
import PropTypes from 'prop-types' //【1】引入proptypes用于接收父组件传值


export default class RichText extends Component {
//【2】接收父组件传值
static propTypes={
detail:PropTypes.string
}

state = {
editorState: EditorState.createEmpty(),
}

//【4】rich-text官网拉到底找到把现成的字符串显示到富文本编辑框内
constructor(props) {
super(props)

const html = this.props.detail //解构出传过来的detail内的html数据

if (html) { // 【5】html如果有值, 根据html格式字符串创建一个对应的编辑对象
const contentBlock = htmlToDraft(html)
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks)
const editorState = EditorState.createWithContent(contentState)
this.state = {
editorState,
}
} else {//【6】没有则让富文本框创建空对象
this.state = {
editorState: EditorState.createEmpty(), // 创建一个没有内容的编辑对象
}
}

}

onEditorStateChange=(editorState) => { //标签写法改成如左写法
this.setState({
editorState,
});
};

//让父组件获取到当前组件的信息
getDetail=()=>{
return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))
}

//【2】图片上传加一个上传按钮
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 // 得到图片的url
resolve({data: {link: url}})
})
xhr.addEventListener('error', () => {
const error = JSON.parse(xhr.responseText)
reject(error)
})
}
)
}

render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
editorStyle={{border: '1px solid black', minHeight: 200, paddingLeft: 10}}
onEditorStateChange={this.onEditorStateChange}
/**【1】图片上传按钮配置*/
toolbar={{
image: { uploadCallback: this.uploadImageCallBack, alt: { present: true, mandatory: true } },
}}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</div>
);
}
}

5.完成效果:点商品列表任一产品的修改按钮,显示如下

在这里插入图片描述
在这里插入图片描述