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(/
Date: Sat, 24 Oct 2026 09:49:49 +0800
Subject: [PATCH 4/7] =?UTF-8?q?feat(diy-card):=20=E8=87=AA=E5=AE=9A?=
=?UTF-8?q?=E4=B9=89=E6=93=8D=E4=BD=9C=E6=89=A9=E8=87=B3=208=20=E7=B1=BB(2?=
=?UTF-8?q?.19.3)=20=E2=80=94=20sms/map/link=20=E7=99=BD=E5=90=8D=E5=8D=95?=
=?UTF-8?q?=20+=20doAct=20=E5=88=86=E6=94=AF=20+=20map=20=E5=9C=B0?=
=?UTF-8?q?=E5=9D=80=E5=9B=9E=E8=90=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components-diy/diy-card-common.js | 5 +++--
components-diy/diy-card-self.vue | 34 +++++++++++++++++++++++++++++++
components-diy/diy-card-show.vue | 34 +++++++++++++++++++++++++++++++
tests/diy-card-style-v2.test.js | 14 +++++++++++++
4 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/components-diy/diy-card-common.js b/components-diy/diy-card-common.js
index 7bb89a2..e1d04b2 100644
--- a/components-diy/diy-card-common.js
+++ b/components-diy/diy-card-common.js
@@ -353,8 +353,9 @@ export function hexToRgba(hex, alpha) {
/** 默认动作类型序(图层面板行序与 C 端合并序一致) */
export const ACT_TYPES = ['tel', 'wx', 'mail', 'web', 'save'];
-/** 自定义动作类型白名单(doAct 天然消费: tel/wx/mail/copy 分支 + else 兜底) */
-export const ACT_ACTION_TYPES = ['tel', 'wx', 'mail', 'web', 'copy'];
+/** 自定义动作类型白名单(doAct 天然消费: tel/wx/mail/copy 分支 + else 兜底;
+ * 2.19.3 扩展: sms 短信/map 导航/link 页面链接(系统链接对话框)) */
+export const ACT_ACTION_TYPES = ['tel', 'wx', 'mail', 'web', 'copy', 'sms', 'map', 'link'];
export const ACT_LABEL_DEFAULTS = {
tel: '打电话',
diff --git a/components-diy/diy-card-self.vue b/components-diy/diy-card-self.vue
index 5fad331..e394880 100644
--- a/components-diy/diy-card-self.vue
+++ b/components-diy/diy-card-self.vue
@@ -217,6 +217,10 @@ export default {
this.goSaveContact();
return;
}
+ // map(导航): value 留空回落名片地址(2.19.3)
+ if (act.type === 'map' && !act.value && this.card && this.card.address) {
+ act = Object.assign({}, act, { value: this.card.address });
+ }
if (!act.value) return;
if (act.type === 'tel') {
uni.makePhoneCall({ phoneNumber: act.value, fail: () => {} });
@@ -235,6 +239,36 @@ export default {
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' }),
});
// #endif
+ } else if (act.type === 'sms') {
+ // #ifdef H5
+ window.location.href = 'sms:' + act.value;
+ // #endif
+ // #ifndef H5
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '号码已复制,请到短信粘贴', icon: 'none' }),
+ });
+ // #endif
+ } else if (act.type === 'map') {
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '地址已复制,可粘贴到地图导航', icon: 'none' }),
+ });
+ } else if (act.type === 'link') {
+ var isHttp = act.value.indexOf('http://') === 0 || act.value.indexOf('https://') === 0;
+ if (isHttp) {
+ // #ifdef H5
+ window.location.href = act.value;
+ // #endif
+ // #ifndef H5
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' }),
+ });
+ // #endif
+ } else {
+ this.$util.redirectTo(act.value, {});
+ }
} else {
uni.setClipboardData({
data: act.value,
diff --git a/components-diy/diy-card-show.vue b/components-diy/diy-card-show.vue
index e8765e7..ddb696b 100644
--- a/components-diy/diy-card-show.vue
+++ b/components-diy/diy-card-show.vue
@@ -129,6 +129,10 @@ export default {
this.goSaveContact();
return;
}
+ // map(导航): value 留空回落名片地址(2.19.3)
+ if (act.type === 'map' && !act.value && this.card && this.card.address) {
+ act = Object.assign({}, act, { value: this.card.address });
+ }
if (!act.value) return;
if (act.type === 'tel') {
uni.makePhoneCall({ phoneNumber: act.value, fail: () => {} });
@@ -147,6 +151,36 @@ export default {
success: () => uni.showToast({ title: '邮箱已复制', icon: 'none' }),
});
// #endif
+ } else if (act.type === 'sms') {
+ // #ifdef H5
+ window.location.href = 'sms:' + act.value;
+ // #endif
+ // #ifndef H5
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '号码已复制,请到短信粘贴', icon: 'none' }),
+ });
+ // #endif
+ } else if (act.type === 'map') {
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '地址已复制,可粘贴到地图导航', icon: 'none' }),
+ });
+ } else if (act.type === 'link') {
+ var isHttp = act.value.indexOf('http://') === 0 || act.value.indexOf('https://') === 0;
+ if (isHttp) {
+ // #ifdef H5
+ window.location.href = act.value;
+ // #endif
+ // #ifndef H5
+ uni.setClipboardData({
+ data: act.value,
+ success: () => uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' }),
+ });
+ // #endif
+ } else {
+ this.$util.redirectTo(act.value, {});
+ }
} else {
uni.setClipboardData({
data: act.value,
diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js
index e69fc25..0f70ba6 100644
--- a/tests/diy-card-style-v2.test.js
+++ b/tests/diy-card-style-v2.test.js
@@ -226,6 +226,11 @@ test('mini.vue + show/self: save(存通讯录)动作全链路(2.17.1)', () => {
}
assert.match(vue, /Array\.isArray\(c\.rows\)[\s\S]*?map\[r\.field\]/, file + ' 字段从 rows(白名单)提取');
assert.doesNotMatch(vue, /doAct\(act\)\s*\{\s*if \(!act \|\| !act\.value\) return;/, file + ' doAct 不再拦 value 为空的 save');
+ // 2.19.3 扩展类型: sms/map/link 分支 + map 空值回落名片地址
+ assert.match(vue, /act\.type === 'map' && !act\.value && this\.card && this\.card\.address/, file + ' map 空值回落名片地址');
+ assert.match(vue, /act\.type === 'sms'\)\s*\{[\s\S]*?window\.location\.href = 'sms:' \+ act\.value;[\s\S]*?号码已复制/, file + ' sms 分支(H5 sms: 链接/非 H5 复制)');
+ assert.match(vue, /act\.type === 'map'\)\s*\{[\s\S]*?地址已复制,可粘贴到地图导航/, file + ' map 分支(复制地址)');
+ assert.match(vue, /act\.type === 'link'\)\s*\{[\s\S]*?indexOf\('http:\/\/'\) === 0 [\s\S]*?\$util\.redirectTo\(act\.value, \{\}\)/, file + ' link 分支(外链 H5 跳转/小程序复制, 站内 redirectTo)');
}
});
@@ -461,6 +466,8 @@ test('common.js: normalizeActVis / normalizeActLabels / normalizeCustomActs / no
assert.equal(list[0].label, '加群', 'label 保留');
assert.equal(list[1].label, '自定义', 'label 缺省');
assert.equal(list[1].type, 'tel', '合法 type 保留');
+ // 2.19.3 扩展类型白名单
+ assert.deepEqual(customFn([{ type: 'sms', value: '139' }, { type: 'map', value: '某某大厦' }, { type: 'link', value: '/pages/index/index' }]).map(x => x.type), ['sms', 'map', 'link'], 'sms/map/link 新类型通过白名单');
// actOrder(2.19.1): 缺省=历史顺序; 去重+白名单+缺失尾补
assert.deepEqual(orderFn(undefined), ['tel', 'wx', 'mail', 'web', 'save'], 'actOrder 缺省=历史顺序');
assert.deepEqual(orderFn(['save', 'tel', 'bogus', 'save', 'mail']), ['save', 'tel', 'mail', 'wx', 'web'], '重排去重+非法剔除+缺失尾补');
@@ -541,6 +548,8 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/
// 自定义动作行: 类型 chips / 取值 / 排序 / 删除 / 添加
assert.match(html, /diy-card-layer--custom" v-for="\(c, ci\) in nc\.customActs"/, comp + ' 自定义动作图层行');
assert.match(html, /:class="\{ active: c\.type === t\.v \}"/, comp + ' 自定义动作类型 chips');
+ assert.match(html, /\{v:'copy',lb:'复制'\},\{v:'sms',lb:'短信'\},\{v:'map',lb:'导航'\},\{v:'link',lb:'页面'\}\]/, comp + ' 类型 chips 含 2.19.3 扩展(短信/导航/页面, web 文案改网址)');
+ assert.match(html, /js-act-syslink" data-scope="custom" :data-idx="ci" v-if="c\.type === 'link'"/, comp + ' link 类型出现「系统链接」选择按钮');
assert.match(html, /v-model="c\.value" maxlength="200"/, comp + ' 自定义动作取值输入');
assert.match(html, /js-act-lib" data-scope="custom" :data-idx="ci"/, comp + ' 素材图标按钮(自定义动作)');
assert.match(html, /nc\.customActs\.splice\(ci - 1, 0, nc\.customActs\.splice\(ci, 1\)\[0\]\)/, comp + ' 上移排序');
@@ -612,6 +621,11 @@ test('card-act-icon.js: 类名委托修 2.18 选图标 Bug + $set 写回 + 素
assert.match(src, /iconKey: '', svg: '', color: ''/, '缺行防御补齐含 svg 键');
assert.match(src, /window\.CARD_ICON_LIB \|\| \{\}/, '弹层渲染消费同源图标库');
assert.match(src, /cal-item" data-key=/, '弹层图标网格');
+ // 2.19.3 系统链接对话框委托(link 类型)
+ assert.match(src, /on\('click', '\.js-act-syslink'/, '系统链接走类名委托');
+ assert.match(src, /ns\.select_link\(\{\}, function \(data\)/, '调全局 ns.select_link 打开系统链接对话框');
+ assert.match(src, /data\.wap_url \|\| data\.url/, '回执取 wap_url 写入 value');
+ assert.match(src, /window\.vue\.\$set\(target\.item, 'value', url\)/, '系统链接结果 $set 写回 value');
// 反例: 面板不再内联调 openAlbum 选快捷操作图标(2.18 Bug 根因规避)
for (const comp of ['show', 'self']) {
const html = readFileSync(resolve(BE, 'component/view/diy-card-' + comp + '/design.html'), 'utf8');
From 549a98204ae0fd45fb9aee61b8cd7ccb452729e5 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Sat, 24 Oct 2026 09:56:58 +0800
Subject: [PATCH 5/7] =?UTF-8?q?test(diy-card):=20QR=20=E9=80=89=E5=9B=BE?=
=?UTF-8?q?=E5=A7=94=E6=89=98=E5=A5=91=E7=BA=A6(js-qr-media=20+=20?=
=?UTF-8?q?=E9=9B=B6=E5=86=85=E8=81=94=20openAlbum)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
tests/diy-card-style-v2.test.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js
index 0f70ba6..fa7ffa8 100644
--- a/tests/diy-card-style-v2.test.js
+++ b/tests/diy-card-style-v2.test.js
@@ -310,7 +310,8 @@ test('2.18: 操作区位置/独立样式/每按钮图标/自定义二维码 全
assert.match(html, /js-act-color" data-scope="default" :data-key="a\.k" data-field="bg"/, comp + ' 每按钮底色输入(同上)');
// 自定义二维码(媒体库)
assert.match(html, /v-model="nc\.qrImage"/, comp + ' 二维码自定义图输入');
- assert.match(html, /openAlbum\(function \(obj\)[\s\S]*?nc\.qrImage = obj\[0\]\.pic_path/, comp + ' 二维码选图走 openAlbum 媒体库');
+ assert.match(html, /js-qr-media" title="从媒体库选择二维码图片"/, comp + ' 二维码选图按钮走 js-qr-media 委托(2.19.3, 内联 openAlbum 不可靠)');
+ assert.doesNotMatch(html, /@click="openAlbum\(/, comp + ' 零内联 openAlbum 调用');
// 预览消费
assert.match(html, /nc\.actsPosition === 'above'/, comp + ' 预览上方档分支');
assert.match(html, /nc\.actsPosition !== 'above'/, comp + ' 预览下方档互斥守卫');
@@ -602,7 +603,7 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/
test('card-act-icon.js: 类名委托修 2.18 选图标 Bug + $set 写回 + 素材库唯一图标来源 + 次序调整(2.19+2.19.1)', () => {
const src = readFileSync(resolve(BE, 'component/view/public/js/card-act-icon.js'), 'utf8');
assert.match(src, /on\('click', '\.js-act-lib'/, '素材图标库走类名委托');
- assert.doesNotMatch(src, /js-act-media|openAlbum\(function/, '媒体库委托已整体移除(2.19.1 图标唯一来源=素材图标库)');
+ assert.doesNotMatch(src, /js-act-media/, '快捷操作媒体库委托已移除(2.19.1 图标唯一来源=素材图标库; QR 选图的 js-qr-media/openAlbum 为 2.19.3 合法新增)');
// 2.19.1 默认动作次序调整
assert.match(src, /on\('click', '\.js-act-move'/, '次序调整走类名委托');
assert.match(src, /var ACT_ORDER_DEF = \['tel', 'wx', 'mail', 'web', 'save'\];/, 'actOrder 白名单默认序');
@@ -626,6 +627,9 @@ test('card-act-icon.js: 类名委托修 2.18 选图标 Bug + $set 写回 + 素
assert.match(src, /ns\.select_link\(\{\}, function \(data\)/, '调全局 ns.select_link 打开系统链接对话框');
assert.match(src, /data\.wap_url \|\| data\.url/, '回执取 wap_url 写入 value');
assert.match(src, /window\.vue\.\$set\(target\.item, 'value', url\)/, '系统链接结果 $set 写回 value');
+ // 2.19.3 自定义二维码选图委托(模板内联 openAlbum 不可靠, 2.18 同坑收尾)
+ assert.match(src, /on\('click', '\.js-qr-media'/, '二维码选图走类名委托');
+ assert.match(src, /window\.vue\.\$set\(comp, 'qrImage', obj\[0\]\.pic_path\)/, '媒体库回执 $set 写回 qrImage');
// 反例: 面板不再内联调 openAlbum 选快捷操作图标(2.18 Bug 根因规避)
for (const comp of ['show', 'self']) {
const html = readFileSync(resolve(BE, 'component/view/diy-card-' + comp + '/design.html'), 'utf8');
From 7d1349f33636b0ccd1e031392bc77bd6933a3a3c Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Sat, 24 Oct 2026 10:15:10 +0800
Subject: [PATCH 6/7] =?UTF-8?q?test(diy-card):=20=E7=BA=BF=E7=A8=BF?=
=?UTF-8?q?=E5=9B=BE=E6=A0=87=20mask+currentColor=20=E5=A5=91=E7=BA=A6(2.1?=
=?UTF-8?q?9.6)=20=E2=80=94=205=20=E7=B1=BB=E7=A6=81=E5=9B=BA=E5=AE=9A?=
=?UTF-8?q?=E8=89=B2=20background-image?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
tests/diy-card-style-v2.test.js | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js
index fa7ffa8..435861a 100644
--- a/tests/diy-card-style-v2.test.js
+++ b/tests/diy-card-style-v2.test.js
@@ -637,3 +637,17 @@ test('card-act-icon.js: 类名委托修 2.18 选图标 Bug + $set 写回 + 素
}
});
+
+test('card-preview.css: 默认线稿图标 mask+currentColor —— 前景色实时跟随(2.19.6)', () => {
+ const css = readFileSync(resolve(BE, 'component/view/public/css/card-preview.css'), 'utf8');
+ // 5 个默认线稿类必须走 mask-image(颜色与图分离) + background-color: currentColor
+ for (const k of ['tel', 'wx', 'mail', 'web', 'save']) {
+ const m = css.match(new RegExp('\\.diy-card-mini__act-svg--' + k + '\\s*\\{[^}]*\\}'));
+ assert.ok(m, '线稿类存在: ' + k);
+ const block = m[0];
+ assert.match(block, /mask-image:\s*url\("data:image\/svg\+xml/, k + ' 走 mask-image(非固定色 background-image)');
+ assert.match(block, /-webkit-mask-image:/, k + ' 带 -webkit 前缀');
+ assert.match(block, /background-color:\s*currentColor/, k + ' 背景消费 currentColor(跟随内联 color)');
+ assert.doesNotMatch(block, /background-image:/, k + ' 不再用固定色 background-image(颜色编码在图内改色无效)');
+ }
+});
From f4998d1582e7703393de1d3a4bae7d229f49229d Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Sat, 24 Oct 2026 10:21:03 +0800
Subject: [PATCH 7/7] =?UTF-8?q?test(diy-card):=20=E9=9D=A2=E6=9D=BF?=
=?UTF-8?q?=E9=A2=9C=E8=89=B2=E5=8D=A0=E4=BD=8D=E4=B8=8E=E9=A2=84=E8=A7=88?=
=?UTF-8?q?=E5=90=8C=E9=93=BE=E5=A5=91=E7=BA=A6(2.19.7)?=
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, 9 insertions(+)
diff --git a/tests/diy-card-style-v2.test.js b/tests/diy-card-style-v2.test.js
index 435861a..087c364 100644
--- a/tests/diy-card-style-v2.test.js
+++ b/tests/diy-card-style-v2.test.js
@@ -584,6 +584,15 @@ test('design.html ×2: 图层式管理面板 + 素材图标库 + 预览显隐/
assert.match(html, /data-field="color"/, comp + ' 前景色 data-field');
assert.match(html, /data-field="bg"/, comp + ' 底色 data-field');
assert.match(html, /data-scope="custom" :data-idx="ci" data-field=/, comp + ' 自定义行颜色输入带作用域');
+ // 2.19.7 面板颜色输入占位必须与预览同链(空值=跟随配色档, 显示诚实):
+ // 默认行 color: wx#07C160/save#FF9500 固定 → theme#2F6BFF → a.fg; bg: theme#E6EDFF(rgba 叠白近似) → a.bg
+ const previewColorChain = html.match(/color: \(nc\.actIcons && nc\.actIcons\[a\.k\] && nc\.actIcons\[a\.k\]\.color\) \|\| \(a\.k === 'wx' \? '#07C160'[^"]*\)/);
+ assert.ok(previewColorChain, comp + ' 预览 color 回落链存在');
+ assert.match(html, /nc\.actIcons\[a\.k\]\.color \? nc\.actIcons\[a\.k\]\.color : \(a\.k === 'wx' \? '#07C160' : a\.k === 'save' \? '#FF9500' : \(nc\.actGridColorMode === 'theme' \? '#2F6BFF' : a\.fg\)\)"/, comp + ' 默认行前景输入占位与预览同链(wx/save 固定色 + theme/preset 档)');
+ assert.match(html, /nc\.actIcons\[a\.k\]\.bg \? nc\.actIcons\[a\.k\]\.bg : \(nc\.actGridColorMode === 'theme' \? '#E6EDFF' : a\.bg\)"/, comp + ' 默认行底色输入占位与预览同链(theme 叠白近似/preset 浅底)');
+ assert.doesNotMatch(html, /actIcons\[a\.k\]\.color \? nc\.actIcons\[a\.k\]\.color : '#2F6BFF'/, comp + ' 默认行前景占位不再恒 #2F6BFF(对多彩档是谎报)');
+ assert.match(html, /:value="c\.color \|\| '#2F6BFF'"/, comp + ' 自定义行前景占位(与自定义预览回落一致)');
+ assert.match(html, /:value="c\.bg \|\| '#E6EDFF'"/, comp + ' 自定义行底色占位(rgba 叠白近似)');
// 结构铁律: 自定义动作 cell 必须在默认动作 template v-for 之外(否则每默认动作重复渲染一次);
// 2.19.1 起默认 cell 为双层嵌套 template(外层 actOrder 次序), 收尾双
assert.equal((html.match(/<\/template><\/template>