一、富文本braft-editor

【后台管理系统】—— Ant Design Pro结合插件(一)-LMLPHP

  • 安装并引用
    npm install braft-editor --save
    
    import BraftEditor from 'braft-editor'
    import 'braft-editor/dist/index.css'
  • state中初始内容

    editorState: BraftEditor.createEditorState(),
  • 表单中使用<BraftEditor/>

    <FormItem label="教程正文" {...formLayout}>
            {getFieldDecorator('content', {
                    validateTrigger: 'onBlur',
                    rules: [{
                        required: true,
                        validator: (_, value, callback) => {
                        if (value.isEmpty()) {
                            callback('请输入正文内容')
                        } else {
                            callback()
                        }
                        }
                    }],
                    // 内容必须通过BraftEditor.createEditorState()转化为可读的格式
                    initialValue: current && detail ? BraftEditor.createEditorState(defaultContent(detail.content)) : ''
                    })(
                        <BraftEditor
                            className="my-editor"
                            controls={controls}                         // 按需添加控件按钮
                            extendControls={extendControls}             // 自定义控件按钮
                            placeholder="请输入正文内容"
                            media={{                                    // 媒体对象
                                uploadFn: handleUploadFn,               // 上传七牛云服务器获取url
                                validateFn: handleValidateFn,           // 上传文件的限制
                                accepts: {                              //  可上传格式
                                  image: 'image/png, image/jpeg, image/jpg, image/gif, image/webp, image/apng, image/svg',
                                  video: 'video/mp4',
                                  audio: 'audio/mp3, audio/mp4, audio/ogg, audio/mpeg'
                                }
                            }}
                            onChange={this.handleContentChange}         // 内容更改方法
                        />
                    )}
    </FormItem>
  • 打开弹框时处理获取到的HTML格式的content,转化为插件可读格式,同时字符串切割替换转换后台传来的七牛云前缀与安卓ios视频图片需要的标签处理
    showEditModal = item => {
        const { dispatch } = this.props;
    
        let content = '';
        if(item.content){
          content = item.content;
          let contentObj = JSON.parse(BraftEditor.createEditorState(content).toRAW());
          let urlArr;
          Object.keys(contentObj.entityMap).forEach((key) => {
            if(contentObj.entityMap[key].data.url){
              urlArr = contentObj.entityMap[key].data.url.split('/')
              console.log('编辑时', urlArr)
              if(urlArr.length == 2){  //ios视频前缀yihezo
                urlArr.splice(0,1);
                contentObj.entityMap[key].data.url = `${setFileHost()}`+ 'yihezo/' + urlArr.join('/');
              }
    
              if(urlArr.length == 3){  //其它媒体文件前缀sys/tutorial
                urlArr.splice(0,1);
                contentObj.entityMap[key].data.url = `${setFileHost()}`+ 'sys/' + urlArr.join('/');
              }
            }
          });
          let contentRaw = JSON.stringify(contentObj);
          item.content = contentRaw;
        };
    
        dispatch({
          type: 'course/fetchDetail',
          payload: {
            id: item.id
          },
          callback: (res) => {
            if(res){
              let detail = res.data;
              let HTML = detail.content;
              HTML = HTML.substring(24).substring(0, HTML.length-6);
              HTML = HTML.replace(/style='max-width:100%' /g, "").replace(/poster=['|"].*?['|"]/g, "").replace(/alt='picvision' /g, "");
              detail.content = HTML;
    
              this.setState({
                detail,
                current: item,
                addSubmit: false
              }, () => {
                this.setState({
                  visible: true
                })
              });
            }
          }
        })
      };
  • 组件需要的参数和方法 
    // 自带的可按需选择的控件按钮
    const controls = [ 'undo', 'redo', 'separator',
            'font-size', 'line-height', 'letter-spacing', 'separator',
            'text-color', 'bold', 'italic', 'underline', 'strike-through', 'separator',
            'superscript', 'subscript', 'remove-styles', 'emoji',  'separator', 'text-indent', 'text-align', 'separator',
            'headings', 'list-ul', 'list-ol', 'separator',
            'link', 'separator', 'hr', 'separator',
            'media', 'separator',
            'clear'
    ];
    
    // 上传七牛云服务器获取url
    const handleUploadFn = (param) => {
         const { file } = param;
    
         const fileTypeArr = file.type.split('/');
         const fileType = fileTypeArr[0];
    
         if(fileType == 'video'){
              handleImageUpload(file, 'tutorialVideo').then(res => {
                param.success({
                  url: `${setFileHost()+res}`,
                  meta: {
                    id: new Date().getTime(),
                    loop: false,
                    autoPlay: false,
                    controls: true
                  }
                })
              })
         }else{
              handleImageUpload(file, 'tutorial').then(res => {
                param.success({
                  url: `${setFileHost()+res}`,
                  meta: {
                    id: new Date().getTime(),
                    loop: false,
                    autoPlay: false,
                    controls: true
                  }
                })
              })
            }
          }
    
         // 可上传文件的大小范围
         const handleValidateFn = (file) => {
            return file.size < 1024 * 1024 * 100
         }
    
         // content内容存在时,处理为可编辑数据
         const defaultContent = (content) => {
              let contentObj = JSON.parse(BraftEditor.createEditorState(content).toRAW());
              let urlArr;
              Object.keys(contentObj.entityMap).forEach((key) => {
                if(contentObj.entityMap[key].data.url){
                  urlArr = contentObj.entityMap[key].data.url.split('/')
                  console.log('默认内容', urlArr);
                  if(urlArr.length == 2){  //ios视频前缀yihezo
                    urlArr.splice(0,1);
                    contentObj.entityMap[key].data.url = `${setFileHost()}`+ 'yihezo/' + urlArr.join('/');
                  }
    
                  if(urlArr.length == 3){  //其它媒体文件前缀sys/tutorail
                    urlArr.splice(0,1);
                    contentObj.entityMap[key].data.url = `${setFileHost()}`+ 'sys/' + urlArr.join('/');
                  }
                }
              });
              let contentRaw = JSON.stringify(contentObj);
              return contentRaw;
          }
    
          // 自定义控件按钮
          const extendControls = [
            {
              key: 'custom-button',
              type: 'button',
              text: '预览',
              onClick: this.handleEditPreview
            }
          ];
  • react页面渲染HTML
    <div dangerouslySetInnerHTML = {{__html:返回的html代码片段}} ></div>

    原理:

    1.dangerouslySetInnerHTMl 是React标签的一个属性,类似于angular的ng-bind;

    2.有2个{{}},第一{}代表jsx语法开始,第二个是代表dangerouslySetInnerHTML接收的是一个对象键值对;

    3.既可以插入DOM,又可以插入字符串;  

二、图片裁剪  

         【后台管理系统】—— Ant Design Pro结合插件(一)-LMLPHP

  • 安装并引用
    npm install react-cropper --save
    
    import Cropper from 'react-cropper'
    import "cropperjs/dist/cropper.css"
  • Modal弹框包裹<Cropper />组件

    <Modal
         title="上传轮播图"
         visible={this.state.editImageModalVisible}
         width={500}
         bodyStyle={{height: 350, textAlign: 'center' }}
         maskClosable={false}
         onCancel={this.handleCancelImg}
         okText="确认上传"
         cancelText="取消"
         onOk={this.handleSaveImg}
         onCancel={this.handleCancelImg}
     >
            <Cropper
                  src={this.state.srcCropper}  //图片路径,即是base64的值,在Upload上传的时候获取到的
                  ref="cropper"
                  viewMode={1}                 //定义cropper的视图模式
                  aspectRatio={1/1}
                  zoomable={false}             //是否允许放大图像
                  movable={false}
                  guides={true}                //显示在裁剪框上方的虚线
                  background={false}           //是否显示背景的马赛克
                  rotatable={false}            //是否旋转
                  style={{ maxWidth:500, maxHeight: 300 }}
                  cropBoxResizable={true}      //是否可以拖拽
                  cropBoxMovable={true}        //是否可以移动裁剪框
                  dragMode="move"
                  center={true}
           />
    </Modal>
  • beforeUpload方法中在获得file之后,判断this.refs.cropper,设置组件所需参数

    beforeUpload = (file) => {
          let type = file.type.split('/')[0];
          let name = file.name.split('.')[0];
          if(type == 'video') {
            let imgArray = [...this.state.imgList];
            imgArray.push(file);
    
            handleImageUpload(file, 'video', name).then(res => {
              this.setState({
                imgList: imgArray
              });
              this.handleFileThumb(res, file, imgArray)
            })
          }else{
            //当打开同一张图片的时候清除上一次的缓存
            if (this.refs.cropper) {
              this.refs.cropper.reset();
            }
    
            var reader = new FileReader();
            const image = new Image();
            //因为读取文件需要时间,所以要在回调函数中使用读取的结果
            reader.readAsDataURL(file); //开始读取文件
    
            reader.onload = (e) => {
              image.src = reader.result;
              image.onload = () => {
                this.setState({
                  srcCropper: e.target.result, //cropper的图片路径
                  selectImgName: file.name, //文件名称
                  selectImgSize: (file.size / 1024 / 1024), //文件大小
                  selectImgSuffix: file.type.split("/")[1], //文件类型
                  editImageModalVisible: true, //打开控制裁剪弹窗的变量,为true即弹窗
                })
                if (this.refs.cropper) {
                  this.refs.cropper.replace(e.target.result);
                }
              }
            }
            return false;
          }
      }
  • 点击弹框中【确定/保存裁剪后的图片,上传七牛云获取url,在Upload组件中显示】,【取消/关闭弹框】

    handleSaveImg = () => {
        let imgArray = [...this.state.imgList];
        let imgFile = this.dataURLtoFile(this.refs.cropper.getCroppedCanvas().toDataURL(), this.state.selectImgName)
        imgArray.push(imgFile);
    
    
        // 上传七牛云方法 --- Upload组件使用中有js封装过程
        handleImageUpload(imgFile, 'image').then(res => {
          this.setState({
            imgList: imgArray,
            srcCropper: this.state.srcCropper, //cropper的图片路径
          }, () => {
             this.setState({
               editImageModalVisible: false
             })
          })
          this.handleFileThumb(res, imgFile, imgArray)
        })
      }
    
    
    handleCancelImg = () => {
        this.setState({
          editImageModalVisible: false,
        });
    }

三、统计图表  

     【后台管理系统】—— Ant Design Pro结合插件(一)-LMLPHP   

  • 官网图例:https://bizcharts.net/products/bizCharts
  • 安装和引用
    npm install bizcharts --save
    
    import {ChartCard, Field, TimelineChart} from '@/components/Charts';
    
    import {
      G2,
      Chart,
      Geom,
      Axis,
      Tooltip,
      Coord,
      Label,
      Legend,
      View,
      Guide,
      Shape,
      Facet,
      Util
    } from "bizcharts";

    官方折线图Mock数据示例:

    class Curved extends React.Component {
      render() {
        const data = [
          {
              month: "Jan",
              city: "Tokyo",
              temperature: 7
          },
          {
              month: "Jan",
              city: "London",
              temperature: 3.9
          },
          {
              month: "Feb",
              city: "Tokyo",
              temperature: 6.9
          },
          {
              month: "Feb",
              city: "London",
              temperature: 4.2
          },
          {
              month: "Mar",
              city: "Tokyo",
              temperature: 9.5
          },
          {
              month: "Mar",
              city: "London",
              temperature: 5.7
          },
          {
              month: "Apr",
              city: "Tokyo",
              temperature: 14.5
          },
          {
              month: "Apr",
              city: "London",
              temperature: 8.5
          },
          {
              month: "May",
              city: "Tokyo",
              temperature: 18.4
          },
          {
              month: "May",
              city: "London",
              temperature: 11.9
          },
          {
              month: "Jun",
              city: "Tokyo",
              temperature: 21.5
          },
          {
              month: "Jun",
              city: "London",
              temperature: 15.2
          },
          {
              month: "Jul",
              city: "Tokyo",
              temperature: 25.2
          },
          {
              month: "Jul",
              city: "London",
              temperature: 17
          },
          {
              month: "Aug",
              city: "Tokyo",
              temperature: 26.5
          },
          {
              month: "Aug",
              city: "London",
              temperature: 16.6
          },
          {
              month: "Sep",
              city: "Tokyo",
              temperature: 23.3
          },
          {
              month: "Sep",
              city: "London",
              temperature: 14.2
          },
          {
              month: "Oct",
              city: "Tokyo",
              temperature: 18.3
          },
          {
              month: "Oct",
              city: "London",
              temperature: 10.3
          },
          {
              month: "Nov",
              city: "Tokyo",
              temperature: 13.9
          },
          {
              month: "Nov",
              city: "London",
              temperature: 6.6
          },
          {
              month: "Dec",
              city: "Tokyo",
              temperature: 9.6
          },
          {
              month: "Dec",
              city: "London",
              temperature: 4.8
          }
        ];
        const cols = {
          month: {
            range: [0, 1]
          }
        };
        return (
          <div>
            <Chart height={400} data={data} scale={cols} forceFit>
              <Legend />
              <Axis name="month" />
              <Axis
                name="temperature"
                label={{
                  formatter: val => `${val}°C`
                }}
              />
              <Tooltip
                crosshairs={{
                  type: "y"
                }}
              />
              <Geom
                type="line"
                position="month*temperature"
                size={2}
                color={"city"}
                shape={"smooth"}
              />
              <Geom
                type="point"
                position="month*temperature"
                size={4}
                shape={"circle"}
                color={"city"}
                style={{
                  stroke: "#fff",
                  lineWidth: 1
                }}
              />
            </Chart>
          </div>
        );
      }
    }
    
    ReactDOM.render(<Curved />, mountNode)

转载请注明出处

03-23 14:29