admin_new/src/components/RichEditor/RichEditor.vue

160 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="rich-editor">
<Toolbar class="rich-editor__toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" />
<Editor
class="rich-editor__body"
v-model="model"
:defaultConfig="editorConfig"
mode="default"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, onBeforeUnmount, watch } from "vue"
import { Editor, Toolbar } from "@wangeditor/editor-for-vue"
import "@wangeditor/editor/dist/css/style.css"
const model = defineModel<string>({ default: "" })
const editorRef = shallowRef<any>(null)
const valueHtml = ref(model.value || "")
// === 单文件上传 (admin_new: /api 前缀 + access_token, 5MB) ===
async function uploadFile(file: File): Promise<string> {
if (file.size > 5 * 1024 * 1024) throw new Error("图片大小不能超过5MB,请压缩后重试")
const fd = new FormData()
fd.append("file", file)
const token = localStorage.getItem("access_token") || ""
let resp: Response
try {
resp = await fetch("/api/sys/file/upload", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: fd,
})
} catch {
throw new Error("图片上传失败,请检查网络后重试")
}
if (resp.status === 413) throw new Error("图片超过大小限制(5MB),请压缩后重试")
const data = await resp.json()
if (data.code === 0) return data?.data?.url || data?.url || ""
throw new Error(data.msg || "图片上传失败")
}
// === 图片上传配置 (工具栏选择/拖拽/粘贴位图统一走 customUpload) ===
const editorConfig = {
placeholder: "请输入资讯内容…支持直接粘贴公众号图文(保留排版,图片自动转存平台)",
MENU_CONF: {
uploadImage: {
maxFileSize: 5 * 1024 * 1024,
async customUpload(file: File, insertFn: (url: string, alt: string, href: string) => void) {
try {
const url = await uploadFile(file)
insertFn(url, "", "")
} catch (e: any) {
alert(e?.message || "图片上传失败")
}
},
},
},
// 自定义粘贴: 处理剪贴板中的位图(截图) -> 上传平台后插入; 排版 HTML 交给编辑器默认逻辑
customPaste: (editor: any, event: ClipboardEvent, callback: (v: boolean) => void) => {
const items = event.clipboardData?.items
if (!items) return callback(true) // 无剪贴板数据, 走默认
const imageFiles: File[] = []
for (const it of Array.from(items)) {
if (it.type.startsWith("image/")) {
const f = it.getAsFile()
if (f) imageFiles.push(f)
}
}
// 无位图: 交给默认粘贴 (HTML 排版由 wangEditor 保留)
if (!imageFiles.length) return callback(true)
// 有位图: 阻止默认, 上传后插入
event.preventDefault()
;(async () => {
for (const f of imageFiles) {
try {
const url = await uploadFile(f)
editor.insertNode({ type: "image", src: url, alt: "", href: "" })
editor.insertNode({ type: "paragraph", children: [{ text: "" }] })
} catch (e: any) {
alert(e?.message || "图片上传失败")
}
}
})()
callback(false)
},
}
const toolbarConfig = {
excludeKeys: ["group-video", "fullScreen"],
toolbarKeys: [
"headerSelect",
"bold",
"italic",
"underline",
"through",
"color",
"bgColor",
"fontSize",
"fontFamily",
"lineHeight",
"justifyLeft",
"justifyCenter",
"justifyRight",
"bulletedList",
"numberedList",
"blockquote",
"codeBlock",
"insertLink",
"uploadImage",
"insertTable",
"undo",
"redo",
],
}
function handleCreated(editor: any) {
editorRef.value = editor
}
function handleChange(editor: any) {
valueHtml.value = editor.getHtml()
model.value = valueHtml.value
}
// 外部 v-model 变化时同步 (编辑回显)
watch(model, (v) => {
if (v !== valueHtml.value && editorRef.value) {
editorRef.value.setHtml(v || "")
}
})
onBeforeUnmount(() => {
if (editorRef.value) editorRef.value.destroy()
})
</script>
<style scoped>
.rich-editor {
border: 1px solid var(--el-border-color, #dcdfe6);
border-radius: 6px;
overflow: hidden;
}
.rich-editor__toolbar {
border-bottom: 1px solid var(--el-border-color, #dcdfe6);
}
.rich-editor__body {
min-height: 320px;
z-index: 100;
}
.rich-editor__body :deep(.w-e-text-container) {
min-height: 320px;
}
</style>