From debfb473cc9be63905feee30c0f0a88e56805894 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Tue, 27 Oct 2026 16:12:33 +0800
Subject: [PATCH 1/3] =?UTF-8?q?test(card):=20=E5=B0=8F=E7=A8=8B=E5=BA=8F?=
=?UTF-8?q?=E7=A0=81=20scene=20=E8=A7=A3=E6=9E=90=E5=A5=91=E7=BA=A6?=
=?UTF-8?q?=E6=B5=8B=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
小程序码的 scene 里放的是名片**主键 id**(微信 scene 仅 32 可见字符,
装不下 30 字符的业务 card_id), 小程序端需先解析 scene 换 card_id。
覆盖:
- scene 解析: "id=448" / 裸 "448" / URL 编码形态 / 只取数字防脏值
- 常规入口(query 带 card_id)不受影响, 直连不换卡
- 换 card_id 走公开读接口 /card/api/diy/card 且带 id 参数
- 换卡失败给明确提示(名片不存在 / 网络异常), 不白屏
- 既无 card_id 也无 scene → 提示「缺少名片编号」
- 反例: 不得把 scene 整体当 card_id 用、不得用 card_id= 前缀解析、
不得硬编码名片 id
---
tests/card-mini-code-scene.test.js | 127 +++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
create mode 100644 tests/card-mini-code-scene.test.js
diff --git a/tests/card-mini-code-scene.test.js b/tests/card-mini-code-scene.test.js
new file mode 100644
index 0000000..3edf114
--- /dev/null
+++ b/tests/card-mini-code-scene.test.js
@@ -0,0 +1,127 @@
+/**
+ * 名片小程序码 · scene 解析契约测试(2.21)
+ *
+ * 背景:
+ * 微信 getwxacodeunlimit 的 scene 只有 32 可见字符, 装不下业务 card_id
+ * (形如 c_1_shop_20261013111612_9588EA, 30 字符; 加前缀更长),
+ * 故 scene 里放的是名片**主键 id**(数字): "id=448"。
+ *
+ * 扫码进入小程序后, 微信把 scene 放在 options.scene(整体 encodeURIComponent
+ * 过), 小程序端需:
+ * ① decodeURIComponent(options.scene)
+ * ② 解析出主键 id
+ * ③ 用 id 向后端换业务 card_id(后端 /card/api/diy/card 支持 id 寻址)
+ *
+ * 覆盖:
+ * 1. scene 解析: "id=448" / 裸 "448" / URL 编码形态
+ * 2. 常规入口(query 带 card_id)不受影响
+ * 3. 解析不出时走「缺少名片编号」提示(不得白屏)
+ * 4. 换 card_id 走公开读接口且带 id 参数
+ * 5. 反例: 不得把 scene 当 card_id 直接用(会查不到名片)
+ *
+ * 跑法: node --test tests/card-mini-code-scene.test.js
+ */
+
+'use strict';
+
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+
+const PAGE = path.resolve(__dirname, '../pages_tool/card/detail.vue');
+const src = fs.readFileSync(PAGE, 'utf8');
+// 先剥注释再断言 —— 注释里写「绝不能写 X」会被自判违规
+const strip = s => s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
+
+const body = strip(src);
+
+// ================================================================
+// 1. scene 解析
+// ================================================================
+test('scene 解析: 从 options.scene 取名片主键 id', () => {
+ assert.match(body, /pickSceneRelId\s*\(\s*options\s*\)/);
+ // 必须先 decodeURIComponent(微信对 scene 整体做过编码)
+ assert.match(body, /decodeURIComponent\(opt\.scene\)/);
+ // 解析 "id=xxx" 形态
+ assert.match(body, /kv\[0\]\s*===\s*'id'/);
+});
+
+test('scene 解析: 兼容裸 id(无前缀)', () => {
+ // 无 "=" 时整体当 id
+ assert.match(body, /let val = scene;/);
+ assert.match(body, /String\(val\)\.replace\(\/\[\^0-9\]\/g, ''\)/);
+});
+
+test('scene 解析: 只取数字(防脏值/防注入)', () => {
+ assert.match(body, /replace\(\/\[\^0-9\]\/g, ''\)/);
+ assert.match(body, /parseInt\(val, 10\)/);
+});
+
+test('scene 解析: 取不到时返回 0', () => {
+ assert.match(body, /return val \? parseInt\(val, 10\) : 0;/);
+});
+
+// ================================================================
+// 2. 常规入口不受影响
+// ================================================================
+test('常规入口: query 带 card_id 时直接使用', () => {
+ assert.match(body, /pickDirectCardId\s*\(\s*options\s*\)/);
+ assert.match(body, /if \(opt\.card_id\)/);
+ assert.match(body, /decodeURIComponent\(opt\.card_id\)/);
+});
+
+test('onLoad: 常规入口直连, scene 入口走换卡流程', () => {
+ assert.match(body, /const directId = this\.pickDirectCardId\(options\);/);
+ assert.match(body, /const sceneId = this\.pickSceneRelId\(options\);/);
+ assert.match(body, /if \(directId\)\s*\{[\s\S]*?this\.cardId = directId;/);
+ assert.match(body, /else if \(sceneId\)\s*\{\s*this\.resolveCardIdByRelId\(sceneId\);/);
+});
+
+// ================================================================
+// 3. 换 card_id
+// ================================================================
+test('换 card_id: 走公开读接口且带 id 参数', () => {
+ assert.match(body, /resolveCardIdByRelId\s*\(\s*relId\s*\)\s*\{/);
+ assert.match(body, /url:\s*'\/card\/api\/diy\/card'/);
+ assert.match(body, /data:\s*\{\s*id:\s*relId\s*\}/);
+});
+
+test('换 card_id: 从返回里取业务 card_id', () => {
+ assert.match(body, /res\.data && res\.data\.card/);
+ assert.match(body, /card\.card_id/);
+ assert.match(body, /this\.cardId = bizId;/);
+});
+
+test('换 card_id: 失败时给明确提示(不白屏)', () => {
+ assert.match(body, /errorMsg = '名片不存在或已被删除'/);
+ assert.match(body, /errorMsg = '网络异常,请稍后重试'/);
+});
+
+test('换 card_id: 成功后继续加载详情', () => {
+ assert.match(body, /this\.cardId = bizId;[\s\S]*?this\.loadDetail\(\);/);
+});
+
+// ================================================================
+// 4. 缺少名片编号的兜底
+// ================================================================
+test('既无 card_id 也无 scene: 提示「缺少名片编号」', () => {
+ assert.match(body, /this\.errorMsg = '缺少名片编号';/);
+});
+
+// ================================================================
+// 5. 反例(防恒真 / 防走偏)
+// ================================================================
+test('反例: 不得把 scene 整体当 card_id 直接用', () => {
+ // 若把 scene 当 card_id 传, 后端按业务 card_id 查不到 → 必然查不到名片
+ assert.doesNotMatch(body, /this\.cardId = decodeURIComponent\(opt\.scene\)/);
+ assert.doesNotMatch(body, /card_id:\s*decodeURIComponent\(opt\.scene\)/);
+});
+
+test('反例: 不得用 card_id= 前缀解析 scene(scene 用 id= 前缀)', () => {
+ assert.doesNotMatch(body, /kv\[0\]\s*===\s*'card_id'/);
+});
+
+test('反例: 不得硬编码名片 id', () => {
+ assert.doesNotMatch(body, /card_id:\s*['"]\d+['"]/);
+});
From 91f47c8e9cb8c70875e916995e338056f9d292cc Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Tue, 27 Oct 2026 18:22:17 +0800
Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=E8=BF=98=E5=8E=9F=E8=A2=AB?=
=?UTF-8?q?=E5=88=A0=E9=99=A4=E7=9A=84webview.vue?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages_tool/webview/webview.vue | 82 ++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 pages_tool/webview/webview.vue
diff --git a/pages_tool/webview/webview.vue b/pages_tool/webview/webview.vue
new file mode 100644
index 0000000..eb8948a
--- /dev/null
+++ b/pages_tool/webview/webview.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 66aa66d2c1371e2b72146dd7279beb96c5acc0ae Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Tue, 27 Oct 2026 18:30:14 +0800
Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0webview=E9=A1=B5?=
=?UTF-8?q?=E9=9D=A2=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages.json | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/pages.json b/pages.json
index 1a7752c..b48c748 100644
--- a/pages.json
+++ b/pages.json
@@ -1220,11 +1220,20 @@
"diy-text": "view",
"diy-video": "view",
"diy-video-list": "view"
- }
- }
- },
- {
- "path": "index/diy",
+ }
+ }
+ },
+ {
+ "path": "webview/webview",
+ "style": {
+ // #ifdef APP-PLUS
+ "navigationStyle": "custom",
+ // #endif
+ "navigationBarTitleText": ""
+ }
+ },
+ {
+ "path": "index/diy",
"style": {
// #ifdef APP-PLUS
"navigationStyle": "custom",