fix(goods): 修复后台商品添加与编辑页面富文本编辑器嵌套与响应式绑定问题

这个提交包含在:
chen
2026-09-19 15:41:17 +08:00
父节点 aaaed1f83c
当前提交 f06538ddf6
共有 3 个文件被更改,包括 116 次插入 和 10 次删除

查看文件

@@ -917,9 +917,9 @@
<el-form :model="form.model" :rules="form.rules" ref="formRef4" label-width="100px">
<div v-show="state.activeStep==4">
<el-form-item class="editor-form-item" label="">
<form role="form">
<div class="editor-wrap" style="width: 100%;">
<textarea id="goodsContent" class="editor"></textarea>
</form>
</div>
</el-form-item>
</div>
</el-form>

查看文件

@@ -454,6 +454,53 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
},
})
function initEditor() {
let $editor = $('#goodsContent');
if (!$editor.length) return;
// 同步原始 textarea 属性与内容
if (typeof form.model.content !== 'undefined' && form.model.content !== null) {
$editor.val(form.model.content);
}
// 若已初始化 summernote,则同步 code 并返回
if ($editor.next('.note-editor').length > 0) {
if (typeof $editor.summernote === 'function') {
if (form.model.content && !$editor.summernote('code')) {
$editor.summernote('code', form.model.content);
}
}
return;
}
// 优先走 FastAdmin 标准 bindevent
Controller.api.bindevent();
// 容错兜底:若 150ms 内未自动实例化,则直接初始化 summernote
setTimeout(() => {
if ($editor.next('.note-editor').length === 0 && typeof $.fn.summernote !== 'undefined') {
var options = (window.Config && window.Config.summernote) || {};
$editor.summernote($.extend(true, {
height: parseInt(options.height || 300),
minHeight: parseInt(options.minHeight || 250),
lang: 'zh-CN',
dialogsInBody: true
}, $editor.data("summernote-options") || {}));
if (form.model.content) {
$editor.summernote('code', form.model.content);
}
}
}, 150);
}
watch(() => state.activeStep, (newStep) => {
if (newStep == 4) {
nextTick(() => {
initEditor();
});
}
});
function getDetail() {
Fast.api.ajax({
url: `shopro/goods/goods/detail/id/${state.id}`,
@@ -479,8 +526,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
form.model.service_ids = form.model.service_ids ? form.model.service_ids : [];
// 富文本
Controller.api.bindevent();
$('#goodsContent').html(form.model.content)
$('#goodsContent').val(form.model.content || '');
if (state.activeStep == 4) {
nextTick(() => {
initEditor();
});
}
getDispatchSelect()
return false
@@ -568,9 +619,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
getDetail()
} else {
getDispatchSelect()
nextTick(() => {
Controller.api.bindevent();
})
if (state.activeStep == 4) {
nextTick(() => {
initEditor();
})
}
}
}
return false
@@ -944,7 +997,14 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
delete submitForm.sn;
}
submitForm.content = $("#goodsContent").val()
let editorContent = '';
let $editor = $("#goodsContent");
if ($editor.next('.note-editor').length > 0 && typeof $editor.summernote === 'function') {
editorContent = $editor.summernote('code');
} else {
editorContent = $editor.val() || '';
}
submitForm.content = editorContent;
if (state.type == 'copy') {
delete submitForm.id;
@@ -1433,8 +1493,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
createApp('addStock', addStock);
},
api: {
bindevent: function () {
Form.api.bindevent($("form[role=form]"));
bindevent: function (container) {
var $target = container ? $(container) : ($("#goodsContent").closest("form").length ? $("#goodsContent").closest("form") : ($("form[role=form]").length ? $("form[role=form]") : $("#addEdit")));
Form.api.bindevent($target);
},
},
};

查看文件

@@ -0,0 +1,45 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');
console.log('--- 开始测试:商品详情富文本编辑器修复验证 ---');
const addHtmlPath = path.join(__dirname, '../application/admin/view/shopro/goods/goods/add.html');
const goodsJsPath = path.join(__dirname, '../public/assets/js/backend/shopro/goods/goods.js');
// 1. 验证 add.html 模板结构
console.log('测试 1: 验证 add.html 中编辑器 DOM 结构合规性...');
assert(fs.existsSync(addHtmlPath), 'add.html 必须存在');
const addHtml = fs.readFileSync(addHtmlPath, 'utf8');
// 验证没有在 el-form 内非法嵌套 form role="form"
assert(!addHtml.includes('<form role="form">'), 'add.html 不应包含非法的嵌套 <form role="form"> 标签');
assert(!addHtml.includes('</form>\n </el-form-item>'), 'el-form-item 内不应存在未闭合或多余的 </form>');
assert(addHtml.includes('<textarea id="goodsContent" class="editor"></textarea>'), '必须存在 id="goodsContent" class="editor" 的文本域');
assert(addHtml.includes('class="editor-wrap"'), '必须使用合法的 div 包装容器');
console.log(' [PASS] add.html 结构验证通过,已彻底移除 HTML 规范禁止的嵌套 form 标签。');
// 2. 验证 goods.js 中的编辑器控制逻辑
console.log('测试 2: 验证 goods.js 中的 initEditor 与 Tab 切换监听...');
assert(fs.existsSync(goodsJsPath), 'goods.js 必须存在');
const goodsJs = fs.readFileSync(goodsJsPath, 'utf8');
assert(goodsJs.includes('function initEditor()'), 'goods.js 必须包含 initEditor 函数');
assert(goodsJs.includes("watch(() => state.activeStep, (newStep) => {"), 'goods.js 必须监听 state.activeStep 变化');
assert(goodsJs.includes("if (newStep == 4)"), 'goods.js 必须在切换到商品详情 Tab(值为 4)时触发');
assert(goodsJs.includes("nextTick(() => {"), 'goods.js 必须在 nextTick 中初始化以确保 DOM 元素可见');
console.log(' [PASS] goods.js 中 initEditor 和 Tab 切换响应式逻辑验证通过。');
// 3. 验证 bindevent 容错容器解析
console.log('测试 3: 验证 Controller.api.bindevent 的容器容错解析...');
assert(goodsJs.includes('$("#goodsContent").closest("form")'), 'bindevent 必须包含对最近的 form 容器查找');
assert(goodsJs.includes('$("#addEdit")'), 'bindevent 必须包含兜底的父容器解析');
console.log(' [PASS] Controller.api.bindevent 容器容错解析逻辑验证通过。');
// 4. 验证 onConfirm 富文本内容读取逻辑
console.log('测试 4: 验证 onConfirm 内容读取与提取逻辑...');
assert(goodsJs.includes("$editor.summernote('code')"), 'onConfirm 必须优先从 summernote 实例提取内容');
assert(goodsJs.includes("$editor.val()"), 'onConfirm 必须包含回退读取 textarea val 的容错');
console.log(' [PASS] onConfirm 内容安全提取与回退逻辑验证通过。');
console.log('--- 全部测试通过!商品详情富文本编辑器修复逻辑完全正确! ---');