Files
competition-website/src/components/RichEditor.vue

120 lines
3.1 KiB
Vue
Raw Normal View History

<template>
<div class="rich-editor" :style="{ zIndex: 3000 }">
<Toolbar
class="rich-editor__toolbar"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
class="rich-editor__body"
:style="{ height: height, overflowY: 'hidden' }"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css'
import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { useAuthStore } from '@/stores/auth'
const props = defineProps({
modelValue: { type: String, default: '' },
height: { type: String, default: '320px' },
placeholder: { type: String, default: '请输入内容…' },
mode: { type: String, default: 'default' }, // 'default' | 'simple'
// 是否开放表格菜单。综合大题题干需要富文本表格,故显式开启;
// 默认 false 保持资讯正文等既有调用方的工具栏不变(行为兼容)。
enableTable: { type: Boolean, default: false },
})
const emit = defineEmits(['update:modelValue'])
const authStore = useAuthStore()
// 编辑器实例:必须用 shallowRef
const editorRef = shallowRef()
// 内部 HTML 值
const valueHtml = ref(props.modelValue || '')
// 外部值变化时同步到编辑器(避免打开弹窗时内容不回填)
watch(
() => props.modelValue,
(val) => {
if (val !== valueHtml.value) {
valueHtml.value = val || ''
}
}
)
const toolbarConfig = computed(() => ({
excludeKeys: [
'group-video', // 题干/正文不需要视频
// 未开启表格时隐藏插入表格入口;开启后除去该项,表格相关菜单恢复默认可见
...(props.enableTable ? [] : ['insertTable']),
'codeBlock',
'fullScreen',
],
}))
const editorConfig = {
placeholder: props.placeholder,
MENU_CONF: {
// 图片上传复用后台统一图片上传接口
uploadImage: {
server: '/api/admin/upload/image',
fieldName: 'file',
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedFileTypes: ['image/*'],
headers: {
get Authorization() {
return authStore.token ? `Bearer ${authStore.token}` : ''
},
},
// 适配后端返回结构 { code:0, data:{ imageUrl } }
customInsert(res, insertFn) {
const url = res?.data?.imageUrl || res?.data?.image_url || res?.data?.url || ''
if (url) {
insertFn(url, '', url)
}
},
},
},
}
function handleCreated(editor) {
editorRef.value = editor
}
function handleChange(editor) {
emit('update:modelValue', editor.getHtml())
}
// 组件销毁时务必销毁编辑器实例,防止内存泄漏
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
</script>
<style scoped>
.rich-editor {
border: 1px solid #dcdfe6;
border-radius: 6px;
overflow: hidden;
width: 100%;
}
.rich-editor__toolbar {
border-bottom: 1px solid #e4e7ed;
}
.rich-editor__body {
background: #fff;
}
</style>