背景:传统富文本编辑器上传图片,采用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.效果演示
预览
方案二 编辑器二 可直接修改上传地址不需要封装组件
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 })
}
}