chore: 前端首次基线提交
运行必须源码:src/、public/、index.html、package.json 及构建配置。 已忽略运行非必须:node_modules/、dist/、.env*。
This commit is contained in:
115
src/components/RichEditor.vue
Normal file
115
src/components/RichEditor.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<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 { 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'
|
||||
})
|
||||
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 = {
|
||||
excludeKeys: [
|
||||
'group-video', // 轮播正文不需要视频
|
||||
'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>
|
||||
Reference in New Issue
Block a user