安装
pnpm install @vueup/vue-quill@beta --save
components 文件夹下新建 quillEditor.vue文件
element-plus el-upload版
<template>
<div style="margin-bottom: 5px; border-radius: 10px">
<el-upload class="editor-img-uploader" :action="upLoadUrl" :show-file-list="false" :headers="headers"
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<i class="el-icon-plus editor-img-uploader"></i>
</el-upload>
<QuillEditor id="editorId" ref="myQuillEditor" v-model:content="content" theme="snow" @update:content="onEditorUpdate($event)"
contentType="html" :options="options" />
</div>
</template>
<script setup>
import { getCode as uploadImage } from '@/api/user'
import { QuillEditor, Quill } from "@vueup/vue-quill";
import { reactive, ref, toRaw, defineEmits, defineProps } from "vue";
import { ElMessage } from "element-plus";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
const props = defineProps({
value: { type: String, default: "" },
size: { type: Number, default: 5}
})
let content = ref("");
let size = ref("")
content.value = props.value;
size.value = props.size
watch(props.value, (newVal, oldVal) => {
content.value = props.value
}, {
deep: true, // 深度监听
immediate: true, // 立即执行
})
let upLoadUrl = ref(uploadImage);
let headers = reactive({
token: sessionStorage.getItem("token"),
});
const myQuillEditor = ref(null)
// 工具栏配置
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用
[{ 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'] // 链接、图片、视频
]
const options = reactive({
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
image: function (value) {
if (value) {
// 调用element图片上传
document
.querySelector(".editor-img-uploader>.el-upload")
.click();
} else {
Quill.format("image", true);
}
},
link: function (value) {
if (value) {
var href = prompt('请输入链接地址:')
this.quill.format('link', href)
} else {
this.quill.format('link', false)
}
},
video: function (value) {
if (value) {
var href = prompt('请输入视频链接:')
this.quill.format('video', href)
} else {
this.quill.format('video', false)
}
}
},
},
history: {
delay: 1000,
maxStack: 50,
userOnly: false
},
},
});
// 图片上传成功返回图片地址
function handleAvatarSuccess(res, file) {
// 如果上传成功
if (res) {
// 获取富文本实例
let quill = toRaw(myQuillEditor.value).getQuill();
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片,res为服务器返回的图片链接地址
quill.insertEmbed(length, "image", res);
// 调整光标到最后
quill.setSelection(length + 1);
} else {
ElMessage({
message: "提交失败!",
type: "error",
});
}
}
// 图片上传前拦截
function beforeAvatarUpload(file) {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const isJPG = type.includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
ElMessage({
message: "图片格式错误",
type: "success",
});
}
if (!isLt2M) {
ElMessage({
message: "上传图片不能超过" + size.value + "M",
type: "success",
});
}
return isJPG && isLt2M;
}
const emit = defineEmits(['change'])
const onEditorUpdate = () => emit('change', this.content)
</script>
<style scoped lang='scss'>
.editor-img-uploader {
display: none;
}
.ql-editor {
min-height: 300px;
}
</style>
input版 -- 推荐
<template>
<div style="margin-bottom: 5px; border-radius: 10px">
<QuillEditor id="editorId" ref="myQuillEditor" v-model:content="content" theme="snow" @update:content="onEditorUpdate($event)"
contentType="html" :options="options" />
<input id="upload" type="file" style="display: none" accept="image/*" @change="handleInputChange">
</div>
</template>
<script setup>
import { getCode as uploadImage } from '@/api/user'
import { QuillEditor, Quill } from "@vueup/vue-quill";
import { reactive, ref, toRaw, defineEmits, defineProps } from "vue";
import { ElMessage } from "element-plus";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
const props = defineProps({
value: { type: String, default: "" },
size: { type: Number, default: 5 }
})
let content = ref("");
let size = ref("");
content.value = props.value;
size.value = props.size;
watch(props.value, (newVal, oldVal) => {
content.value = props.value
}, {
deep: true, // 深度监听
immediate: true, // 立即执行
})
const myQuillEditor = ref(null)
// 工具栏配置
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用
[{ 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'] // 链接、图片、视频
]
const options = reactive({
modules: {
toolbar: {
container: toolbarOptions,
handlers: {
image: function (value) {
if (value) {
// 调用element图片上传
document
.querySelector("#upload")
.click();
} else {
Quill.format("image", true);
}
},
link: function (value) {
if (value) {
var href = prompt('请输入链接地址:')
Quill.format('link', href)
} else {
Quill.format('link', false)
}
},
video: function (value) {
if (value) {
var href = prompt('请输入视频链接:')
Quill.format('video', href)
} else {
Quill.format('video', false)
}
}
},
},
history: {
delay: 1000,
maxStack: 50,
userOnly: false
},
},
});
const dataURItoBlob = (dataURI, type) => {
// atob用于解码使用 base-64 编码的字符串
var binary = atob(dataURI.split(',')[1])
var array = []
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i))
}
return new Blob([new Uint8Array(array)], { type: type })
}
const handleInputChange = (e) => {
const file = e.target.files[0]
if (!/image\/\w+/.test(file.type)) {
ElMessage.error('图片格式不正确')
return
}
const isLt5M = file.size / 1024 / 1024 < size
if (!isLt5M) {
ElMessage.error(`图片大小不能超过 ${size.value}MB!`)
return
}
const reader = new FileReader()
const image = new Image()
image.onload = () => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 根据原图片大小和图片压缩配置确定压缩后图片的宽和高
const width = image.width * 0.5
const height = image.height * 0.5
canvas.width = width
canvas.height = height
// 清空canvas画布上填充的矩形
context.clearRect(0, 0, width, height)
// 将原图片按压缩后的尺寸大小绘制在canvas画布中
context.drawImage(image, 0, 0, width, height)
const dataUrl = canvas.toDataURL(file.type)
// 将base64格式的图片转换为Blob对象
const blobData = dataURItoBlob(dataUrl, file.type)
// 将文件流转换为formData对象
const formData = new FormData()
formData.append('file', blobData)
uploadImage(formData)
.then((res) => {
if (res.data.code === 200) {
// 获取富文本实例
let quill = toRaw(myQuillEditor.value).getQuill();
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片,res为服务器返回的图片链接地址
quill.insertEmbed(length, "image", res);
// 调整光标到最后
quill.setSelection(length + 1);
}
})
}
reader.onload = (e) => {
image.src = e.target.result
}
reader.readAsDataURL(file)
}
/*
*注册emit事件
*/
const emit = defineEmits(['change'])
/*
* 当内容变动时,把内容发给富文本调用者,触发change事件
*/
const onEditorUpdate = () => emit('change', content)
</script>
<style scoped lang='scss'>
.editor-img-uploader {
display: none;
}
.ql-editor {
min-height: 300px;
}
</style>