feat(editor): 增强 TinyMCE 编辑器功能与内容同步

- 提交前同步 PC 与移动端编辑器内容
- 增加编辑器未初始化时的待写入内容处理
- 优化内容读写方法,保证数据绑定准确
- 重构事件处理,避免初始化时覆盖 v-model 值
This commit is contained in:
田香琪
2026-08-24 12:46:39 +08:00
parent 085674dfa7
commit 3ca9aad9be
2 changed files with 74 additions and 29 deletions

View File

@@ -1452,6 +1452,22 @@ export default {
/** 查询商品参数 */ /** 查询商品参数 */
this.GET_GoodsParams(); this.GET_GoodsParams();
this.$nextTick(() => {
this.$refs.editor?.setContent?.(this.baseInfoForm.intro);
this.$refs.introEditor?.setContent?.(this.baseInfoForm.mobileIntro);
});
},
/** TinyMCE 的 v-model 可能滞后,提交前从编辑器实例取最新 HTML */
syncIntroFromEditors() {
const pcEditor = this.$refs.editor;
const mobileEditor = this.$refs.introEditor;
if (pcEditor?.getContent) {
this.baseInfoForm.intro = pcEditor.getContent() ?? "";
}
if (mobileEditor?.getContent) {
this.baseInfoForm.mobileIntro = mobileEditor.getContent() ?? "";
}
}, },
// 渲染sku数据 // 渲染sku数据
renderGoodsDetailSku(skuList) { renderGoodsDetailSku(skuList) {
@@ -1522,8 +1538,8 @@ export default {
}, },
// 将pc商品描述同步给移动端 // 将pc商品描述同步给移动端
promiseIntroEditor() { promiseIntroEditor() {
const pcContent = this.$refs.editor?.getContent?.() ?? this.baseInfoForm.intro ?? ""; this.syncIntroFromEditors();
this.baseInfoForm.intro = pcContent; const pcContent = this.baseInfoForm.intro ?? "";
this.baseInfoForm.mobileIntro = pcContent; this.baseInfoForm.mobileIntro = pcContent;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.introEditor?.setContent?.(pcContent); this.$refs.introEditor?.setContent?.(pcContent);
@@ -2360,6 +2376,7 @@ export default {
if (!this.$route.query.copyId) { if (!this.$route.query.copyId) {
this.baseInfoForm.goodsId = this.goodsId; this.baseInfoForm.goodsId = this.goodsId;
} }
this.syncIntroFromEditors();
let submit = JSON.parse(JSON.stringify(this.baseInfoForm)); let submit = JSON.parse(JSON.stringify(this.baseInfoForm));
if ( if (
submit.goodsGalleryFiles && submit.goodsGalleryFiles &&

View File

@@ -36,6 +36,7 @@ export default {
initEditor, initEditor,
hasChange: false, // 标记内容是否有更改 hasChange: false, // 标记内容是否有更改
hasInit: false, // 标记编辑器是否已初始化 hasInit: false, // 标记编辑器是否已初始化
pendingContent: null, // 编辑器未就绪时暂存待回填内容
tinymceId: tinymceId:
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""), // 生成唯一的编辑器 ID "tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""), // 生成唯一的编辑器 ID
fullscreen: false, // 标记编辑器是否处于全屏模式 fullscreen: false, // 标记编辑器是否处于全屏模式
@@ -43,9 +44,6 @@ export default {
content: "", // 编辑器内容 content: "", // 编辑器内容
}; };
}, },
created() {
this.init();
},
computed: { computed: {
bindValue() { bindValue() {
return this.modelValue ?? this.value ?? ""; return this.modelValue ?? this.value ?? "";
@@ -54,40 +52,55 @@ export default {
watch: { watch: {
bindValue: { bindValue: {
handler(val) { handler(val) {
if (!this.hasChange && this.hasInit) { if (!this.hasChange) {
const editor = window.tinymce.get(this.tinymceId); this.setContent(val || "");
if (editor) {
this.$nextTick(() => editor.setContent(val || ""));
}
} }
}, },
deep: true,
}, },
}, },
methods: { methods: {
emitContent(content) {
this.$emit("update:modelValue", content);
this.$emit("input", content);
},
getEditor() {
return window.tinymce?.get(this.tinymceId);
},
isEditorReady() {
const editor = this.getEditor();
return !!(editor && editor.initialized && editor.dom);
},
// 数据返回并给富文本框插入图片 // 数据返回并给富文本框插入图片
insertImage(arr){ insertImage(arr){
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img src="${v}" >`)) const editor = this.getEditor();
if (!editor || !this.isEditorReady()) {
return;
}
arr.forEach(v => editor.insertContent(`<img src="${v}" >`))
}, },
init() { init() {
// 初始化编辑器 // 初始化编辑器
this.initTinymce(); this.initTinymce();
}, },
initTinymce() { initTinymce() {
if (this.getEditor()) {
return;
}
const _this = this; const _this = this;
window.tinymce.init({ window.tinymce.init({
selector: `#${this.tinymceId}`, selector: `#${this.tinymceId}`,
convert_urls: false, convert_urls: false,
init_instance_callback: (editor) => { init_instance_callback: (editor) => {
if (_this.bindValue) {
_this.$nextTick(() => editor.setContent(_this.bindValue));
}
_this.hasInit = true; _this.hasInit = true;
editor.on("NodeChange Change KeyUp SetContent", () => { const initial = _this.pendingContent ?? _this.bindValue;
_this.pendingContent = null;
if (initial) {
_this.applyContent(editor, initial);
}
// 不监听 NodeChange/SetContent初始化或回填时会把空内容写回 v-model覆盖已加载的商品描述
editor.on("input change undo redo KeyUp", () => {
_this.hasChange = true; _this.hasChange = true;
const content = editor.getContent(); _this.emitContent(editor.getContent());
_this.$emit("update:modelValue", content);
_this.$emit("input", content);
}); });
}, },
setup(editor) { setup(editor) {
@@ -101,23 +114,38 @@ export default {
height:this.height height:this.height
}); });
}, },
setContent(value) { applyContent(editor, content) {
const editor = window.tinymce.get(this.tinymceId); if (!editor || !editor.initialized || !editor.dom) {
if (!editor) { return false;
return;
} }
try {
editor.setContent(content);
return true;
} catch (e) {
console.warn("tinymce setContent failed", e);
return false;
}
},
setContent(value) {
const content = value || ""; const content = value || "";
this.hasChange = false; this.hasChange = false;
editor.setContent(content); if (!this.isEditorReady()) {
this.$emit("update:modelValue", content); this.pendingContent = content;
this.$emit("input", content); return;
}
this.pendingContent = null;
this.applyContent(this.getEditor(), content);
this.emitContent(content);
}, },
getContent() { getContent() {
// 获取编辑器的内容 const editor = this.getEditor();
return window.tinymce.get(this.tinymceId).getContent(); if (!editor || !editor.initialized) {
return this.pendingContent ?? this.bindValue ?? "";
}
return editor.getContent();
}, },
destroyTinymce() { destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId); const tinymce = this.getEditor();
if (tinymce) { if (tinymce) {
// 销毁编辑器实例 // 销毁编辑器实例