【前端】富文本编辑器,支持自定义上传图片,视频,富文本预览 图片大图,压缩图片后上传

798 阅读5分钟

背景:传统富文本编辑器上传图片,采用base64格式上传,如果图片过大加载速度很慢,改为自定义上传到服务器

1.安装相应依赖

npm install vue-quill-editor --save
npm install quill-image-resize-module --save

2.组件代码

<!--
 * @Description  : 编辑器  可自定义上传 图片 视频
 * @Author       : wxy
 * @Date         : 2023-04-17 10:35:44
 * @LastEditTime : 2023-04-20 12:16:05
 * @LastEditors  : wxy
 * @FilePath     : \luban-front\src\components\QuillEditor\index.vue
-->
<template>
    <div style="height: 100%;">
        <!-- :action="action" -->
        <el-upload class="upload-demo" action="http://192.168.20.41:8082/tools/uploadS3File/" multiple :limit="3"
            :on-exceed="handleExceed" :file-list="fileList" :on-preview="handlePreview" :on-success="uploadSuccess"
            :on-remove="handleRemove" :before-upload="quillImgBefore" :show-file-list="false" :data="uploadData"
            :headers="headers">
            <el-button size="small" type="primary" id="imgInput" style="display: none;">上传</el-button>
        </el-upload>
        <quill-editor style="height: 80%;" v-bind="$attrs" v-on="$listeners" v-model="content" :options="editorOption"
            ref="myQuillEditor" @blur="onEditorBlur($event)" @focus="onEditorFocus($event)"
            @change="onEditorChange($event)">
        </quill-editor>
        <lb-dialog append-to-body v-model="videoVisible" :showCancel="false" :showConfirm="false" width="500px">
            <div class="video-wrapper" v-loading="loading" element-loading-text="视频上传中,请耐心等待!">
                <el-tabs v-model="activeName">
                    <el-tab-pane label="添加链接" name="1">
                        <div class="link-upload-wrapper">
                            <span>视频地址:</span>
                            <el-input size="small" v-model="videoUrl" style="width:70%;margin-right: 8px;"></el-input>
                            <el-button type="primary" @click="addVideoUrlToQuill" size="small">添加</el-button>
                        </div>
                    </el-tab-pane>
                    <el-tab-pane label="上传视频" name="2">
                        <el-upload class="avatar-uploader_video" :limit=1 action="#" :show-file-list="false"
                            :http-request="uploadVideoTip">
                            <i class="el-icon-plus avatar-uploader-icon"></i>
                        </el-upload>
                    </el-tab-pane>
                </el-tabs>
            </div>
        </lb-dialog>
    </div>
</template>
<script>
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import Quill from 'quill'
import { getToken } from "@/utils/auth";
import ImageResize from 'quill-image-resize-module'
Quill.register('modules/imageResize', ImageResize)
import { quillEditor } from 'vue-quill-editor'
import Video from './video';
Quill.register(Video, true)
import { toolbarOptions, imageResizeOptions } from './index'
export default {
    name: 'QuillEditor',
    components: {
        quillEditor
    },
    props: {
        value: {
            type: String,
            default: ''
        },
        uploadData: {
            type: Object,
            default: () => {
                return {
                    name: ''
                }
            }
        },
        uploadPath: {
            type: String,
            default: '/tools/uploadS3File/'
        },
    },
    data () {
        return {
            activeName: '1',
            videoVisible: false,
            videoIndex: 0,
            videoUrl: '',
            loading: false,
            videoUploadVisible: false,
            headers: {
                'Authorization': "Bearer " + getToken(),
            },
            fileList: [],
            editorOption: {
                modules: {
                    imageResize: imageResizeOptions,
                    toolbar: {
                        container: toolbarOptions,
                        handlers: {
                            'video': () => {
                                this.onVideo();
                            },
                            'image': function (value) {
                                if (value) {
                                    const fileInput = document.getElementById('imgInput')
                                    fileInput.click()
                                } else {
                                    this.quill.format('image', false);
                                }
                            }
                        }
                    }
                }
            },
        }
    },
    mounted () {
    },
    computed: {
        content: {
            get () {
                return this.value
            },
            set (val) {
                this.$emit('input', val)
            }
        },
        action () {
            return location.origin + this.uploadPath
        }
    },
    methods: {
        onVideo () {
            this.videoVisible = true;
            var range = this.$refs.myQuillEditor.quill.getSelection();
            if (range == null) {
                this.videoIndex = 0;
            } else {
                this.videoIndex = range.index;
            }
        },
        async uploadVideoTip (param) {
            this.loading = true;
            const { data: { file_name } } = await this.$api.testcaseApi.uploadS3File({ file: param.file })
            const { data: { file_url } } = await this.$api.testcaseApi.getS3FileUrl({ file_name })
            const quill = this.$refs.myQuillEditor.quill
            quill.insertEmbed(this.videoIndex, 'video', file_url)
            quill.setSelection(this.videoIndex + 1)
            this.loading = false;
            this.videoVisible = false;
        },
        addVideoUrlToQuill () {
            if (this.videoUrl) {
                const quill = this.$refs.myQuillEditor.quill
                quill.insertEmbed(this.videoIndex, 'video', this.videoUrl)
                quill.setSelection(this.videoIndex + 1)
                this.videoVisible = false;
            } else {
                this.$message.info('请填写视频链接')
            }
        },
        async uploadSuccess (response, file, fileList) {
            const { data: { file_name } } = response
            const { data: { file_url } } = await this.$api.testcaseApi.getS3FileUrl({ file_name })
            const quill = this.$refs.myQuillEditor.quill
            const addImageRange = quill.getSelection()
            const newRange = 0 + (addImageRange !== null ? addImageRange.index : 0)
            quill.insertEmbed(newRange, 'image', { src: file_url, file_name })
            quill.setSelection(1 + newRange)
        },
        quillImgBefore (file) {
            let fileType = file.type;
            if (fileType === 'image/jpeg' || fileType === 'image/png') {
                return true;
            } else {
                this.$message.error('请插入图片类型文件(jpg/jpeg/png)');
                return false;
            }
        },
        handleRemove (file, fileList) {
            console.log(file, fileList);
        },
        handlePreview (file) {
            console.log(file);
        },
        handleExceed (files, fileList) {
            this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
        },
        onEditorBlur () { }, // 失去焦点触发事件
        onEditorFocus () { }, // 获得焦点触发事件
        onEditorChange () { }, // 内容改变触发事件
    },
}
</script>
<style lang='scss' scoped>
.video-wrapper {
    width: 100%;
}

.avatar-uploader_video {
    width: 100px;
    height: 100px;
    border: 1px solid #D9D9D9;
    display: flex;
    justify-content: center;
    align-items: center;
}

.avatar-uploader-icon {
    font-size: 28px;
    color: #D9D9D9;
    line-height: 90px;
    text-align: center;
}
</style>

3.video.js

import { Quill } from "vue-quill-editor";
// 源码中是import直接倒入,这里要用Quill.import引入
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");

const ATTRIBUTES = ["height", "width"];

class Video extends BlockEmbed {
  static create(value) {
    const node = super.create(value);
    // 添加video标签所需的属性
    node.setAttribute("controls", "controls");
    node.setAttribute("type", "video/mp4");
    node.setAttribute("src", this.sanitize(value));
    return node;
  }
  static formats(domNode) {
    return ATTRIBUTES.reduce((formats, attribute) => {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute);
      }
      return formats;
    }, {});
  }

  static sanitize(url) {
    return Link.sanitize(url);
  }

  static value(domNode) {
    return domNode.getAttribute("src");
  }

  format(name, value) {
    if (ATTRIBUTES.indexOf(name) > -1) {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name);
      }
    } else {
      super.format(name, value);
    }
  }
  html() {
    const { video } = this.value();
    return `<a href="${video}">${video}</a>`;
  }
}
Video.blotName = "video"; // 不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot
Video.className = "ql-video";
Video.tagName = "video"; // 用video标签替换iframe
export default Video;

4.index.js

import QuillEditor from "vue-quill-editor";
const BlockEmbed = QuillEditor.Quill.imports["blots/embed"];
重写quil中的node,支持传入对象自定义属性。 quill.insertEmbed(newRange, 'image', { src: file_url, file_name })
class ImageBlot extends BlockEmbed {
  static create(value) {
    const node = super.create();
    node.setAttribute("src", value.src);
    node.setAttribute("file_name", value.file_name);
    return node;
  }

  static value(node) {
    return {
      src: node.getAttribute("src"),
      file_name: node.getAttribute("file_name")
    };
  }
}
ImageBlot.blotName = "image";
ImageBlot.tagName = "img";
QuillEditor.Quill.register(ImageBlot);
export const toolbarOptions = [
  ["bold", "italic", "underline", "strike"], // 加粗,斜体,下划线,删除线
  ["blockquote", "code-block"], // 引用,代码块
  [{ "header": 1 }, { "header": 2 }], // 标题,键值对的形式;1、2表示字体大小
  [{ "list": "ordered" }, { "list": "bullet" }], // 列表
  [{ "script": "sub" }, { "script": "super" }], // 上下标
  [{ "indent": "-1" }, { "indent": "+1" }], // 缩进
  [{ "direction": "rtl" }], // 文本方向
  [{ "size": ["small", false, "large", "huge"] }], // 字体大小
  [{ "header": [1, 2, 3, 4, 5, 6, false] }], // 几级标题
  [{ "color": [] }, { "background": [] }], // 字体颜色,字体背景颜色
  [{ "font": [] }], // 字体
  [{ "align": [] }], // 对齐方式
  ["clean"], // 清除字体样式
  ["link", "image", "video"] // 上传图片、上传视频
];
export const imageResizeOptions = {
  displayStyles: {
    backgroundColor: "black",
    border: "none",
    color: "white"
  },
  modules: ["Resize", "DisplaySize", "Toolbar"]
};

5.富文本上传后预览一般是使用v-html标签,解决v-html标签不能解析组件

<!--
 * @Description  :统一编辑组件 编辑时展示输入框编辑,详情展示文本(带格式)
 * @Author       : wxy
 * @Date         : 2023-02-03 18:25:19
 * @LastEditTime : 2023-04-20 10:35:02
 * @LastEditors  : wxy
 * @FilePath     : \luban-front\src\views\components\detailEdit\index.vue
-->
<template>
    <div class="wrapper">
        <div>
            {{ title }}
        </div>
        <div class="label-wrapper">
        <el-image style="display: none;" :src="imageSrc" :preview-src-list="imagePreviews">
        </el-image>
            <div v-if="hideEdit" v-html="labelFormat"></div>
            <slot v-if="isEdit"></slot>
            <span v-if="!hideEdit && !isEdit" v-html="labelFormat" class="label-class"></span>
        </div>
    </div>
</template>
<script>
import ImageRender from './ImageRender.vue'
export default {
    name: 'detailEdit',
    components: { ImageRender },
    props: {
        title: {
            type: String,
            default: ''
        },
        label: {
            type: String,
            default: ''
        },
        //不可编辑标识 
        hideEdit: {
            type: Boolean,
            default: false
        },
        isEdit: {
            type: Boolean,
            default: false
        }
    },

    data () {
        return {
            imageSrc: '',
            imagePreviews: []
        }
    },
    mounted () {
        // 必须挂载window,否则dom 给点击事件不生效
        window.previewImg = this.previewImg
    },
    watch: {
        imageSrc: {
            handler () {
                const imgDoms = document.getElementsByTagName('img')
                if (imgDoms.length > 0) {
                    for (let i = 0; i < imgDoms.length; i++) {
                        const element = imgDoms[i];
                        if (element.src.indexOf('s3.corp.kcb.com') > -1) {
                            //给富文本中的图片加点击事件,实际上就是找到el-image下面的img元素就可以点击触发预览。
                            element.onclick = window.previewImg
                        }
                    }
                }
            },
            immediate: true
        },
    },
    computed: {
        nativeFormat () {
            return ''
        },
        labelFormat () {
            // 传给后台传对应的filename  取filename后再通过filename去获取相应的链接(链接7天有效)
            let res = this.label ? this.label.replace(/\n/g, '<br>') : ''
            if (res.indexOf('img') > -1) {
                try {
                    // 将dom的字符串转换为dom对象 改变dom对象中img的url, 
                    var parser = new DOMParser();
                    var doc = parser.parseFromString(res, "text/html");
                    const imgs = doc.getElementsByTagName('img')
                    for (let i = 0; i < imgs.length; i++) {
                        const element = imgs[i];
                        const file_name = element.getAttribute('file_name')
                        this.$api.testcaseApi.getS3FileUrl({ file_name }).then(res => {
                            const { data: { file_url } } = res
                            element.src = file_url
                            this.imageSrc = file_url
                            this.imagePreviews = [...new Set([...this.imagePreviews, file_url])]
                        })
                    }
                    // 将dom对象重新转换为字符串,给到v-html渲染
                    var xmlSerializer = new XMLSerializer()
                    res = xmlSerializer.serializeToString(doc);

                } catch (error) {
                    return res
                }
            }
            return res
        },
    },
    methods: {
        previewImg (e) {
            const dom = document.getElementsByClassName('el-image__preview')[0]
            dom.click()
        }
    },
}
</script>
<style lang="scss" scoped>
.wrapper {
    padding: 10px 0;
    display: flex;
    align-items: center;

    // width: 100%;
    .label-wrapper {
        flex: 1;

        .label-class {
            display: inline-block;
            cursor: pointer;

            &:hover {
                background-color: #eee;
            }
        }
    }
}
</style>

6.效果演示

Snipaste_2023-04-20_14-13-59.png

Snipaste_2023-04-20_14-14-08.png

Snipaste_2023-04-20_14-14-15.png
预览 Snipaste_2023-04-20_14-16-54.png

方案二 编辑器二 可直接修改上传地址不需要封装组件

1.安装编辑器依赖

 "@packy-tang/vue-tinymce": "^1.1.2"
  "tinymce": "^5.1.5"
  // 此编辑器特别强大支持自定义钩子上传  file_picker_callback和images_upload_handler。
<template>
  <div class="richEditorWrap" id="richEditorWrap" :style="{ height: height + 'px' }" style="overflow: auto;">
    <vue-tinymce @change="onChanged" :content="content" :setup="setup" :setting="setting" />
  </div>
</template>
<script>
import Vue from "vue"
import tinymce from 'tinymce'

import VueTinymce from "@packy-tang/vue-tinymce"

//样式
import 'tinymce/skins/content/default/content.min.css'

//主题
import 'tinymce/themes/silver'
import "tinymce/icons/default/icons"

//插件
import 'tinymce/plugins/code'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/link' //链接插件
import 'tinymce/plugins/image' //图片插件
import 'tinymce/plugins/imagetools' //图片插件
import 'tinymce/plugins/media' //媒体插件
import 'tinymce/plugins/table' //表格插件
import 'tinymce/plugins/advlist' //列表插件
import 'tinymce/plugins/lists' //列表插件
import 'tinymce/plugins/quickbars' //快速栏插件
import 'tinymce/plugins/fullscreen' //全屏插件
import 'tinymce/plugins/preview'
import './plugins/importword'

/**
 * 注:
 * 5.3.x版本需要额外引进图标,没有所有按钮就会显示not found
 */
import 'tinymce/icons/default/icons'

//本地化
import './zh_CN'
import StyleStr from './normalize'
import { CompressFile } from '@/utils/common.js'

//安装组件
Vue.prototype.$tinymce = tinymce
Vue.use(VueTinymce)

export default {
  name: 'RichEditor',
  model: {
    prop: "content",
    event: "change"
  },
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
  props: {
    content: {
      type: [String, Object],
      default: ''
    },
    width: {
      type: [String, Number],
      default: 810,
    },
    height: {
      type: [String, Number],
      default: 500,
    },
    disabled: {
      type: Boolean,
      default: false,
    }
  },
  data: function () {
    const that = this
    return {
      setting: {
        // content_style: "img {max-width:100%;}",
        skin: "oxide",
        skin_url: "/tinymce/skins/ui/oxide",
        content_css: [
          "/tinymce/skins/ui/oxide/content.min.css",
          "/tinymce/skins/content/default/content.min.css"
        ],
        readonly: this.disabled || (this.elForm || {}).disabled,
        editorContainer: '',
        toolbar_mode: 'wrap',
        quickbars_selection_toolbar: "removeformat | bold italic underline strikethrough | fontsizeselect forecolor backcolor",
        quickbars_insert_toolbar: false,
        imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions',
        toolbar: 'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
                     styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
                     table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs importword kityformula-editor',
        plugins: "code link image imagetools table advlist lists fullscreen quickbars preview importword wordcount media paste",
        language: 'zh_CN', //本地化设置
        height: Number(this.height),
        // CONFIG: ContentStyle 这块很重要, 在最后呈现的页面也要写入这个基本样式保证前后一致, `table`和`img`的问题基本就靠这个来填坑了
        content_style: StyleStr,
        // FontSelect
        font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats;知乎配置=BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif;小米配置=Helvetica Neue,Helvetica,Arial,Microsoft Yahei,Hiragino Sans GB,Heiti SC,WenQuanYi Micro Hei,sans-serif',
        fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
        paste_data_images: true,
        file_picker_types: 'file image media',
        images_upload_credentials: '',
        file_picker_callback: function (callback, value, meta) {
          //文件分类
          let filetype = ''
          //为不同插件指定文件类型及后端地址
          switch (meta.filetype) {
            case 'image':
              filetype = '.jpg, .jpeg, .png, .gif'
              break
            case 'media':
              filetype = '.mp3, .mp4'
              break
            case 'file':
              filetype = 'application/pdf'
              break
            default:
          }
          const input = document.createElement('input')
          input.setAttribute('type', 'file')
          input.setAttribute('accept', filetype)
          input.onchange = async function () {
            var file = this.files[0]
            file = await new CompressFile().commonZipPic(file)
            var reader = new FileReader()
            reader.onload = async function () {
              var id = 'blobid' + (new Date()).getTime()
              var blobCache = tinymce.activeEditor.editorUpload.blobCache
              var base64 = reader.result.split(',')[1]

              var blobInfo = blobCache.create(id, file, base64)
              blobCache.add(blobInfo)
              //自定义上传 核心
              if (meta.filetype === 'image' || meta.filetype === 'media') {
                const { file_url, file_name } = await that.uploadS3Normal(blobInfo.blob())
                callback(file_url, { title: file_name })
              } else {
                const loading = that.$loading({
                  lock: true,
                  text: '正在上传pdf',
                  spinner: 'el-icon-loading',
                  background: 'rgba(0, 0, 0, 0.7)',
                  customClass: 'topLoading'
                })
                upload({
                  fileContent: blobInfo.base64(),
                  fileName: Date.now(),
                  fileSuffix: 'pdf',
                }).then((data) => {
                  callback(data.fileUrl, { title: file.name, text: file.name })
                }).finally(() => {
                  loading.close()
                })
              }
            }
            reader.readAsDataURL(file)
          }
          input.click()
        },
        images_file_types: 'jpeg, jpg, png, gif',
        urlconverter_callback: function (url, node, on_save, name) {
          if (node === 'img' && url.startsWith('blob:')) {
            tinymce.activeEditor && tinymce.activeEditor.uploadImages()
          }
          return url
        },
        //自定义上传 核心 能监测到复制图片的动作
        images_upload_handler: async (blobInfo, success, failure) => {
          let file = await new CompressFile().commonZipPic(blobInfo.blob())
          const { file_url } = await this.uploadS3Normal(file)
          success(file_url)
          // this.uploadHandle(blobInfo, success, failure); //下文的自定义函数
        }
      }
    }
  },
  methods: {
    uploadS3Normal (file) {
      return new Promise(async (resolve, _) => {
        const fileData = new FormData()
        fileData.append('file', file)
        const { data: { file_name } } = await this.$api.testcaseApi.uploadS3FileNormal(fileData)
        const { data: { file_url } } = await this.$api.testcaseApi.getS3FileUrl({ file_name })
        resolve({ file_url, file_name })
      })
    },
    setup (editor) {
      // console.log(editor)
    },
    uploadHandle (blobInfo, success, failure) {

      upload({
        fileContent: blobInfo.base64(),
        fileName: Date.now(),
        fileSuffix: 'png',
      }, (e) => {
        const progress = (e.loaded / e.total) * 100
      }).then(
        (data) => {
          success(data.fileUrl)
        },
        () => {
          failure('', { remove: true })
        }
      )

      // success('https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64154410ea9c4afb9db3a8cfddc2c1ef~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp');
      return blobInfo
    },
    onChanged (content) {
      this.$emit('change', content)
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

2.格式化样式文件 normalize

const styleStr = `
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Render the \`main\` element consistently in IE.
 */

main {
  display: block;
}

/**
 * Correct the font size and margin on \`h1\` elements within \`section\` and
 * \`article\` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * 1. Add the correct box sizing in Firefox.
 * 2. Show the overflow in Edge and IE.
 */

hr {
  box-sizing: content-box; /* 1 */
  height: 0; /* 1 */
  overflow: visible; /* 2 */
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd \`em\` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * Remove the gray background on active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * 1. Remove the bottom border in Chrome 57-
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd \`em\` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent \`sub\` and \`sup\` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove the border on images inside links in IE 10.
 */

img {
  border-style: none;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Show the overflow in IE.
 * 1. Show the overflow in Edge.
 */

button,
input { /* 1 */
  overflow: visible;
}

/**
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select { /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Correct the padding in Firefox.
 */

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

/**
 * 1. Correct the text wrapping in Edge and IE.
 * 2. Correct the color inheritance from \`fieldset\` elements in IE.
 * 3. Remove the padding so developers are not caught out when they zero out
 *    \`fieldset\` elements in all browsers.
 */

legend {
  box-sizing: border-box; /* 1 */
  color: inherit; /* 2 */
  display: table; /* 1 */
  max-width: 100%; /* 1 */
  padding: 0; /* 3 */
  white-space: normal; /* 1 */
}

/**
 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  vertical-align: baseline;
}

/**
 * Remove the default vertical scrollbar in IE 10+.
 */

textarea {
  overflow: auto;
}

/**
 * 1. Add the correct box sizing in IE 10.
 * 2. Remove the padding in IE 10.
 */

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  outline-offset: -2px; /* 2 */
}

/**
 * Remove the inner padding in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to \`inherit\` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button; /* 1 */
  font: inherit; /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in Edge, IE 10+, and Firefox.
 */

details {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* Misc
   ========================================================================== */

/**
 * Add the correct display in IE 10+.
 */

template {
  display: none;
}

/**
 * Add the correct display in IE 10.
 */

[hidden] {
  display: none;
}


html {
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
}

a {
  text-decoration: none;
}

input,
button,
textarea {
  color: inherit;
  font: inherit;
}

a,
input,
button,
textarea{
  &:focus {
    outline: none;
  }
}

ol,
ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
`

export default styleStr

3.压缩图片

export class CompressFile {
  // 图片通用压缩提示
  commonZipPic (file) {
    // MessageBox.confirm(
    //   `您上传的图片大小约为${(file.size / 1024).toFixed(
    //     2
    //   )}Kb,是否进行压缩上传?(此操作会降低图片质量)`
    //   , "提示", {
    //   confirmButtonText: "确定", cancelButtonText: "取消", type: "warning"
    // }).then(r = true).catch(r = false)
    const r = true
    if (r == true) {
      const _this = this
      return new Promise((resolve, reject) => {
        const image = new Image()
        let resultBlob = ""
        image.src = URL.createObjectURL(file)
        image.onload = () => {
          // 调用方法获取blob格式,方法写在下边
          resultBlob = _this.compressUpload(image, file)
          const fs = new File([resultBlob], file.name, {
            type: file.type,
          })
          // if (fs.size / 1024 > 100) {
          //   // this.commonZipPic(fs)
          //   this.$message.warning("压缩后图片仍大于100kb,请您手动压缩")
          //   reject()
          // }
          resolve(fs)
        }
        image.onerror = () => {
          reject()
        }
      })
    } else {
      return Promise.reject()
    }
  }
  /* 图片压缩方法-canvas压缩 */
  compressUpload (image, file) {
    const canvas = document.createElement("canvas")
    const ctx = canvas.getContext("2d")
    // const initSize = image.src.length
    const { width } = image
    const { height } = image
    // 大于等于1M时压缩
    const rate = file.size / 1024 / 1024 >= 1 ? 3 : 1
    canvas.width = width / rate
    canvas.height = height / rate
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    ctx.drawImage(image, 0, 0, width / rate, height / rate)
    // 进行最小压缩0.1
    const compressData = canvas.toDataURL(file.type || "image/jpeg", 0.3)
    // 压缩后调用方法进行base64转Blob,方法写在下边
    const blobImg = this.dataURItoBlob(compressData)
    return blobImg
  }
  /* base64转Blob对象 */
  dataURItoBlob (data) {
    let byteString
    if (data.split(",")[0].indexOf("base64") >= 0) {
      byteString = atob(data.split(",")[1])
    } else {
      byteString = unescape(data.split(",")[1])
    }
    const mimeString = data.split(",")[0].split(":")[1].split(";")[0]
    const ia = new Uint8Array(byteString.length)
    for (let i = 0; i < byteString.length; i += 1) {
      ia[i] = byteString.charCodeAt(i)
    }
    return new Blob([ia], { type: mimeString })
  }
}