《React后台管理系统实战:五》产品管理(二):产品添加页面及验证等、富文本编辑器、提交商品

49 篇文章 6 订阅

一、产品添加基础部分

1 home.jsx点添加按钮动作跳转到添加商品页

点击:onClick={() => this.props.history.push('/product/add-update')}>

//card右侧内容
        const extra=(
            <Button type='primary' onClick={() => this.props.history.push('/product/add-update')}>
                <Icon type='plus'/>
                添加商品
            </Button>
        )

2 静态页面 add-update.jsx

import React,{Component} from 'react'
import {
    Card,
    Icon,
    Form,
    Input,
    Cascader,//级联组件
    Upload, //上传组件
    Button,
    message,
} from 'antd'
import LinkButton from '../../../components/link-button'
const {Item}=Form
const {TextArea}=Input

export default class AddUpdate extends Component{
    render(){
        //card左
        const title=(
            <span>
                <LinkButton>
                    <Icon type='arrow-left' style={{fontSize:20}} />                    
                </LinkButton>
                <span>添加商品</span>
            </span>
        )
                
        //form内的Item的布局样式
        const formItemLayout = {
            labelCol: {span: 2}, //左侧label标签的宽度占2个格栅
            wrapperCol: {span: 8 }, //右侧(输入框外面有一层包裹)占8个格栅
          };
        return(
            <Card title={title} extra=''>
                {/* 使用组件的扩展属性语法 */}
                <Form {...formItemLayout}>
                    {/* label指定商品前面标签名,placeholder指定输入框提示内容 */}
                    <Item label='商品名称'>
                        <Input  placeholder='输入商品名' />
                    </Item>

                    <Item label='商品描述'>
                        {/* autoSize指定文本域最小高度和最大高度 */}
                        <TextArea placeholder='输入商品描述' autoSize={{ minRows: 2, maxRows: 6 }} />
                    </Item>

                    <Item label='商品价格'>
                        <Input type='number' placeholder='输入商品价格' addonAfter="元" />
                    </Item>

                    <Item label='商品分类'>
                        <Input placeholder='输入商品分类' />
                    </Item>

                    <Item label='商品图片'>
                        <Input placeholder='输入商品图片' />
                    </Item> 

                    <Item label='商品详情'>
                        <Input placeholder='输入商品详情' />
                    </Item>

                    <Item >
                        <Button type='primary'>提交</Button>
                    </Item>  

                </Form>
            </Card>
        )
    }
}

效果:http://localhost:3000/product/add-update

在这里插入图片描述

3 表单验证add-update.jsx

【0】包装当前类使得到form的的强大函数
【0-1】解构得到from的getFieldDecorator
【1】商品名规则
【2】商品描述验证规则
【3】商品价格验证规则:知识点自自定义验证函数
【5】表单提交验证
【6】自定义验证规则要求价格大于0
【7】自定义验证:商品价格大于0函数

import React,{Component} from 'react'
import {
    Card,
    Icon,
    Form,
    Input,
    Cascader,//级联组件
    Upload, //上传组件
    Button,
    message,
} from 'antd'
import LinkButton from '../../../components/link-button'
const {Item}=Form
const {TextArea}=Input

class AddUpdate extends Component{
    //【5】表单提交验证
    submit=()=>{
        this.props.form.validateFields((err,v)=>{
            if(!err){
                alert('产品添加中')
            }
        })
    }
    //【7】自定义验证:商品价格大于0函数
    valiPrice=(rule, value, callback)=>{
        console.log(value,typeof(value)) //在价格输入-1即显示是string类型
        if(value*1>0){ //字符串*1:将字符串转化为数字类型
            callback('验证通过') //每个节点必须调用否validateFields则会验证失败
        }else{
            callback('价格必须大于0')
        }
    }
    render(){
        //card左
        const title=(
            <span>
                <LinkButton>
                    <Icon type='arrow-left' style={{fontSize:20}} />                   
                </LinkButton>
                <span>添加商品</span>
            </span>
        )
        //card右
        
        //form内的Item的布局样式
        const formItemLayout = {
            labelCol: {span: 2}, //左侧label标签的宽度占2个格栅
            wrapperCol: {span: 8 }, //右侧(输入框外面有一层包裹)占8个格栅
          };
		//【0-1】获取from的getFieldDecorator
        const {getFieldDecorator}=this.props.form
        return(
            <Card title={title} extra=''>
                {/* 使用组件的扩展属性语法 */}
                <Form {...formItemLayout}>
                    {/* label指定商品前面标签名,placeholder指定输入框提示内容 */}
                    <Item label='商品名称'>
                        {//【1】商品名规则
                            getFieldDecorator('name',{
                                initialValue:'',
                                rules:[
                                    {required:true,message:'商品名称必须填写'}
                                ]
                            })(<Input  placeholder='输入商品名' />)
                        }
                        
                    </Item>

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

                    <Item label='商品价格'>
                        {//【3】
                            getFieldDecorator('price',{
                                initialValue:'',
                                rules:[
                                    {required:true,message:'价格必须输入'},
                                    {validator:this.valiPrice},//【6】自定义验证规则要求价格大于0
                                ]
                            })(<Input type='number' placeholder='输入商品价格' addonAfter="元" />)
                        }
                        
                    </Item>

                    <Item label='商品分类'>
                        <Input placeholder='输入商品分类' />
                    </Item>

                    <Item label='商品图片'>
                        <Input placeholder='输入商品图片' />
                    </Item> 

                    <Item label='商品详情'>
                        <Input placeholder='输入商品详情' />
                    </Item>

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

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

效果:http://localhost:3000/product/add-update

在这里插入图片描述

4 商品分类:Cascader级联组件

代码add-update.jsx

【0】定义状态选项
【1】级联商品分类
【2】获取categorys
【3】把获取到的categorys解析为options
【4】加载categorys并初始化为
【5】加载二级分类列表函数

import React,{Component} from 'react'
import {
    Card,
    Icon,
    Form,
    Input,
    Cascader,//级联组件
    Upload, //上传组件
    Button,
    message,
} from 'antd'
import LinkButton from '../../../components/link-button'
import {reqCategorys} from '../../../api'
const {Item}=Form
const {TextArea}=Input

// 定义选项
// const options = [
//     {
//       value: 'zhejiang',
//       label: 'Zhejiang',
//       isLeaf: false,
//     },
//     {
//       value: 'jiangsu',
//       label: 'Jiangsu',
//       isLeaf: false,
//     },
//   ];


class AddUpdate extends Component{
    state={
        options:[], //【0】定义状态选项
    }

    //表单提交验证
    submit=()=>{
        this.props.form.validateFields((err,v)=>{
            if(!err){
                alert('产品添加中')
            }
        })
    }

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

        this.setState({options})
    }

    //【2】获取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')
        }
    }

    //【5】加载二级分类列表函数
    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],
            })        
      };
      
    componentDidMount(){
        this.getCategorys('0') //【4】加载categorys并初始化为
    }
      
    render(){
        //card左
        const title=(
            <span>
                <LinkButton>
                    <Icon type='arrow-left' style={{fontSize:20}} />                   
                </LinkButton>
                <span>添加商品</span>
            </span>
        )
        //card右
        
        //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:'',
                                rules:[
                                    {required:true,message:'商品名称必须填写'}
                                ]
                            })(<Input  placeholder='输入商品名' />)
                        }
                        
                    </Item>

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

                    <Item label='商品价格'>
                        {//
                            getFieldDecorator('price',{
                                initialValue:'',
                                rules:[
                                    {required:true,message:'价格必须输入'},
                                    {validator:this.valiPrice},//自定义验证规则要求价格大于0
                                ]
                            })(<Input type='number' placeholder='输入商品价格' addonAfter="元" />)
                        }
                        
                    </Item>

                    <Item label='商品分类'>
                        {/*【1】级联商品分类 */}
                        <Cascader
	                        placeholder='请选择'
                            options={this.state.options}
                            loadData={this.loadData}                            
                        />
                    </Item>

                    <Item label='商品图片'>
                        <Input placeholder='输入商品图片' />
                    </Item> 

                    <Item label='商品详情'>
                        <Input placeholder='输入商品详情' />
                    </Item>

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

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

效果:http://localhost:3000/product/add-update

在这里插入图片描述

5 图片上传部分

图片上传因为代码较多所以单独写个组件,引入进来

.5.1图片上传组件编写pictures-walls.jsx

【1】上传图片的接口地址
【2】只接受图片格式
【3】请求参数名,来自api说明上传图片的参数类型
【5】file: 当前操作的图片文件(上传/删除)
fileList: 所有已上传图片文件对象的数组
官方文档:https://ant.design/components/upload-cn/#onChange
【6】 一旦上传成功, 将当前上传的file的信息修正成最新的(name, url)
【7】在操作(上传/删除)过程中不断更新fileList状态

import React,{Component} from 'react'
import { Upload, Icon, Modal,message } from 'antd';

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 {
  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,
    });
  };

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

    //【6】 一旦上传成功, 将当前上传的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('上传错误')
      }
    }

    // 【7】在操作(上传/删除)过程中不断更新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" /**【1】上传图片的接口地址 */
          accept='image/*'  /**【2】只接受图片格式 */
          name='image' /**【3】请求参数名,来自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>
    );
  }
}

.5.2 获取子组件的图片信息:父子组件传值

ref的官方教程:https://zh-hans.reactjs.org/docs/refs-and-the-dom.html
首先,从add-update.jsx内引入picture-wall.jsx

.5.2.1 picture-wall.jsx
 constructor(props){
    super(props)

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

    /*
  【1】获取所有已上传图片文件名的数组
   */
  getImgs  = () => {
  //返回状态中的文件列表中每个文件的文件名
    return this.state.fileList.map(file => file.name)
  }
.5.2.1 add-update.jsx
    constructor(props){
        super(props)

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

		//【1】创建用于存放指定ref标识的标签对象容器
        this.pw=React.createRef()
    }

//表单提交验证
     submit=()=>{
        this.props.form.validateFields((error,values)=>{           
            if(!error){
                //【2】获取子组件的相关图片名数组信息
                const imgs=this.pw.current.getImgs()         
                alert('成功')
                //【3】输出看看
                console.log('表单,图片:',values,imgs)
            }else{               
                console.log('失败')
            }             
        })       
    }

//【4】render()内组件,ref标记为[1]处的容器
<Item label='商品图片'>
   <PicturesWall ref={this.pw} />
</Item>

效果:提交后可看到获取到的数据

在这里插入图片描述

6 图片删除(移除前端并删除服务器对应图片)

第一步:编写删除api函数src/api/index.js

删除服务器上指定名称图片

import ajax from './ajax'
import jsonp from 'jsonp'
import {message} from 'antd' 
// const BASE = 'http://localhost:5000'
const BASE = ''

// 删除服务器上指定名称图片
export const reqDeletPic=(name)=>ajax(BASE+'/manage/img/delete',{name},'POST')

第二步 执行删除 src/pages/product/pictures-wall.jsx

修改函数:handleChange加入删除对应图片代码:

import {reqDeletPic} from '../../../api' //【1】

    /*
  file: 当前操作的图片文件(上传/删除)
  fileList: 所有已上传图片文件对象的数组
  官方文档:https://ant.design/components/upload-cn/#onChange
   */
  handleChange = async ({ file,fileList }) => { //【3】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'){//【2】如果文件的状态为移除,则删除服务器上对应图片名图片
      const result=await reqDeletPic(file.name)
      if(result.status===0){
        message.success('图片删除成功:'+file.name)
      }else{
        message.error('图片删除失败:'+file.name)
      }
    }

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

效果:

在这里插入图片描述

二、商品详情:富文本编辑器

1 使用基于react的富文本编程器插件库: react-draft-wysiwyg

官方文档:https://github.com/jpuri/react-draft-wysiwyg
官方实例:https://jpuri.github.io/react-draft-wysiwyg/#/demo
安装1:cnpm install --save react-draft-wysiwyg draft-js
安装2:cnpm i --save draftjs-to-html
安装3:cnpm i --save html-to-draftjs

第一步,建新组件src/pages/product/rich-text.jsx

import React, { Component } from 'react';
import { EditorState, convertToRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' //【1】引入编辑器样式,否则会乱七八糟


export default class RichText extends Component {
  state = {
    editorState: EditorState.createEmpty(),
  }

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

  render() {
    const { editorState } = this.state;
    return (
      <div>
        <Editor
          editorState={editorState}
          wrapperClassName="demo-wrapper"
          editorClassName="demo-editor"
          onEditorStateChange={this.onEditorStateChange}
          /*【3】给编辑器加内置样式,边框,高等*/
          editorStyle={{border: '1px solid black', minHeight: 200, paddingLeft: 10}}
        />
        <textarea
          disabled
          value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
        />
      </div>
    );
  }
}

第二步,引入编辑器组件

在add-updgate.jsx内引入组件

import RichText from './rich-text'

//render()内
<Item label='商品详情'>
    <RichText />
</Item>

第三步,编辑器尺寸改大

开始编辑器尺寸非常小,原因是/src/pages/product/add-update.jsx里定义了表单的格栅尺寸:
//form内的Item的布局样式
        const formItemLayout = {
            labelCol: {span: 2}, //左侧label标签的宽度占2个格栅
            wrapperCol: {span: 8 }, //右侧(输入框外面有一层包裹)占8个格栅
          };
修改编辑器大小/src/pages/product/add-update.jsx
<Item label='商品详情' labelCol={{span: 2}} wrapperCol={{span: 20}}>
      <RichText />
</Item>

2 父子传表单数据:点提交时获取富文本编辑器的内容

第一步,在rich-text.jsx内建立返回带html标签的字符串数据函数

//【1】让父组件获取到当前组件的信息(state之下建立即可)
 getDetail=()=>{
     return draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))
 }

第二步,在父组件add-update.jsx内接收子组件的数据

    constructor(props){
        super(props)
        //创建用于存放指定ref标识的标签对象容器
        this.pw=React.createRef()
        //【1】建立空容器
        this.editor=React.createRef()

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



//表单提交验证
     submit=()=>{
        this.props.form.validateFields((error,values)=>{            
            if(!error){
                //获取子组件的相关信息
                const imgs=this.pw.current.getImgs()  
                //【3】获取子组件商品详情的带html标签的字符串数据
                const detail=this.editor.current.getDetail()       
                alert('成功')
                //【4】输出看看
                console.log('表单,图片,详情:',values,imgs,detail)
            }else{             
                console.log('失败')
            }           
        })       
    }


//render(){}内
<Item label='商品详情' labelCol={{span: 2}} wrapperCol={{span: 20}}>
      {/**【2】指定把richtext对象装进editor里 */}
      <RichText ref={this.editor} />
</Item>

效果:填写商品信息,提交后,控制台输出

表单,图片,详情: {name: "游客131071427", desc: "a", price: "2", categoryIds: Array(1)} [] 
<p><strong>你好</strong><span style="color: rgb(226,80,65);">地地地柑</span>  <del>工 城</del>&nbsp;&nbsp;</p>

3 图片上传:没有按钮,加个,并实现功能rich-text.jsx

【1】图片上传按钮配置
【2】图片上传加一个上传按钮功能实现函数

//【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(){}
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>
    );
  }

效果:点富文本内的图片上传按钮可成功上传图片:

注意 alt 内容必须填个,否则无法上传图片
在这里插入图片描述

三、提交添加商品请求

第一步:编写api请求函数(src/api/index.js)

添加商品/修改商品:因为参数相同所以组成二合一接口,请求地址为按条件拼接,如果参数存在._id则为修改商品,否则为添加商品

//添加商品/修改商品:二合一接口,如果参数存在._id则为修改商品,否则为添加商品
export const reqAddUpdatePro=(product)=>ajax(BASE+'/manage/product/'+(product._id?'update':'add'),product,'POST')

第二步:收集表单数据(src/pages/product/add-update.jsx)

//表单提交验证
     submit=()=>{
        this.props.form.validateFields((error,values)=>{
            
            //【2】调用接口请求函数去添加/更新
            //【3】根据结果提示是否添加/更新成功
            if(!error){
                //【1】收集数据, 并封装成product对象
                const {name,desc,price,categoryIds}=values
                let pCategoryId,categoryId
                if(categoryIds.length===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()       
                //将收集到的表单数据封闭成product对象
                const product={name,desc,price,imgs,detail,pCategoryId,categoryId}
                //输出看看
                console.log(product)
            }else{                
                console.log('失败')
            }   
            
            
        })      
    }

2步效果:填写完成添加产品表单,点提交按钮:

//只有一级分类的产品数据
categoryId: "5e4154e725a557082c18f430"
desc: "工"
detail: "<p>手镯</p>↵<img src="http://localhost:5000/upload/image-1582702969070.jpg" alt="顶替" style="height: auto;width: auto"/>↵<p></p>↵"
imgs: Array(2)
0: "image-1582702950591.jpg"
1: "image-1582702955364.jpg"
length: 2
__proto__: Array(0)
name: "游客131071427"
pCategoryId: "0"
price: "12"
__proto__: Object

//二级分类的产品数据
categoryId: "5e4771a418da331714a18693"
desc: "工"
detail: "<p>手镯</p>↵<img src="http://localhost:5000/upload/image-1582702969070.jpg" alt="顶替" style="height: auto;width: auto"/>↵<p></p>↵"
imgs: Array(2)
0: "image-1582702950591.jpg"
1: "image-1582702955364.jpg"
length: 2
__proto__: Array(0)
name: "游客131071427"
pCategoryId: "5e41549925a557082c18f426"
price: "12"
__proto__: Object

第三步:提交数据(添加商品)

import {reqCategorys,reqAddUpdatePro} from '../../../api' //【0】引入添加修改产品函数

//产品表单提交
     submit=()=>{
        this.props.form.validateFields(async(error,values)=>{
                                   
            if(!error){
                //【1】解构values收集数据, 并封装成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()       
               //封闭成product对象
                const product={name,desc,price,imgs,detail,pCategoryId,categoryId}
                //输出看看
                console.log(product)

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

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

    }

3步效果:全部完成点提交,将显示添加产品成功

附:完整代码

1.商品管理主页src/pages/product/home.jsx

import React,{Component} from 'react'
import {
    Card,
    Select,
    Input,
    Table,
    Icon,
    Button,
    message
} from 'antd'
import LinkButton from '../../../components/link-button'
import {reqProducts} from '../../../api/' //【0】引入产品列表请求
import {PAGE_SIZE} from '../../../utils/constans' //【0.1】引入常量


const Option=Select.Option

export default class Home extends Component{
    state={
        //商品列表
         products:[
        //             {
        //                 "status": 1,
        //                 "imgs": [
        //                     "image-1559402396338.jpg"
        //                 ],
        //                 "_id": "5ca9e05db49ef916541160cd",
        //                 "name": "联想ThinkPad 翼4809",
        //                 "desc": "年度重量级新品,X390、T490全新登场 更加轻薄机身设计9",
        //                 "price": 65999,
        //                 "pCategoryId": "5ca9d6c0b49ef916541160bb",
        //                 "categoryId": "5ca9db9fb49ef916541160cc",
        //                 "detail": "<p><span style=\"color: rgb(228,57,60);background-color: rgb(255,255,255);font-size: 12px;\">想你所需,超你所想!精致外观,轻薄便携带光驱,内置正版office杜绝盗版死机,全国联保两年!</span> 222</p>\n<p><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\">联想(Lenovo)扬天V110 15.6英寸家用轻薄便携商务办公手提笔记本电脑 定制 2G独显 内置</span></p>\n<p><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\">99999</span></p>\n",
        //                 "__v": 0
        //             },
        //             {
        //                 "status": 1,
        //                 "imgs": [
        //                     "image-1559402448049.jpg",
        //                     "image-1559402450480.jpg"
        //                 ],
        //                 "_id": "5ca9e414b49ef916541160ce",
        //                 "name": "华硕(ASUS) 飞行堡垒",
        //                 "desc": "15.6英寸窄边框游戏笔记本电脑(i7-8750H 8G 256GSSD+1T GTX1050Ti 4G IPS)",
        //                 "price": 6799,
        //                 "pCategoryId": "5ca9d6c0b49ef916541160bb",
        //                 "categoryId": "5ca9db8ab49ef916541160cb",
        //                 "detail": "<p><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\">华硕(ASUS) 飞行堡垒6 15.6英寸窄边框游戏笔记本电脑(i7-8750H 8G 256GSSD+1T GTX1050Ti 4G IPS)火陨红黑</span>&nbsp;</p>\n<p><span style=\"color: rgb(228,57,60);background-color: rgb(255,255,255);font-size: 12px;\">1T+256G高速存储组合!超窄边框视野无阻,强劲散热一键启动!</span>&nbsp;</p>\n",
        //                 "__v": 0
        //             },
        //             {
        //                 "status": 2,
        //                 "imgs": [
        //                     "image-1559402436395.jpg"
        //                 ],
        //                 "_id": "5ca9e4b7b49ef916541160cf",
        //                 "name": "你不知道的JS(上卷)",
        //                 "desc": "图灵程序设计丛书: [You Don't Know JS:Scope & Closures] JavaScript开发经典入门图书 打通JavaScript的任督二脉",
        //                 "price": 35,
        //                 "pCategoryId": "0",
        //                 "categoryId": "5ca9d6c9b49ef916541160bc",
        //                 "detail": "<p style=\"text-align:start;\"><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\">图灵程序设计丛书:你不知道的JavaScript(上卷)</span> <span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\"><strong>[You Don't Know JS:Scope &amp; Closures]</strong></span></p>\n<p style=\"text-align:start;\"><span style=\"color: rgb(227,57,60);background-color: rgb(255,255,255);font-size: 12px;\">JavaScript开发经典入门图书 打通JavaScript的任督二脉 领略语言内部的绝美风光</span>&nbsp;</p>\n",
        //                 "__v": 0
        //             },
        //             {
        //                 "status": 2,
        //                 "imgs": [
        //                     "image-1554638240202.jpg"
        //                 ],
        //                 "_id": "5ca9e5bbb49ef916541160d0",
        //                 "name": "美的(Midea) 213升-BCD-213TM",
        //                 "desc": "爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护",
        //                 "price": 1388,
        //                 "pCategoryId": "5ca9d695b49ef916541160ba",
        //                 "categoryId": "5ca9d9cfb49ef916541160c4",
        //                 "detail": "<p style=\"text-align:start;\"><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;font-family: Arial, \"microsoft yahei;\">美的(Midea) 213升 节能静音家用三门小冰箱 阳光米 BCD-213TM(E)</span></p>\n<p><span style=\"color: rgb(228,57,60);background-color: rgb(255,255,255);font-size: 12px;font-family: tahoma, arial, \"Microsoft YaHei\", \"Hiragino Sans GB\", u5b8bu4f53, sans-serif;\">爆款直降!大容量三口之家优选! *节能养鲜,自动低温补偿,36分贝静音呵护! *每天不到一度电,省钱又省心!</span>&nbsp;</p>\n",
        //                 "__v": 0
        //             },
        //             {
        //                 "status": 1,
        //                 "imgs": [
        //                     "image-1554638403550.jpg"
        //                 ],
        //                 "_id": "5ca9e653b49ef916541160d1",
        //                 "name": "美的(Midea)KFR-35GW/WDAA3",
        //                 "desc": "正1.5匹 变频 智弧 冷暖 智能壁挂式卧室空调挂机",
        //                 "price": 2499,
        //                 "pCategoryId": "5ca9d695b49ef916541160ba",
        //                 "categoryId": "5ca9da1ab49ef916541160c6",
        //                 "detail": "<p style=\"text-align:start;\"><span style=\"color: rgb(102,102,102);background-color: rgb(255,255,255);font-size: 16px;\">美的(Midea)正1.5匹 变频 智弧 冷暖 智能壁挂式卧室空调挂机 KFR-35GW/WDAA3@</span></p>\n<p style=\"text-align:start;\"></p>\n<p><span style=\"color: rgb(228,57,60);background-color: rgb(255,255,255);font-size: 12px;\">提前加入购物车!2299元成交价!前50名下单送赠品加湿型电风扇,赠完即止!8日0点开抢!</span><a href=\"https://sale.jd.com/mall/LKHdqZUIYk.html\" target=\"_blank\"><span style=\"color: rgb(94,105,173);background-color: rgb(255,255,255);font-size: 12px;\">更有无风感柜挂组合套购立减500元!猛戳!!</span></a>&nbsp;</p>\n",
        //                 "__v": 0
        //             }
          ], 
          loading:false,
    }
    //Table的列名及对应显示的内容渲染
    initColumns=()=>{
        this.columns=[
            {
                title:'商品名称',
                dataIndex:'name'
            },
            {
                title:'商品描述',
                dataIndex:'desc'
            },
            {
                title:'价格',
                dataIndex:'price',
                render:(price)=>'¥'+price //把price渲染进对应的行,并加上¥符号
            },
            {
                width:100,
                title:'商品状态',
                dataIndex:'status',
                render:(status)=>{
                    return(
                        <span>
                            <Button type='primary'>{status===1 ? '下架' : '上架'}</Button>
                            <span>{status===1 ? '在售':'已下架'}</span>
                        </span>
                    )
                }
            },
            {
                width:100,
                title:'操作',
                
                render:(proObj)=>{
                    return(
                        <span>
                            <LinkButton>详情</LinkButton>
                            <LinkButton>修改</LinkButton>
                        </span>
                    )
                }
            },
        ]
    }

    // addPro=async()=>{
    //     const result=await reqAddPro('5e41549925a557082c18f426','0','桔子','desc','price','detail',[])
    //     console.log(result)
    //     if (result.status===0){
    //         message.success('产品添加成功')
    //     }else{
    //         message.error(result.msg)
    //     }
    // }

    //【1】请求产品列表放入state,后台分页
    getProducts=async(pageNum)=>{//pageNum为请求页码
        this.setState({loading:true}) //设置加载动画开始显示
        this.pageNum=pageNum //接收参数
        const result = await reqProducts({pageNum,PAGE_SIZE})
        this.setState({loading:true}) //关闭加载动画
        if(result.status===0){
            console.log(result.data.list)
            //this.setState({products:result.data})
        }else{
            message.error('加载产品失败,请刷新页面重试')
        }
    }

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

    componentDidMount(){
        this.getProducts(1)
        
    }

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

        //card左侧内容
        const title=(
            <span>
                <Select value='1' style={{width:150,}}>
                    <Option value='1'>按名称搜索</Option>
                    <Option value='2'>按描述搜索</Option>
                </Select>
                <Input placeholder='关键字' style={{width:150,margin:'0 8px'}} />
                <Button type='primary'>搜索</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} 
                columns={this.columns} />
            </Card>
        )
    }
}

商品管理主页效果http://localhost:3000/product

在这里插入图片描述

2.商品添加组件src/pages/product/add-update.jsx

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' //【0】引入添加修改产品函数
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=(categorys)=>{
        const options = categorys.map((v,k)=>({ //返回一个字典,要额外加一个括号            
                value: v._id,
                label: v.name,
                isLeaf: false,             
        }))

        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){
                //【1】收集数据, 并封装成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)


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

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



    }

    

      
    componentDidMount(){
        this.getCategorys('0') //加载categorys并初始化为
    }
      
    render(){
        //card左
        const title=(
            <span>
                <LinkButton>
                    <Icon type='arrow-left' style={{fontSize:20}} />                   
                </LinkButton>
                <span>添加商品</span>
            </span>
        )
        //card右
        
        //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:'',
                                rules:[
                                    {required:true,message:'商品名称必须填写'}
                                ]
                            })(<Input  placeholder='输入商品名' />)
                        }
                        
                    </Item>

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

                    <Item label='商品价格'>
                        {//validator自定义验证规则要求价格大于0
                            getFieldDecorator('price',{
                                initialValue:'',
                                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: [],
                            rules: [
                            {required: true, message: '必须指定商品分类'},
                            ]
                        })(<Cascader
                            placeholder='请指定商品分类'
                            options={this.state.options}  /*需要显示的列表数据数组*/
                            loadData={this.loadData} /*当选择某个列表项, 加载下一级列表的监听回调*/
                            />
                        )
                        }

                    </Item>

                    <Item label='商品图片'>
                        <PicturesWall ref={this.pw} />
                    </Item> 

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

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

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

商品添加页效果:http://localhost:3000/product/add-update

在这里插入图片描述

3.异步请求函数src/api/index.js

import ajax from './ajax'
import jsonp from 'jsonp'
import {message} from 'antd' //借用antd返回信息组件
// const BASE = 'http://localhost:5000'
const BASE = ''

//导出一个函数,第1种写法
//登录接口函数
// export function reqLogin(username,password){
//     return ajax('login',{username,password},'POST')
// }

//导出一个函数,第2种写法
// 登录接口函数
export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')


//获取产品一级/二级分类列表接口
export const reqCategorys=(parentId)=>ajax(BASE+'/manage/category/list',{parentId})
//添加产品分类接口
export const reqAddCategory=(parentId,categoryName)=>ajax(BASE+'/manage/category/add',{parentId,categoryName},'POST')
//修改产品分类接口
export const reqUpdateCategory=({categoryId,categoryName})=>ajax(BASE+'/manage/category/update',{categoryId,categoryName},'POST')


//获取产品列表
export const reqProducts=({pageNum,pageSize})=>ajax(BASE+'/manage/product/list',{pageNum,pageSize})
//添加商品/修改商品:二合一接口,如果参数存在._id则为修改商品,否则为添加商品
export const reqAddUpdatePro=(product)=>ajax(BASE+'/manage/product/'+(product._id?'update':'add'),product,'POST')


// 删除服务器上指定名称图片
export const reqDeletPic=(name)=>ajax(BASE+'/manage/img/delete',{name},'POST')







// 天气接口
export const reqWeather=(city) => {    
    const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city}&output=json&ak=3p49MVra6urFRGOT9s8UBWr2`
    //返回一个promise函数
    return new Promise((resolve,reject) => {
        //发送一个jsonp请求
        jsonp(url,{},(err,data) => {
            //输出请求的数据到控制台
            console.log('jsonp()', err, data)
            //如果请求成功
            if(!err && data.status==='success'){
                //从数据中解构取出图片、天气
                const {dayPictureUrl,weather}=data.results[0].weather_data[0]
                //异步返回图片、天气给调用函数者
                resolve({dayPictureUrl,weather})
            }else{//如果请求失败
                message.error('天气信息获取失败')
            }
        })
    })
}
//reqWeather('上海')

4.商品路由页src/page/pruduct/index.jsx

import React,{Component} from 'react'
import './index.less'
import {Switch,Route,Redirect} from 'react-router-dom'

import Home from './home'
import AddUpdate from './add-update'
import Detail from './detail'

export default class Product extends Component{
    render(){
        return(
            <Switch>
                {/* 为防止不能匹配到product/xxx,加上exact */}
                <Route exact path='/product' component={Home} />
                <Route path='/product/add-update' component={AddUpdate} />
                <Route path='/product/detail' component={Detail} />
                {/* 如果以上都不匹配则跳转到产品首页 */}
                <Redirect to='/product' />
            </Switch>
        )
    }
}

5.富文件编辑组件rich-text.jsx

import React, { Component } from 'react';
import { EditorState, convertToRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' //引入编辑器样式,否则会乱七八糟


export default class RichText extends Component {
  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>
    );
  }
}

6.图片上传组件src/page/product/pictures-wall.jsx

import React,{Component} from 'react'
import { Upload, Icon, Modal,message } from 'antd';
import {reqDeletPic} from '../../../api' //【1】

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 {
  constructor(props){
    super(props)

    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 }) => { //【3】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'){//【2】如果文件的状态为移除,则删除服务器上对应图片名图片
      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>
    );
  }
}
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值