1.Editor.vue组件
html
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// 定义编辑器实例,推荐用 shallowRef,避免深度监视,提升性能
const editorRef = shallowRef()
// 定义编辑器边框颜色的状态变量
const isFocused = ref(false)
// 定义mode变量
const mode = 'default'
//组件通信,传递editor内容
const emit = defineEmits(['change-content'])
// 定义编辑器HTML内容变量
const valueHtml = ref('')
// 接收父组件传递的初始内容,用于文章修改页面
const props = defineProps({
initContent: {
type: String,
default: ''
}
})
//编辑器工具栏配置
const toolbarConfig = {}
//编辑器默认配置
const editorConfig = {
placeholder: '请输入内容...',
autoFocus: false,
// 初始化 MENU_CONF 对象,用于图片上传
MENU_CONF: {
uploadImage: {
server: '/api/index.php/index/uploadImg',
fieldName: 'your-custom-name',
maxFileSize: 1 * 1024 * 1024, // 1M
maxNumberOfFiles: 10,
allowedFileTypes: ['image/*'],
meta: {
token: 'xxx',
otherKey: 'yyy',
},
metaWithUrl: false,
headers: {
Accept: 'text/x-json',
otherKey: 'xxx',
},
withCredentials: true,
timeout: 5 * 1000, // 5 秒
onBeforeUpload(file) {
return file
},
onProgress(progress) {
console.log('progress', progress)
},
onSuccess(file, res) {
console.log(`${file.name} 上传成功`, res)
},
onFailed(file, res) {
console.log(`${file.name} 上传失败`, res)
},
onError(file, err, res) {
console.log(`${file.name} 上传出错`, err, res)
},
}
}
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
// 在编辑器创建完成后,将父组件传来的内容同步给编辑器,
// 必须使用 editor.setHtml() 而不是直接修改 valueHtml
const handleCreated = (editor) => {
editorRef.value = editor
if (props.initContent) {
editor.setHtml(props.initContent)
}
}
//编辑器内容变化监听
const handleChange = () => {
emit('change-content', valueHtml.value)
}
// focus功能:wangEditor提供了focus()方法,可直接调用
const focusEditor = () => {
editorRef.value?.focus();
};
// 编辑器focus颜色改变:处理聚焦事件
const handleFocus = () => {
isFocused.value = true
}
// 编辑器focus颜色改变:处理失焦事件
const handleBlur = () => {
isFocused.value = false
}
// 清空编辑器内容
const clearContent = () => {
if (editorRef.value) {
// 根据你使用的编辑器,调用对应的清空API
editorRef.value.clear()
}
}
// 将聚焦方法、清空、监听内容变化暴露给父组件
defineExpose({
focusEditor,
clearContent,
handleBlur
});
</script>
<template>
<div :class="['editor-wrapper', { 'editor-focused': isFocused }]">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
<!-- Editor组件中添加了一个 on-change事件来传递编辑器的值-->
<Editor style="height: 500px; overflow-y: hidden;" v-model="valueHtml" :defaultConfig="editorConfig" :mode="mode"
@onCreated="handleCreated" @on-change="handleChange" @onFocus="handleFocus" @onBlur="handleBlur" />
</div>
</template>
<style scoped>
.editor-wrapper {
border: 1px solid #ccc;
transition: border-color 0.3s ease;
}
/* 聚焦时的蓝绿色边框 */
.editor-focused {
border-color: #1ec4a0;
}
</style>
2.updaetArticle.vue页面
html
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { showArticle_u, updateArticle } from '@/api/updateArticle'
import { toast } from '@/components/Toast'
import { ref } from 'vue'
import Editor from '@/components/Editor.vue'
const route = useRoute()
const router = useRouter()
const submitting = ref(false) // 防重复提交状态
const inputRef = ref(null)
const editorRef = ref(null)
const id = route.params.id
const title = ref('')
const content = ref('')
const handleChange = (val) => {
content.value = val
}
const getArticle = async () => {
const res = await showArticle_u({ id })
if (res.code === 200) {
title.value = res.data.title
content.value = res.data.content
document.title = "修改文章-" + res.data.title
} else {
console.log(res.msg)
}
}
getArticle()
//提交文章
const handleSubmit = async () => {
if (submitting.value) return // 【新增】如果正在提交,直接返回
// 验证标题
if (title.value === '') {
toast({ msg: '标题为空', type: 'error' })
inputRef.value?.focus()
return
}
// 验证编辑器内容
if (content.value === '<p><br></p>') {
toast({ msg: '编辑器内容为空', type: 'error' })
editorRef.value?.focusEditor()
return
}
// 验证通过,提交
submitting.value = true // 【新增】开启加载状态
try {
const res = await updateArticle({
id: id,
title: title.value,
content: content.value
})
toast({ msg: '修改成功', type: 'success' })
router.push(`/detail/id/${id}`)
console.log(res)
} catch (err) {
console.error('请求失败', err)
// 提示后端返回的具体错误信息
toast({ msg: err.response?.data?.message || '提交失败,请稍后重试', type: 'error' })
console.log(err.response?.data?.message)
} finally {
submitting.value = false // 【新增】无论成功失败,都关闭加载状态
}
}
</script>
<template>
<div class="box">
<form @submit.prevent="handleSubmit">
文章ID:{{ id }}
<br /><br />
<input ref="inputRef" class="input_title" v-model="title" placeholder="标题">
<br /><br />
<Editor ref="editorRef" v-if="content" :init-content="content" @change-content="handleChange" />
<br />
<button :disabled="submitting" class="button_form" type="submit">修改</button>
</form>
</div>
</template>
<style scoped>
.box {
width: 90%;
margin: 0 auto;
margin-top: 50px;
}
</style>
3.注意事项
错误示例:
js
if (props.initContent) {
editor.setHtml(props.initContent)
}
valueHtml.value = editor.getHtml()
在Editor.vue中,应避免同时使用editor.setHtml和editor.getHtml()。
隐患:
setHtml 是异步解析的,紧接着执行 getHtml() 时,编辑器可能还没解析完,导致拿到残缺的 HTML,然后 v-model 又把残缺的 HTML 覆盖回编辑器。
解决办法:
删掉 valueHtml.value = editor.getHtml() 这一行。v-model 会自动处理双向绑定,不需要手动赋值。