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.$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数据
renderGoodsDetailSku(skuList) {
@@ -1522,8 +1538,8 @@ export default {
},
// 将pc商品描述同步给移动端
promiseIntroEditor() {
const pcContent = this.$refs.editor?.getContent?.() ?? this.baseInfoForm.intro ?? "";
this.baseInfoForm.intro = pcContent;
this.syncIntroFromEditors();
const pcContent = this.baseInfoForm.intro ?? "";
this.baseInfoForm.mobileIntro = pcContent;
this.$nextTick(() => {
this.$refs.introEditor?.setContent?.(pcContent);
@@ -2360,6 +2376,7 @@ export default {
if (!this.$route.query.copyId) {
this.baseInfoForm.goodsId = this.goodsId;
}
this.syncIntroFromEditors();
let submit = JSON.parse(JSON.stringify(this.baseInfoForm));
if (
submit.goodsGalleryFiles &&

View File

@@ -36,6 +36,7 @@ export default {
initEditor,
hasChange: false, // 标记内容是否有更改
hasInit: false, // 标记编辑器是否已初始化
pendingContent: null, // 编辑器未就绪时暂存待回填内容
tinymceId:
"tinymce-" + +new Date() + ((Math.random() * 1000).toFixed(0) + ""), // 生成唯一的编辑器 ID
fullscreen: false, // 标记编辑器是否处于全屏模式
@@ -43,9 +44,6 @@ export default {
content: "", // 编辑器内容
};
},
created() {
this.init();
},
computed: {
bindValue() {
return this.modelValue ?? this.value ?? "";
@@ -54,40 +52,55 @@ export default {
watch: {
bindValue: {
handler(val) {
if (!this.hasChange && this.hasInit) {
const editor = window.tinymce.get(this.tinymceId);
if (editor) {
this.$nextTick(() => editor.setContent(val || ""));
}
if (!this.hasChange) {
this.setContent(val || "");
}
},
deep: true,
},
},
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){
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() {
// 初始化编辑器
this.initTinymce();
},
initTinymce() {
if (this.getEditor()) {
return;
}
const _this = this;
window.tinymce.init({
selector: `#${this.tinymceId}`,
convert_urls: false,
init_instance_callback: (editor) => {
if (_this.bindValue) {
_this.$nextTick(() => editor.setContent(_this.bindValue));
}
_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;
const content = editor.getContent();
_this.$emit("update:modelValue", content);
_this.$emit("input", content);
_this.emitContent(editor.getContent());
});
},
setup(editor) {
@@ -101,23 +114,38 @@ export default {
height:this.height
});
},
setContent(value) {
const editor = window.tinymce.get(this.tinymceId);
if (!editor) {
return;
applyContent(editor, content) {
if (!editor || !editor.initialized || !editor.dom) {
return false;
}
try {
editor.setContent(content);
return true;
} catch (e) {
console.warn("tinymce setContent failed", e);
return false;
}
},
setContent(value) {
const content = value || "";
this.hasChange = false;
editor.setContent(content);
this.$emit("update:modelValue", content);
this.$emit("input", content);
if (!this.isEditorReady()) {
this.pendingContent = content;
return;
}
this.pendingContent = null;
this.applyContent(this.getEditor(), content);
this.emitContent(content);
},
getContent() {
// 获取编辑器的内容
return window.tinymce.get(this.tinymceId).getContent();
const editor = this.getEditor();
if (!editor || !editor.initialized) {
return this.pendingContent ?? this.bindValue ?? "";
}
return editor.getContent();
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId);
const tinymce = this.getEditor();
if (tinymce) {
// 销毁编辑器实例