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


    一、富文本braft-editor

    • 安装并引用
      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-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,又可以插入字符串;  

    二、图片裁剪  

             

    • 安装并引用
      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,
          });
      }
      

    三、统计图表  

            

    • 官网图例: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)

    转载请注明出处

  • 相关阅读:
    04 链表(上):如何实现LRU缓存淘汰算法?
    03 数组:为什么很多编程语言中数组都从0开始编号?
    02 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度
    01 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
    Winform PictureBox图片旋转
    我的第一篇博客
    redis分布式锁实现与思考
    java 时间字符串中毫秒值时有时无,怎么解析
    spring 接收处理 json 类型的请求(spring 默认使用jackson 处理接收的数据), json 字段的中的Date 类型会自动 转换为 Long 类型
    java 中的正则使用
  • 原文地址:https://www.cnblogs.com/ljq66/p/11919062.html
Copyright © 2020-2023  润新知