CMVR-IOT-UI/src/components/Editor/index.vue

277 lines
7.9 KiB
Vue
Raw Normal View History

2025-08-21 14:11:58 +08:00
<template>
<div>
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
name="file"
:show-file-list="false"
:headers="headers"
class="editor-img-uploader"
v-if="type == 'url'"
>
<i ref="uploadRef" class="editor-img-uploader"></i>
</el-upload>
</div>
<div class="editor">
<quill-editor
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="(e) => $emit('update:modelValue', content)"
:options="options"
:style="styles"
/>
</div>
</template>
<script setup>
2026-07-28 16:26:07 +08:00
import axios from 'axios'
import { QuillEditor } from "@vueup/vue-quill"
import "@vueup/vue-quill/dist/vue-quill.snow.css"
import { getToken } from "@/utils/auth"
2025-08-21 14:11:58 +08:00
2026-07-28 16:26:07 +08:00
const { proxy } = getCurrentInstance()
2025-08-21 14:11:58 +08:00
2026-07-28 16:26:07 +08:00
const quillEditorRef = ref()
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // 上传的图片服务器地址
2025-08-21 14:11:58 +08:00
const headers = ref({
Authorization: "Bearer " + getToken()
2026-07-28 16:26:07 +08:00
})
2025-08-21 14:11:58 +08:00
const props = defineProps({
/* 编辑器的内容 */
modelValue: {
type: String,
},
/* 高度 */
height: {
type: Number,
default: null,
},
/* 最小高度 */
minHeight: {
type: Number,
default: null,
},
/* 只读 */
readOnly: {
type: Boolean,
default: false,
},
/* 上传文件大小限制(MB) */
fileSize: {
type: Number,
default: 5,
},
/* 类型base64格式、url格式 */
type: {
type: String,
default: "url",
}
2026-07-28 16:26:07 +08:00
})
2025-08-21 14:11:58 +08:00
const options = ref({
theme: "snow",
bounds: document.body,
debug: "warn",
modules: {
// 工具栏配置
toolbar: [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
["blockquote", "code-block"], // 引用 代码块
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
[{ indent: "-1" }, { indent: "+1" }], // 缩进
[{ size: ["small", false, "large", "huge"] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
["clean"], // 清除文本格式
["link", "image", "video"] // 链接、图片、视频
],
},
placeholder: "请输入内容",
readOnly: props.readOnly
2026-07-28 16:26:07 +08:00
})
2025-08-21 14:11:58 +08:00
const styles = computed(() => {
2026-07-28 16:26:07 +08:00
let style = {}
2025-08-21 14:11:58 +08:00
if (props.minHeight) {
2026-07-28 16:26:07 +08:00
style.minHeight = `${props.minHeight}px`
2025-08-21 14:11:58 +08:00
}
if (props.height) {
2026-07-28 16:26:07 +08:00
style.height = `${props.height}px`
2025-08-21 14:11:58 +08:00
}
2026-07-28 16:26:07 +08:00
return style
})
2025-08-21 14:11:58 +08:00
2026-07-28 16:26:07 +08:00
const content = ref("")
2025-08-21 14:11:58 +08:00
watch(() => props.modelValue, (v) => {
if (v !== content.value) {
2026-07-28 16:26:07 +08:00
content.value = v == undefined ? "<p></p>" : v
2025-08-21 14:11:58 +08:00
}
2026-07-28 16:26:07 +08:00
}, { immediate: true })
2025-08-21 14:11:58 +08:00
// 如果设置了上传地址则自定义图片上传事件
onMounted(() => {
if (props.type == 'url') {
2026-07-28 16:26:07 +08:00
let quill = quillEditorRef.value.getQuill()
let toolbar = quill.getModule("toolbar")
2025-08-21 14:11:58 +08:00
toolbar.addHandler("image", (value) => {
if (value) {
2026-07-28 16:26:07 +08:00
proxy.$refs.uploadRef.click()
2025-08-21 14:11:58 +08:00
} else {
2026-07-28 16:26:07 +08:00
quill.format("image", false)
2025-08-21 14:11:58 +08:00
}
2026-07-28 16:26:07 +08:00
})
quill.root.addEventListener('paste', handlePasteCapture, true)
2025-08-21 14:11:58 +08:00
}
2026-07-28 16:26:07 +08:00
})
2025-08-21 14:11:58 +08:00
// 上传前校检格式和大小
function handleBeforeUpload(file) {
2026-07-28 16:26:07 +08:00
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"]
const isJPG = type.includes(file.type)
2025-08-21 14:11:58 +08:00
//检验文件格式
if (!isJPG) {
2026-07-28 16:26:07 +08:00
proxy.$modal.msgError(`图片格式错误!`)
return false
2025-08-21 14:11:58 +08:00
}
// 校检文件大小
if (props.fileSize) {
2026-07-28 16:26:07 +08:00
const isLt = file.size / 1024 / 1024 < props.fileSize
2025-08-21 14:11:58 +08:00
if (!isLt) {
2026-07-28 16:26:07 +08:00
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false
2025-08-21 14:11:58 +08:00
}
}
2026-07-28 16:26:07 +08:00
return true
2025-08-21 14:11:58 +08:00
}
// 上传成功处理
function handleUploadSuccess(res, file) {
// 如果上传成功
if (res.code == 200) {
// 获取富文本实例
2026-07-28 16:26:07 +08:00
let quill = toRaw(quillEditorRef.value).getQuill()
2025-08-21 14:11:58 +08:00
// 获取光标位置
2026-07-28 16:26:07 +08:00
let length = quill.selection.savedRange.index
2025-08-21 14:11:58 +08:00
// 插入图片res.url为服务器返回的图片链接地址
2026-07-28 16:26:07 +08:00
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName)
2025-08-21 14:11:58 +08:00
// 调整光标到最后
2026-07-28 16:26:07 +08:00
quill.setSelection(length + 1)
2025-08-21 14:11:58 +08:00
} else {
2026-07-28 16:26:07 +08:00
proxy.$modal.msgError("图片插入失败")
2025-08-21 14:11:58 +08:00
}
}
// 上传失败处理
function handleUploadError() {
2026-07-28 16:26:07 +08:00
proxy.$modal.msgError("图片插入失败")
}
// 复制粘贴图片处理
function handlePasteCapture(e) {
const clipboard = e.clipboardData || window.clipboardData
if (clipboard && clipboard.items) {
for (let i = 0; i < clipboard.items.length; i++) {
const item = clipboard.items[i]
if (item.type.indexOf('image') !== -1) {
e.preventDefault()
const file = item.getAsFile()
insertImage(file)
}
}
}
}
function insertImage(file) {
const formData = new FormData()
formData.append("file", file)
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
handleUploadSuccess(res.data)
})
2025-08-21 14:11:58 +08:00
}
</script>
<style>
.editor-img-uploader {
display: none;
}
.editor, .ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: "保存";
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
</style>