From 0acd9e0e7b18cfbef4433770d777601552517867 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 24 Oct 2026 09:00:58 +0800 Subject: [PATCH 1/7] =?UTF-8?q?test(diy-card):=20=E9=94=81=E6=AD=BB?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E6=93=8D=E4=BD=9C=E9=9D=A2=E6=9D=BF=E5=AD=98?= =?UTF-8?q?=E9=87=8F=E7=BB=84=E4=BB=B6=E9=98=B2=E7=82=B8=E5=A5=91=E7=BA=A6?= =?UTF-8?q?(v-if=20=E5=AE=88=E5=8D=AB/=E9=9B=B6=E8=A3=B8=E8=AF=BB/?= =?UTF-8?q?=E9=98=B2=E5=BE=A1=E5=BC=8F=E5=86=99=E5=9B=9E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/diy-card-style-v2.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js index 98c764e..112f18f 100644 --- a/tests/diy-card-style-v2.test.js +++ b/tests/diy-card-style-v2.test.js @@ -519,7 +519,14 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/ // 图层管理器: 眼睛开关 / 图标缩略 / 行内改名 / 素材库+媒体库按钮 / 清除 assert.match(html, /diy-card-layer__eye layui-icon" :class="nc\.actVis && nc\.actVis\[a\.k\] === 0 \? 'layui-icon-eye-invisible' : 'layui-icon-eye'/, comp + ' 默认动作眼睛开关'); assert.match(html, /nc\.actVis\[a\.k\] = nc\.actVis\[a\.k\] === 0 \? 1 : 0;/, comp + ' 眼睛切换写回 actVis'); - assert.match(html, /v-model="nc\.actLabels\[a\.k\]" maxlength="6"/, comp + ' 行内改名 v-model actLabels'); + // 存量组件防御(2026-10-24): defaults($diy_view_utils)缺新字段时 refreshComponent 补不了, + // v-model="nc.actLabels[a.k]" 裸读双层直取会在 render 阶段炸 "reading 'tel'" —— + // 管理器容器 v-if 守卫 + 改名输入 :value/@input 防御式写回 + @click 禁以 if 开头(keyword 编译警告) + assert.match(html, /v-if="nc\.showQuick && nc\.actLabels && nc\.actIcons"/, comp + ' 管理器容器 v-if 守卫(未补全不渲染)'); + assert.doesNotMatch(html, /v-model="nc\.[a-zA-Z]+\[/, comp + ' 零 v-model 双层裸读(render 阶段防炸)'); + assert.match(html, /:value="nc\.actLabels \? \(nc\.actLabels\[a\.k\] \|\| ''\) : ''" @input="\$set\(nc, 'actLabels', nc\.actLabels \|\| \{\}\); \$set\(nc\.actLabels, a\.k, \$event\.target\.value\)"/, comp + ' 行内改名防御式写回($set 兼容存量行)'); + assert.doesNotMatch(html, /@click="if \(/, comp + ' 内联事件不以 if 开头(消 JS keyword 编译警告)'); + assert.match(html, /v-model="nc\.actLabels\[a\.k\]" maxlength="6"|maxlength="6" title="快捷操作文字"/, comp + ' 改名输入在位'); assert.match(html, /js-act-lib" :data-key="a\.k" data-scope="default"/, comp + ' 素材图标按钮(默认动作)'); assert.doesNotMatch(html, /js-act-media/, comp + ' 媒体库按钮已移除(2.19.1)'); assert.match(html, /\$set\(nc\.actIcons\[a\.k\], 'iconKey', ''\)/, comp + ' 清除图标走 $set(iconKey 兼容存量行)'); From 6977b914d3d531164137259702731b6ea2da4d87 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 24 Oct 2026 09:18:40 +0800 Subject: [PATCH 2/7] =?UTF-8?q?test(diy-card):=20=E9=94=81=E6=AD=BB?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=BC=95=E7=94=A8=E7=89=88=E6=9C=AC=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=A5=91=E7=BA=A6(6=20=E6=9D=A1=20=3Fv=3D{$version}?= =?UTF-8?q?=20+=20=E9=9B=B6=E6=97=A0=E7=89=88=E6=9C=AC=E5=BC=95=E7=94=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/diy-card-style-v2.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js index 112f18f..93dab0a 100644 --- a/tests/diy-card-style-v2.test.js +++ b/tests/diy-card-style-v2.test.js @@ -568,9 +568,12 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/ assert.doesNotMatch(html, /<\/div>
/g) || []).length, comp + ' template 标签配平'); - // 资源引用 - assert.match(html, /js src="\{\$resource_path\}\/\.\.\/public\/js\/card-icon-lib\.js"/, comp + ' 引入素材图标库 JS'); - assert.match(html, /js src="\{\$resource_path\}\/\.\.\/public\/js\/card-act-icon\.js"/, comp + ' 引入图标选择委托 JS'); + // 资源引用(2026-10-24 起带 ?v={$version} 破生产浏览器/CDN 缓存 —— 同名 CSS/JS + // 旧缓存曾致生产编辑器面板图层行与预览宫格样式全丢、竖排裸文字) + for (const r of ['css/card-select\\.css', 'css/card-panel\\.css', 'css/card-preview\\.css', 'js/card-select\\.js', 'js/card-icon-lib\\.js', 'js/card-act-icon\\.js']) { + assert.match(html, new RegExp('src="\\{\\$resource_path\\}/\\.\\./public/' + r + '\\?v=\\{\\$version\\}"'), comp + ' 资源引用带版本参数: ' + r); + } + assert.doesNotMatch(html, /public\/(css|js)\/card-[a-z-]+\.(css|js)"/, comp + ' 零无版本资源引用'); } }); From 1b6114ef5885a696a1190b2d4ef0e1831593cde6 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 24 Oct 2026 09:42:32 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat(diy-card):=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=88=A0=E9=99=A4+=E6=81=A2=E5=A4=8D(2.19.2)?= =?UTF-8?q?=20=E2=80=94=20normalizeActDeleted=20+=20acts=20=E5=8F=8C?= =?UTF-8?q?=E9=87=8D=E8=BF=87=E6=BB=A4=20+=20=E9=80=8F=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components-diy/diy-card-common.js | 14 ++++++++++++++ components-diy/diy-card-mini.vue | 7 ++++--- components-diy/diy-card-self.vue | 1 + components-diy/diy-card-show.vue | 1 + tests/diy-card-style-v2.test.js | 31 ++++++++++++++++++++++--------- 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/components-diy/diy-card-common.js b/components-diy/diy-card-common.js index aff9d5d..7bb89a2 100644 --- a/components-diy/diy-card-common.js +++ b/components-diy/diy-card-common.js @@ -396,6 +396,20 @@ export function normalizeActVis(vis) { return out; } +/** + * 默认动作删除(2.19.2): actDeleted = { type: 0|1 }, 1=已删除(C端不渲染, 面板行隐藏, + * 经「恢复」chips 找回)。与 actVis(眼睛临时隐藏)独立 —— 删除与隐藏 C 端均不渲染。 + * 缺键/非法 = 未删除(0), 历史配置无此字段 → 零变化 + */ +export function normalizeActDeleted(deleted) { + const src = deleted && typeof deleted === 'object' ? deleted : {}; + const out = {}; + ACT_TYPES.forEach(t => { + out[t] = String(src[t]) === '1' ? 1 : 0; + }); + return out; +} + /** * 默认动作文案(2.19): actLabels = { type: string }, 空/缺 = 内置默认 * 历史配置无此字段 → 全默认, 渲染零变化 diff --git a/components-diy/diy-card-mini.vue b/components-diy/diy-card-mini.vue index e0cb872..d714f49 100644 --- a/components-diy/diy-card-mini.vue +++ b/components-diy/diy-card-mini.vue @@ -192,7 +192,7 @@ import { WAP_LINE_ICONS, svgLineIconBg, ACT_GRID_PRESETS, ACT_GRID_PRESET_FALLBACK, hexToRgba, // 2.19: 显隐/文案/自定义动作归一化 + 素材图标库(与后端 CardIconLibrary 同源) - normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg, + normalizeActVis, normalizeActDeleted, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg, } from './diy-card-common.js'; export default { @@ -388,6 +388,7 @@ export default { acts() { const raw = this.card && Array.isArray(this.card.acts) ? this.card.acts : []; const vis = normalizeActVis(this.c.actVis); + const del = normalizeActDeleted(this.c.actDeleted); const labels = normalizeActLabels(this.c.actLabels); const icons = this.c.actIcons && typeof this.c.actIcons === 'object' ? this.c.actIcons : {}; const order = normalizeActOrder(this.c.actOrder); @@ -409,12 +410,12 @@ export default { }); const out = []; order.forEach(t => { - if (byType[t] && vis[t] !== 0) out.push(merge(byType[t])); + if (byType[t] && vis[t] !== 0 && del[t] !== 1) out.push(merge(byType[t])); }); raw.forEach(a => { if (!a || !a.type) return; const t = String(a.type); - if (order.indexOf(t) >= 0 || vis[t] === 0) return; + if (order.indexOf(t) >= 0 || vis[t] === 0 || del[t] === 1) return; out.push(merge(a)); }); normalizeCustomActs(this.c.customActs).forEach(ca => { diff --git a/components-diy/diy-card-self.vue b/components-diy/diy-card-self.vue index f09e17e..5fad331 100644 --- a/components-diy/diy-card-self.vue +++ b/components-diy/diy-card-self.vue @@ -123,6 +123,7 @@ export default { qrImage: v.qrImage, // 2.19: 默认动作显隐/文案 + 自定义动作(基件 acts computed 合成) actVis: v.actVis, + actDeleted: v.actDeleted, actLabels: v.actLabels, actOrder: v.actOrder, customActs: v.customActs, diff --git a/components-diy/diy-card-show.vue b/components-diy/diy-card-show.vue index 7ca70b7..e8765e7 100644 --- a/components-diy/diy-card-show.vue +++ b/components-diy/diy-card-show.vue @@ -86,6 +86,7 @@ export default { qrImage: v.qrImage, // 2.19: 默认动作显隐/文案 + 自定义动作(基件 acts computed 合成) actVis: v.actVis, + actDeleted: v.actDeleted, actLabels: v.actLabels, actOrder: v.actOrder, customActs: v.customActs, diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js index 93dab0a..e69fc25 100644 --- a/tests/diy-card-style-v2.test.js +++ b/tests/diy-card-style-v2.test.js @@ -363,7 +363,7 @@ test('反例自测: 新形态标记不得出现在未改造组件中', () => { * 2.19 快捷操作: 显示控制 + 自定义操作 + 素材图标库 + 图层式管理 * ------------------------------------------------------------------ */ -const V19_FIELDS = { actVis: 'object', actLabels: 'object', customActs: 'array', actOrder: 'array' }; +const V19_FIELDS = { actVis: 'object', actDeleted: 'object', actLabels: 'object', customActs: 'array', actOrder: 'array' }; const V19_ACT_LABELS = { tel: '打电话', wx: '微信号', mail: '发邮件', web: '网站', save: '存通讯录' }; test('diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认文案/空数组/历史顺序=零变化)', () => { @@ -379,6 +379,8 @@ test('diy_view.php: Show/Self value 均含 2.19 字段(默认值=全显/默认 assert.deepEqual(v.customActs, [], comp + '.customActs 默认空数组'); // actOrder(2.19.1): 历史渲染顺序 assert.deepEqual(v.actOrder, ['tel', 'wx', 'mail', 'web', 'save'], comp + '.actOrder 默认=历史顺序'); + // actDeleted(2.19.2): 全 0=无删除(历史零变化) + assert.deepEqual(v.actDeleted, { tel: 0, wx: 0, mail: 0, web: 0, save: 0 }, comp + '.actDeleted 默认全 0'); } }); @@ -432,13 +434,16 @@ test('common.js: normalizeActVis / normalizeActLabels / normalizeCustomActs / no js.match(/export const ACT_ORDER_DEFAULT = [^;]+;/)[0].replace('export const', 'const'), ].join('\n'); const grab = (name) => js.match(new RegExp('export function ' + name + '[\\s\\S]*?\\n}'))[0].replace('export function ', 'function '); - const body = consts + '\n' + ['normalizeActVis', 'normalizeActLabels', 'normalizeCustomActs', 'normalizeActOrder'].map(grab).join('\n') + '\n' - + 'return [normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder];'; - const [visFn, labelFn, customFn, orderFn] = new Function(body)(); + const body = consts + '\n' + ['normalizeActVis', 'normalizeActDeleted', 'normalizeActLabels', 'normalizeCustomActs', 'normalizeActOrder'].map(grab).join('\n') + '\n' + + 'return [normalizeActVis, normalizeActDeleted, normalizeActLabels, normalizeCustomActs, normalizeActOrder];'; + const [visFn, delFn, labelFn, customFn, orderFn] = new Function(body)(); // actVis: 缺省全显(历史零变化); '0' → 0; 未知键忽略 assert.deepEqual(visFn(undefined), { tel: 1, wx: 1, mail: 1, web: 1, save: 1 }, 'actVis 缺省全显'); assert.deepEqual(visFn({ tel: '0', wx: 0 }), { tel: 0, wx: 0, mail: 1, web: 1, save: 1 }, 'actVis 0=隐藏/缺键=显'); + // actDeleted(2.19.2): 缺省全 0(历史零变化); '1' → 1 + assert.deepEqual(delFn(undefined), { tel: 0, wx: 0, mail: 0, web: 0, save: 0 }, 'actDeleted 缺省全 0'); + assert.deepEqual(delFn({ tel: '1', save: 1 }), { tel: 1, wx: 0, mail: 0, web: 0, save: 1 }, 'actDeleted 1=已删/缺键=0'); // actLabels: 缺省=内置文案; 空串回落默认 assert.deepEqual(labelFn(undefined), V19_ACT_LABELS, 'actLabels 缺省=内置默认'); assert.deepEqual(labelFn({ tel: '', wx: '加微信' }).wx, '加微信', 'actLabels 改名生效'); @@ -492,8 +497,11 @@ test('mini.vue: acts 合成(显隐过滤 + 次序 + 文案覆盖 + 自定义动 assert.match(src, /acts\(\)\s*\{[\s\S]*?normalizeCustomActs\(this\.c\.customActs\)/, 'acts 消费 normalizeCustomActs'); // 2.19.1 次序: 按 normalizeActOrder 输出默认动作 + order 外扩展类型尾接兜底 assert.match(src, /acts\(\)\s*\{[\s\S]*?normalizeActOrder\(this\.c\.actOrder\)/, 'acts 消费 normalizeActOrder'); - assert.match(src, /acts\(\)\s*\{[\s\S]*?const byType = \{\};[\s\S]*?order\.forEach\(t => \{[\s\S]*?if \(byType\[t\] && vis\[t\] !== 0\) out\.push\(merge\(byType\[t\]\)\);/, '按 order 次序取默认动作(vis=0 过滤)'); - assert.match(src, /acts\(\)\s*\{[\s\S]*?if \(order\.indexOf\(t\) >= 0 \|\| vis\[t\] === 0\) return;/, 'order 外扩展类型尾接且隐藏不渲染'); + assert.match(src, /acts\(\)\s*\{[\s\S]*?const byType = \{\};[\s\S]*?order\.forEach\(t => \{/, 'acts 按 order 次序取默认动作'); + assert.match(src, /acts\(\)\s*\{[\s\S]*?if \(order\.indexOf\(t\) >= 0 \|\| vis\[t\] === 0 \|\| del\[t\] === 1\) return;/, 'order 外扩展类型尾接且隐藏/已删不渲染'); + // 2.19.2 删除: 按 order 取默认动作时 deleted=1 的不渲染 + assert.match(src, /acts\(\)\s*\{[\s\S]*?normalizeActDeleted\(this\.c\.actDeleted\)/, 'acts 消费 normalizeActDeleted'); + assert.match(src, /acts\(\)\s*\{[\s\S]*?if \(byType\[t\] && vis\[t\] !== 0 && del\[t\] !== 1\) out\.push\(merge\(byType\[t\]\)\);/, 'order 取默认动作时 vis=0/deleted=1 均过滤'); assert.match(src, /labels\[t\] \|\| a\.label/, 'actLabels 覆盖文案(空回落服务端)'); assert.match(src, /cfgImg: String\(cfg\.img \|\| ''\)\.trim\(\)/, '默认动作合并 cfgImg'); assert.match(src, /cfgKey: String\(cfg\.iconKey \|\| ''\)\.trim\(\)/, '默认动作合并 cfgKey(iconKey)'); @@ -503,11 +511,11 @@ test('mini.vue: acts 合成(显隐过滤 + 次序 + 文案覆盖 + 自定义动 assert.match(src, /actLibStyle\(act\)\s*\{[\s\S]*?libIconBg\(this\.actLibOf\(act\), this\.actIconColor\(act\)\)/, 'actLibStyle 消费 libIconBg + 四级色口径'); assert.equal((src.match(/v-else-if="actLibOf\(act\)" class="act-grid-svg" :style="actLibStyle\(act\)"/g) || []).length, 2, '上/下两份宫格副本均插入素材库分支'); assert.match(src, /actSvgOf\(act\)\s*\{[\s\S]*?!this\.actImgOf\(act\) && !this\.actLibOf\(act\) && !!WAP_LINE_ICONS/, '线稿档被 img/素材库优先占位后不再进入'); - assert.match(src, /import \{[\s\S]*?normalizeActVis, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg/, 'mini.vue 引入 2.19+2.19.1 依赖'); + assert.match(src, /import \{[\s\S]*?normalizeActVis, normalizeActDeleted, normalizeActLabels, normalizeCustomActs, normalizeActOrder, libIconBg/, 'mini.vue 引入 2.19+2.19.1+2.19.2 依赖'); // 透传 for (const file of ['diy-card-show.vue', 'diy-card-self.vue']) { const vue = readFileSync(resolve(FE, file), 'utf8'); - for (const f of ['actVis', 'actLabels', 'actOrder', 'customActs']) { + for (const f of ['actVis', 'actDeleted', 'actLabels', 'actOrder', 'customActs']) { assert.match(vue, new RegExp(f + ': v\\.' + f + ','), file + ' 透传 ' + f); } } @@ -540,13 +548,18 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/ assert.match(html, /nc\.customActs\.splice\(ci, 1\)/, comp + ' 删除自定义动作'); // 2.19.1 默认动作次序: 面板行按 actOrder 渲染 + ↑↓ 委托调整 assert.equal((html.match(/