From f06538ddf62bc289a87eb74ec1ae8fdb7c9cc8df Mon Sep 17 00:00:00 2001 From: chen Date: Sat, 19 Sep 2026 15:41:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(goods):=20=E4=BF=AE=E5=A4=8D=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=95=86=E5=93=81=E6=B7=BB=E5=8A=A0=E4=B8=8E=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E9=A1=B5=E9=9D=A2=E5=AF=8C=E6=96=87=E6=9C=AC=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E5=B5=8C=E5=A5=97=E4=B8=8E=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=BC=8F=E7=BB=91=E5=AE=9A=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/view/shopro/goods/goods/add.html | 4 +- .../assets/js/backend/shopro/goods/goods.js | 77 +++++++++++++++++-- tests/verify_goods_editor_fix_spec.js | 45 +++++++++++ 3 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 tests/verify_goods_editor_fix_spec.js diff --git a/application/admin/view/shopro/goods/goods/add.html b/application/admin/view/shopro/goods/goods/add.html index c614740..0a698d9 100644 --- a/application/admin/view/shopro/goods/goods/add.html +++ b/application/admin/view/shopro/goods/goods/add.html @@ -917,9 +917,9 @@
-
+
- +
diff --git a/public/assets/js/backend/shopro/goods/goods.js b/public/assets/js/backend/shopro/goods/goods.js index 1849666..0ad3e95 100644 --- a/public/assets/js/backend/shopro/goods/goods.js +++ b/public/assets/js/backend/shopro/goods/goods.js @@ -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); }, }, }; diff --git a/tests/verify_goods_editor_fix_spec.js b/tests/verify_goods_editor_fix_spec.js new file mode 100644 index 0000000..173472f --- /dev/null +++ b/tests/verify_goods_editor_fix_spec.js @@ -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('
'), 'add.html 不应包含非法的嵌套 标签'); +assert(!addHtml.includes('
\n '), 'el-form-item 内不应存在未闭合或多余的 '); +assert(addHtml.includes(''), '必须存在 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('--- 全部测试通过!商品详情富文本编辑器修复逻辑完全正确! ---');