fix(h5): show raw skin age, support Harmony touch pickers and add WeChat logout
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# 原始肌龄显示与鸿蒙滚轮兼容
|
||||
|
||||
## 范围
|
||||
|
||||
- 生物可视年龄直接展示算法原始肌龄,取消实际年龄 ±3 的展示修正。原始肌龄缺失显示 `--`,不以实际年龄或旧展示值补齐,不额外四舍五入。
|
||||
- 新报告 `skinAgeDisplay` 与 `skinAgeRaw` 相同,保留兼容字段 `ageClampNote` 但值为空。推荐规则、评分和其他话术不变。
|
||||
- 历史报告回看优先读取快照 `vm.skinAgeRaw`;更老的快照缺该字段时,从保存的 `payload.result.skin_age` 读取。忽略旧的钳制展示值,不改写存量快照,不重算历史推荐/文案。后台列表、详情、CSV 的展示肌龄也改为读取原始肌龄,避免前后台不一致。
|
||||
- `mobile-select@1.4.0` 原来通过 UA 字符串选择 touch/mouse,无法识别不带 Android 的 OpenHarmony/ArkWeb。保留上游版本与 MIT 声明,添加本地补丁:通过 `navigator.maxTouchPoints > 0` 或 `ontouchstart` 能力选择触摸事件,无触摸能力则使用鼠标事件;不再按系统名称判断。
|
||||
- 同时按事件的 `touches/changedTouches` 读取坐标,不依赖全局 `TouchEvent` 构造函数,避免无该构造函数的鼠标环境报错。弹层挂载、页面禁滑和滚轮联动逻辑不变。
|
||||
|
||||
## 回归
|
||||
|
||||
```bash
|
||||
node h5/test/raw-skin-age.js
|
||||
node h5/js/skin-score.js
|
||||
node h5/test/recommendation.js
|
||||
node h5/test/regression.js
|
||||
node h5/test/report-persistence.mjs
|
||||
node h5/test/harmony-picker.mjs
|
||||
node h5/test/profile-interaction.mjs
|
||||
node h5/test/guest-privacy.mjs
|
||||
node h5/test/report-touch-layout.mjs
|
||||
```
|
||||
|
||||
鸿蒙专项使用 Chromium 的真实触摸/鼠标分发配合不同 UA 与能力配置,逐项验证年、月、省、市四轮、地区联动、确认回填、重开定位和表单提交。覆盖 Android 触摸、OpenHarmony 5、OpenHarmony 6+微信、桌面 UA 的触摸设备、无 TouchEvent 构造函数的桌面鼠标、Android UA 的无触摸环境。
|
||||
|
||||
这属于 UA/能力兼容回归,不等同于鸿蒙真机或 ArkWeb 内核验证。测试使用隔离临时目录或拦截账号请求,不写线上报告,不调用付费检测。历史报告测试故意保留旧展示值 57 岁,确认 H5/后台改显示原始肌龄且原文件字节不变。
|
||||
|
||||
用户已确认发版。本次与《微信退出登录-2026-10-13.md》的修改一起提交,采用 Git bundle 快进同步到 `/home/ubuntu/juzi`,重启 `juzi-h5.service` 后校验线上资源、健康与浏览器交互。不代用户推送 origin,不改写既有报告或调用线上付费检测。摄像头切换不在本次修改范围内。
|
||||
@@ -0,0 +1,11 @@
|
||||
# 微信退出登录
|
||||
|
||||
- 首页与「我的报告」的状态区,仅微信已登录时显示「退出登录」。游客模式不显示,不恢复旧的「切换登录方式」按钮。
|
||||
- 点击使用系统确认框提示:确认退出微信登录?历史报告将保留,重新微信登录后可查看。取消不调用退出接口。
|
||||
- 确认后调用现有 `api/auth/logout`,撤销当前服务端会话和 Cookie;成功才清除前端登录状态并回到入口页。失败提示错误,保留当前页面/身份并允许重试;请求期间禁用退出按钮,防止重复提交。
|
||||
- 不删除账号、报告快照或微信偏好缓存。退出后重新登录同一微信,仍可查看原有历史报告。
|
||||
- 退出时清空历史列表并作废在途历史请求,限制旧报告的返回导航;旧微信请求迟到也不能在游客身份下打开报告。
|
||||
|
||||
验证:`node h5/test/wechat-browser.mjs`(模拟微信、真实 HTTPS Cookie)覆盖取消、接口失败、确认退出、服务端会话撤销、返回键、迟到报告、游客不显示退出及重新微信登录后快照文件字节不变。`node h5/test/guest-privacy.mjs`、`node h5/test/report-persistence.mjs` 回归游客与历史报告。
|
||||
|
||||
用户已确认与原始肌龄显示、鸿蒙滚轮兼容一起发版。发布沿用 Git bundle 快进同步和服务重启,不代用户推送 origin,不删除历史报告。微信退出流程使用隔离模拟账号验证;线上不操作真实微信账号、报告或付费接口。
|
||||
@@ -5,27 +5,27 @@
|
||||
"3": "skin-score.js",
|
||||
"4": "customer-feedback-second.js",
|
||||
"5": "disposeUnusedWeightTensors",
|
||||
"6": "n",
|
||||
"6": "f",
|
||||
"7": "test/profile-store.js",
|
||||
"8": "admin.js",
|
||||
"9": "__awaiter",
|
||||
"10": "wechat-browser.mjs",
|
||||
"10": "customer-feedback-third.mjs",
|
||||
"11": "lib/accounts.js",
|
||||
"12": "server.js",
|
||||
"13": "files",
|
||||
"14": "NeuralNetwork",
|
||||
"14": "showScreen",
|
||||
"15": "customer-feedback-fourth.mjs",
|
||||
"16": "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范",
|
||||
"17": "report-persistence.mjs",
|
||||
"18": "1'me肌肤状态检测 H5",
|
||||
"19": "customer-feedback-fourth.js",
|
||||
"20": "build_fixtures.py",
|
||||
"21": "getEnv",
|
||||
"22": "k",
|
||||
"21": "extractFaces",
|
||||
"22": "i",
|
||||
"23": "facepp-skinanalyze(旷视皮肤分析专业版调用技能)",
|
||||
"24": "巨子测肤 · 1'me 语音播报内容设计文档 V1.0",
|
||||
"25": "mobile-select.iife.min.js",
|
||||
"26": ".extractWeights",
|
||||
"26": "NeuralNetwork",
|
||||
"27": "gen_voice.py",
|
||||
"28": "C",
|
||||
"29": "face-api.js 本地化依赖(h5/vendor/faceapi/)",
|
||||
@@ -40,13 +40,13 @@
|
||||
"38": "FaceLandmark68Net",
|
||||
"39": "UI交互界面调整",
|
||||
"40": "第四轮反馈修改与验收",
|
||||
"41": "vizCompose",
|
||||
"41": "FaceLandmarks68",
|
||||
"42": "layer_at",
|
||||
"43": "extractAllFacesAndComputeResults",
|
||||
"44": "start-local.sh",
|
||||
"45": "PredictAllAgeAndGenderWithFaceAlignmentTask",
|
||||
"46": "PredictAllFaceExpressionsWithFaceAlignmentTask",
|
||||
"47": "Box",
|
||||
"47": "extractFaceTensors",
|
||||
"48": "已修改",
|
||||
"49": "mobile-select/README.md",
|
||||
"50": "call_skinanalyze_3view.sh",
|
||||
@@ -76,8 +76,8 @@
|
||||
"74": "report-copy.js",
|
||||
"75": "prepare_h5_assets.py",
|
||||
"76": "报告页滑动防冲突-2026-10-13.md",
|
||||
"77": "drawFaceExpressions",
|
||||
"78": "FaceLandmarks68",
|
||||
"77": "FaceLandmarks",
|
||||
"78": ".toString",
|
||||
"79": "登录反馈与加载优化(2026-10-12)",
|
||||
"80": "线上微信登录部署记录",
|
||||
"81": "wechat-production.mjs",
|
||||
@@ -92,16 +92,16 @@
|
||||
"90": "customer-feedback.mjs",
|
||||
"91": "wi",
|
||||
"92": "客户反馈修改(2026-10-12)",
|
||||
"93": ".runAndExtendWithFaceDetection",
|
||||
"93": ".runAndExtendWithFaceDetections",
|
||||
"94": "第三轮反馈:PSD 对齐、验收及上线记录",
|
||||
"95": "pn",
|
||||
"96": "play",
|
||||
"96": "onDetect",
|
||||
"97": "account-client.js",
|
||||
"98": "js/profile-store.js",
|
||||
"99": "capture-diagnostics.js",
|
||||
"100": ".computeFaceDescriptor",
|
||||
"100": ".draw",
|
||||
"101": "wechat-fixture.cjs",
|
||||
"102": "extractFaces",
|
||||
"102": "DetectSingleFaceLandmarksTask",
|
||||
"103": "第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md",
|
||||
"104": "export_fourth_feedback_assets.py",
|
||||
"105": "capture-quality-browser.mjs",
|
||||
@@ -110,19 +110,22 @@
|
||||
"108": "profile-interaction.mjs",
|
||||
"109": "submitPhotos",
|
||||
"110": "doCapture",
|
||||
"111": "i",
|
||||
"112": ".toString",
|
||||
"111": "n",
|
||||
"112": "微信退出登录-2026-10-13.md",
|
||||
"113": "swipe-track.js",
|
||||
"114": "optimize_delivery_images.py",
|
||||
"115": "ao",
|
||||
"116": "TinyYolov2Base",
|
||||
"115": "normalize",
|
||||
"117": "startCamera",
|
||||
"118": "PredictSingleAgeAndGenderWithFaceAlignmentTask",
|
||||
"119": "PredictSingleFaceExpressionsWithFaceAlignmentTask",
|
||||
"120": "LabeledFaceDescriptors",
|
||||
"121": ".runAndExtendWithFaceDetections",
|
||||
"122": "readPhotoFile",
|
||||
"122": "onPhotoCaptured",
|
||||
"123": "评分圈内居中、基础信息页固定与正脸语音修复",
|
||||
"124": "TinyYolov2",
|
||||
"125": "报告等待页实拍背景-2026-10-12.md"
|
||||
"125": "报告等待页实拍背景-2026-10-12.md",
|
||||
"126": ".forwardInput",
|
||||
"128": "AgeGenderNet",
|
||||
"130": ".predictExpressions",
|
||||
"131": "原始肌龄显示与鸿蒙滚轮兼容",
|
||||
"132": "harmony-picker.mjs",
|
||||
"134": "height"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"0": "6518846594ff224e", "1": "cb6ea50bd4e4a5ca", "2": "43be28fd2ee8685d", "3": "1f737ca8edb978f2", "4": "56bf12952b0de42b", "5": "5d94cf57d5d1a6fb", "6": "09c43aeeb7124cf2", "7": "03b56b13a08b5f83", "8": "a689fcb62e7754af", "9": "4d10d0cc23541396", "10": "2b037735e1f5b0f6", "11": "ed0d13cfa8571d24", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "5fb489abcf0ab827", "15": "fbc8a72e6fda6359", "16": "95add6a975c0cb72", "17": "e042d7fe7351bcb9", "18": "f93d5d85e671fcee", "19": "2becf835f050dc29", "20": "aecf2fce44ba49c5", "21": "edc5fd195ace167e", "22": "4103800758a55924", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "92ef1d4e1a67b77f", "27": "eab427ce9a5219e9", "28": "de5647b9c3703ba7", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "a005abe41943c7cd", "42": "916170b9f5d7346b", "43": "3c55135955ae8607", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "88346d1cde75f0df", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "812950f51be20e40", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "74f01de01fa1f70c", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "e594ecaa8e7d1538", "65": "bdee3c4a37df52f8", "66": "9727b30d720298e3", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "589a2d5a76f3880a", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "3c4c278d6026bc3a", "77": "6cd5c5b10bc3e59b", "78": "68c52c80f38f8b2c", "79": "c6cfdfe181232ecc", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "0a373ad2197fd1a6", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "987a3393280b3d91", "94": "713d04996c4627df", "95": "57b2d61a5df6f056", "96": "0adfe3c7d77dcbe1", "97": "55f3c43a702bef8a", "98": "9b1cf1985223f90a", "99": "9380a89a868c76d2", "100": "444ef0744645f5ac", "101": "beb276de6301634c", "102": "4a34431a7e4bc59f", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "3877490226a78145", "108": "e0fc0a42e33bda91", "109": "4afb1715f62865b1", "110": "45259f492e4f2efb", "111": "dc9609eb7ae61b97", "112": "064a2b8ba0231c0a", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "115": "a3859fc25c864a81", "116": "a99a3157460afca4", "117": "02c78dd73c398bfa", "118": "01f68dfc34a29d32", "119": "8c471c7baeaaca54", "120": "f8b38aeaaab11ec4", "121": "46204fb2e4d50c53", "122": "4e91cfdf55362604", "123": "b091e3eb82b456eb", "124": "c96276581a8c98d4", "125": "e4f29216745ec758"}
|
||||
{"0": "b513c3d9e2ce0ee9", "1": "cb6ea50bd4e4a5ca", "2": "8bae62aa27da5427", "3": "125a85b2d8e78e52", "4": "56bf12952b0de42b", "5": "3fef2b47fc7c0327", "6": "f5fa3e3d7d829c42", "7": "03b56b13a08b5f83", "8": "a689fcb62e7754af", "9": "58f4243880289b8c", "10": "325690c89d34f602", "11": "8fe2f9d63516be5c", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "767b88826fe02948", "15": "fbc8a72e6fda6359", "16": "95add6a975c0cb72", "17": "e042d7fe7351bcb9", "18": "f93d5d85e671fcee", "19": "2becf835f050dc29", "20": "aecf2fce44ba49c5", "21": "11ae63a0b8df3c18", "22": "57d1520d2b3138d5", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "d2f8327bf38ee13c", "27": "eab427ce9a5219e9", "28": "d5bc4d13da321cc8", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "0a737a97b7cae9fc", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "68c52c80f38f8b2c", "42": "916170b9f5d7346b", "43": "0f4ade1799be2fc4", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "24c736a2281aad6e", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "812950f51be20e40", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "1b6525c6404105b9", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "e594ecaa8e7d1538", "65": "bdee3c4a37df52f8", "66": "9727b30d720298e3", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "589a2d5a76f3880a", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "3c4c278d6026bc3a", "77": "f5b5a4d7c34325bf", "78": "c3f9847676ae2caf", "79": "c6cfdfe181232ecc", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "0a373ad2197fd1a6", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "a38797466ce43978", "94": "713d04996c4627df", "95": "96eb6de87b194490", "96": "69eb267b9ec22897", "97": "505ea719b994ac5e", "98": "9b1cf1985223f90a", "99": "9380a89a868c76d2", "100": "bf75bfc99d4f9e34", "101": "beb276de6301634c", "102": "23f1d406f6c779ae", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "3877490226a78145", "108": "e0fc0a42e33bda91", "109": "4afb1715f62865b1", "110": "47af540ec3714a5e", "111": "16df50aadff513fb", "112": "af995dd35403b4dd", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "115": "5a279d83dc286aa4", "117": "4cd7191f102eea22", "118": "01f68dfc34a29d32", "119": "8c471c7baeaaca54", "122": "f4bc824c2d55ef51", "123": "b091e3eb82b456eb", "124": "c96276581a8c98d4", "125": "e4f29216745ec758", "126": "880a74c6d255e5cc", "128": "4b44a21bafc4714c", "130": "3a86f59f8a877384", "131": "482dbb7ec33d8c23", "132": "3daacabc5062b554", "134": "aef6e0c179bd709d"}
|
||||
+122
-119
@@ -1,16 +1,16 @@
|
||||
# Graph Report - juzi (2026-10-13)
|
||||
|
||||
## Corpus Check
|
||||
- 110 files · ~4,907,127 words
|
||||
- 114 files · ~4,908,611 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
- 1475 nodes · 2920 edges · 126 communities (102 shown, 24 thin omitted)
|
||||
- 1491 nodes · 2938 edges · 129 communities (102 shown, 27 thin omitted)
|
||||
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 209 edges (avg confidence: 0.57)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Graph Freshness
|
||||
- Built from commit: `1fdbb575`
|
||||
- Built from commit: `4e42e339`
|
||||
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
|
||||
- Run `graphify update .` after code changes (no API cost).
|
||||
|
||||
@@ -21,27 +21,27 @@
|
||||
- skin-score.js
|
||||
- customer-feedback-second.js
|
||||
- disposeUnusedWeightTensors
|
||||
- n
|
||||
- f
|
||||
- test/profile-store.js
|
||||
- admin.js
|
||||
- __awaiter
|
||||
- wechat-browser.mjs
|
||||
- customer-feedback-third.mjs
|
||||
- lib/accounts.js
|
||||
- server.js
|
||||
- files
|
||||
- NeuralNetwork
|
||||
- showScreen
|
||||
- customer-feedback-fourth.mjs
|
||||
- 巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范
|
||||
- report-persistence.mjs
|
||||
- 1'me肌肤状态检测 H5
|
||||
- customer-feedback-fourth.js
|
||||
- build_fixtures.py
|
||||
- getEnv
|
||||
- k
|
||||
- extractFaces
|
||||
- i
|
||||
- facepp-skinanalyze(旷视皮肤分析专业版调用技能)
|
||||
- 巨子测肤 · 1'me 语音播报内容设计文档 V1.0
|
||||
- mobile-select.iife.min.js
|
||||
- .extractWeights
|
||||
- NeuralNetwork
|
||||
- gen_voice.py
|
||||
- C
|
||||
- face-api.js 本地化依赖(h5/vendor/faceapi/)
|
||||
@@ -54,13 +54,13 @@
|
||||
- FaceLandmark68Net
|
||||
- UI交互界面调整
|
||||
- 第四轮反馈修改与验收
|
||||
- vizCompose
|
||||
- FaceLandmarks68
|
||||
- layer_at
|
||||
- extractAllFacesAndComputeResults
|
||||
- start-local.sh
|
||||
- PredictAllAgeAndGenderWithFaceAlignmentTask
|
||||
- PredictAllFaceExpressionsWithFaceAlignmentTask
|
||||
- Box
|
||||
- extractFaceTensors
|
||||
- 已修改
|
||||
- mobile-select/README.md
|
||||
- call_skinanalyze_3view.sh
|
||||
@@ -84,8 +84,8 @@
|
||||
- 报告顶部评分与年龄排版
|
||||
- build_report_copy.py
|
||||
- 报告页滑动防冲突-2026-10-13.md
|
||||
- drawFaceExpressions
|
||||
- FaceLandmarks68
|
||||
- FaceLandmarks
|
||||
- .toString
|
||||
- 登录反馈与加载优化(2026-10-12)
|
||||
- 线上微信登录部署记录
|
||||
- wechat-production.mjs
|
||||
@@ -99,35 +99,38 @@
|
||||
- 傅艳NEW与项思炼的会话 2026年10月12日.md
|
||||
- wi
|
||||
- 客户反馈修改(2026-10-12)
|
||||
- .runAndExtendWithFaceDetection
|
||||
- .runAndExtendWithFaceDetections
|
||||
- 第三轮反馈:PSD 对齐、验收及上线记录
|
||||
- pn
|
||||
- play
|
||||
- onDetect
|
||||
- account-client.js
|
||||
- js/profile-store.js
|
||||
- capture-diagnostics.js
|
||||
- .computeFaceDescriptor
|
||||
- .draw
|
||||
- wechat-fixture.cjs
|
||||
- extractFaces
|
||||
- DetectSingleFaceLandmarksTask
|
||||
- 第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md
|
||||
- 第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md
|
||||
- 基础信息记忆与肤质拖动
|
||||
- profile-interaction.mjs
|
||||
- submitPhotos
|
||||
- doCapture
|
||||
- i
|
||||
- .toString
|
||||
- ao
|
||||
- TinyYolov2Base
|
||||
- n
|
||||
- 微信退出登录-2026-10-13.md
|
||||
- normalize
|
||||
- startCamera
|
||||
- PredictSingleAgeAndGenderWithFaceAlignmentTask
|
||||
- PredictSingleFaceExpressionsWithFaceAlignmentTask
|
||||
- LabeledFaceDescriptors
|
||||
- .runAndExtendWithFaceDetections
|
||||
- readPhotoFile
|
||||
- onPhotoCaptured
|
||||
- 评分圈内居中、基础信息页固定与正脸语音修复
|
||||
- TinyYolov2
|
||||
- 报告等待页实拍背景-2026-10-12.md
|
||||
- .forwardInput
|
||||
- AgeGenderNet
|
||||
- .predictExpressions
|
||||
- 原始肌龄显示与鸿蒙滚轮兼容
|
||||
- harmony-picker.mjs
|
||||
- height
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `__awaiter()` - 59 edges
|
||||
@@ -156,35 +159,35 @@
|
||||
## Import Cycles
|
||||
- None detected.
|
||||
|
||||
## Communities (126 total, 24 thin omitted)
|
||||
## Communities (129 total, 27 thin omitted)
|
||||
|
||||
### Community 0 - "face-api.js"
|
||||
Cohesion: 0.02
|
||||
Nodes (36): cn(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), decodeBoxesLayer(), dr(), en(), FaceDetection() (+28 more)
|
||||
Nodes (41): bgrToRgbTensor(), cn(), computeReshapedDimensions(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), Dimensions(), dr() (+33 more)
|
||||
|
||||
### Community 1 - "线上闭环测试(不含真实微信授权)"
|
||||
Cohesion: 0.25
|
||||
Nodes (7): 发现并修复, 可复现命令, 已通过的检查, 线上闭环测试(不含真实微信授权), 结论, 话术及仓库同步, 验证边界
|
||||
|
||||
### Community 2 - "app.js"
|
||||
Cohesion: 0.12
|
||||
Nodes (22): cancelSkinDrag(), enterGuest(), esc(), goSkin(), loadAppFont(), loginAllowed(), loginSucceeded(), loginWechat() (+14 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (28): cancelSkinDrag(), decode(), drain(), esc(), goSkin(), load(), loginAllowed(), loginWechat() (+20 more)
|
||||
|
||||
### Community 3 - "skin-score.js"
|
||||
Cohesion: 0.12
|
||||
Nodes (45): acneGrade(), buildViewModel(), calcAcne(), calcContour(), calcSens(), calcSmooth(), calcTone(), calcWrinkles() (+37 more)
|
||||
Cohesion: 0.10
|
||||
Nodes (49): acneGrade(), buildViewModel(), calcAcne(), calcContour(), calcSens(), calcSmooth(), calcTone(), calcWrinkles() (+41 more)
|
||||
|
||||
### Community 4 - "customer-feedback-second.js"
|
||||
Cohesion: 0.17
|
||||
Nodes (10): assert, context, face, fs, geometry, original, path, source (+2 more)
|
||||
|
||||
### Community 5 - "disposeUnusedWeightTensors"
|
||||
Cohesion: 0.09
|
||||
Cohesion: 0.10
|
||||
Nodes (23): disposeUnusedWeightTensors(), extractorsFactory$3(), extractorsFactory$5(), extractorsFactory$7(), extractorsFactory$9(), extractParamsFromWeigthMap(), extractParamsFromWeigthMap$1(), extractParamsFromWeigthMap$2() (+15 more)
|
||||
|
||||
### Community 6 - "n"
|
||||
Cohesion: 0.17
|
||||
Nodes (38): a(), ae(), bf(), ce(), cf(), d(), de(), drawContour() (+30 more)
|
||||
### Community 6 - "f"
|
||||
Cohesion: 0.20
|
||||
Nodes (24): a(), cf(), d(), drawContour(), dt(), ea(), f(), g() (+16 more)
|
||||
|
||||
### Community 7 - "test/profile-store.js"
|
||||
Cohesion: 0.14
|
||||
@@ -195,16 +198,16 @@ Cohesion: 0.20
|
||||
Nodes (29): ageText(), api(), bind(), captureQualityHtml(), closeDetail(), collectFilters(), dimsHtml(), doRetry() (+21 more)
|
||||
|
||||
### Community 9 - "__awaiter"
|
||||
Cohesion: 0.18
|
||||
Nodes (9): __awaiter(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow(), __generator(), loadWeightMap(), Mtcnn() (+1 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (17): __awaiter(), bufferToImage(), ComposableTask(), FaceRecognitionNet(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow() (+9 more)
|
||||
|
||||
### Community 10 - "wechat-browser.mjs"
|
||||
Cohesion: 0.22
|
||||
Nodes (6): createAccounts, h5, ReportUI, require, root, SkinScore
|
||||
### Community 10 - "customer-feedback-third.mjs"
|
||||
Cohesion: 0.14
|
||||
Nodes (9): actualLines(), errors, guideLines, manifest, rect(), requests, root, widths (+1 more)
|
||||
|
||||
### Community 11 - "lib/accounts.js"
|
||||
Cohesion: 0.07
|
||||
Nodes (34): atomic(), body(), cookie(), cookies(), crypto, fs, json(), login() (+26 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (40): atomic(), body(), cookie(), cookies(), crypto, fs, json(), login() (+32 more)
|
||||
|
||||
### Community 12 - "server.js"
|
||||
Cohesion: 0.08
|
||||
@@ -214,9 +217,9 @@ Nodes (52): accounts, appendRecord(), callFacepp(), CaptureDiagnostics, CRED, cr
|
||||
Cohesion: 0.12
|
||||
Nodes (15): files, analyze-fail, analyzing, countdown-321, hint-far, hint-front, hint-hold, hint-near (+7 more)
|
||||
|
||||
### Community 14 - "NeuralNetwork"
|
||||
Cohesion: 0.20
|
||||
Nodes (3): getModelUris(), NeuralNetwork(), TinyFaceDetector()
|
||||
### Community 14 - "showScreen"
|
||||
Cohesion: 0.19
|
||||
Nodes (13): enterGuest(), loadAppFont(), loginSucceeded(), logoutWechat(), openOverlay(), prepareScanArtwork(), renderAccountStatus(), resetReport() (+5 more)
|
||||
|
||||
### Community 15 - "customer-feedback-fourth.mjs"
|
||||
Cohesion: 0.20
|
||||
@@ -242,13 +245,13 @@ Nodes (15): assert, check(), Diagnostics, face(), HeadPose, held, ReportUI, norm
|
||||
Cohesion: 0.40
|
||||
Nodes (9): build_case1(), build_case2(), build_truth(), col(), dump(), main(), parse_value(), set_path() (+1 more)
|
||||
|
||||
### Community 21 - "getEnv"
|
||||
Cohesion: 0.07
|
||||
Nodes (34): awaitMediaLoaded(), bufferToImage(), conv(), conv$1(), convDown(), convLayer$1(), convNoRelu(), createCanvas() (+26 more)
|
||||
### Community 21 - "extractFaces"
|
||||
Cohesion: 0.22
|
||||
Nodes (17): awaitMediaLoaded(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractImagePatches(), getContext2dOrThrow(), getEnv(), getMediaDimensions() (+9 more)
|
||||
|
||||
### Community 22 - "k"
|
||||
Cohesion: 0.16
|
||||
Nodes (16): ba(), ca(), da(), fa(), k(), pa(), pd(), Pi() (+8 more)
|
||||
### Community 22 - "i"
|
||||
Cohesion: 0.08
|
||||
Nodes (31): b(), ba(), ca(), cl(), da(), dn(), ee(), et() (+23 more)
|
||||
|
||||
### Community 23 - "facepp-skinanalyze(旷视皮肤分析专业版调用技能)"
|
||||
Cohesion: 0.20
|
||||
@@ -262,17 +265,17 @@ Nodes (8): 1. 音频格式约定(已定), 2. 播报清单(14 条), 3. 播放策
|
||||
Cohesion: 0.20
|
||||
Nodes (16): _arrayLikeToArray(), _arrayWithHoles(), _arrayWithoutHoles(), _classCallCheck(), _createClass(), _defineProperties(), h(), _iterableToArray() (+8 more)
|
||||
|
||||
### Community 26 - ".extractWeights"
|
||||
Cohesion: 0.05
|
||||
Nodes (30): AgeGenderNet(), createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractConvParamsFactory() (+22 more)
|
||||
### Community 26 - "NeuralNetwork"
|
||||
Cohesion: 0.06
|
||||
Nodes (28): createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractConvParamsFactory(), extractFCParamsFactory() (+20 more)
|
||||
|
||||
### Community 27 - "gen_voice.py"
|
||||
Cohesion: 0.30
|
||||
Nodes (11): ndarray, Path, gen_countdown(), main(), normalize_clip(), 倒计时单独按字生成再拼接,避免「三、二、一」整句被读成播报腔、与提示音气质漂移。 与提示音共用同一 speaker + instruct;句间留约…, 裁掉过长首尾静音,保留 ≤150ms 边沿,避免提示显迟钝。, 响度归一 −16 LUFS + 转 MP3 48k CBR mono 22050Hz。 (+3 more)
|
||||
|
||||
### Community 28 - "C"
|
||||
Cohesion: 0.11
|
||||
Nodes (28): Bo(), C(), cd(), cl(), co(), eo(), fo(), Ft() (+20 more)
|
||||
Cohesion: 0.09
|
||||
Nodes (31): ao(), Bo(), C(), cd(), co(), Do(), eo(), fo() (+23 more)
|
||||
|
||||
### Community 29 - "face-api.js 本地化依赖(h5/vendor/faceapi/)"
|
||||
Cohesion: 0.29
|
||||
@@ -287,8 +290,8 @@ Cohesion: 0.60
|
||||
Nodes (4): main(), 递归缩放 result 内全部坐标:{left,top,width,height} 矩形与 {x,y} 点,与原数据同为整数。, scale_coords(), to_data_url()
|
||||
|
||||
### Community 35 - "customer-feedback.js"
|
||||
Cohesion: 0.06
|
||||
Nodes (27): alternate, approved, assert, crop, details, excellent, fs, model (+19 more)
|
||||
Cohesion: 0.11
|
||||
Nodes (18): alternate, approved, assert, crop, details, excellent, fs, model (+10 more)
|
||||
|
||||
### Community 37 - "UI 外发版 10.09 图片资源"
|
||||
Cohesion: 0.40
|
||||
@@ -302,25 +305,21 @@ Nodes (4): 1meAI测肤旷视UI问题反馈, UI交互界面调整, 交互, **设
|
||||
Cohesion: 0.22
|
||||
Nodes (8): PSD 来源, 前端参数及限制, 后台校准留痕, 已确认并实现, 生产部署及只读验收, 皱纹(紧塑力)原文, 第四轮反馈修改与验收, 验证
|
||||
|
||||
### Community 41 - "vizCompose"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): renderReport(), renderViz(), setDim(), vizCollectMarks(), vizCompose(), vizFaceCrop(), vizLoadImage(), vizMapURL() (+3 more)
|
||||
|
||||
### Community 42 - "layer_at"
|
||||
Cohesion: 0.18
|
||||
Nodes (11): list, bounds(), layer_at(), main(), save_image(), composite(), main(), sha256() (+3 more)
|
||||
|
||||
### Community 43 - "extractAllFacesAndComputeResults"
|
||||
Cohesion: 0.14
|
||||
Nodes (10): age(), extendWithAge(), extendWithFaceExpressions(), extendWithGender(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult(), PredictAllAgeAndGenderTask(), PredictAllFaceExpressionsTask() (+2 more)
|
||||
Cohesion: 0.12
|
||||
Nodes (9): ComputeAllFaceDescriptorsTask(), ComputeSingleFaceDescriptorTask(), extendWithAge(), extendWithFaceDescriptor(), extendWithGender(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult(), PredictAllAgeAndGenderTask() (+1 more)
|
||||
|
||||
### Community 44 - "start-local.sh"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): HOST, PORT, start-local.sh script
|
||||
|
||||
### Community 47 - "Box"
|
||||
Cohesion: 0.11
|
||||
Nodes (11): bgrToRgbTensor(), Box(), Dimensions(), extendWithFaceLandmarks(), FaceLandmarks(), isDimensions(), isValidNumber(), isValidProbablitiy() (+3 more)
|
||||
### Community 47 - "extractFaceTensors"
|
||||
Cohesion: 0.14
|
||||
Nodes (6): Box(), DetectAllFaceLandmarksTask(), extendWithFaceLandmarks(), extractFaceTensors(), isTensor3D(), ObjectDetection()
|
||||
|
||||
### Community 48 - "已修改"
|
||||
Cohesion: 0.22
|
||||
@@ -347,8 +346,8 @@ Cohesion: 0.60
|
||||
Nodes (5): diffCtx(), loadJSON(), main(), okNum(), okStr()
|
||||
|
||||
### Community 58 - ".forwardInput"
|
||||
Cohesion: 0.14
|
||||
Nodes (16): computeReshapedDimensions(), extractBoundingBoxes(), getSizesForScale(), iou(), isEven(), nonMaxSuppression(), nonMaxSuppression$1(), normalize$1() (+8 more)
|
||||
Cohesion: 0.23
|
||||
Nodes (7): iou(), nonMaxSuppression(), nonMaxSuppression$1(), scale(), stage1(), stage2(), stage3()
|
||||
|
||||
### Community 61 - "报告布局与跟手交互调整"
|
||||
Cohesion: 0.33
|
||||
@@ -363,7 +362,7 @@ Cohesion: 0.29
|
||||
Nodes (4): EXPECTED, fails, pageErrors, pwRoot
|
||||
|
||||
### Community 64 - "recommendation.js"
|
||||
Cohesion: 0.38
|
||||
Cohesion: 0.33
|
||||
Nodes (6): assert, check(), missing, raw, SS, UI
|
||||
|
||||
### Community 65 - "微信登录接入与公网部署说明"
|
||||
@@ -382,9 +381,13 @@ Nodes (7): assert, end, fs, path, source, start, vm
|
||||
Cohesion: 0.29
|
||||
Nodes (3): errors, samples, unexpectedWrites
|
||||
|
||||
### Community 77 - "drawFaceExpressions"
|
||||
Cohesion: 0.19
|
||||
Nodes (10): DrawBox(), drawDetections(), drawFaceExpressions(), DrawFaceLandmarks(), extendWithFaceDetection(), FaceExpressions(), isWithFaceDetection(), isWithFaceExpressions() (+2 more)
|
||||
### Community 77 - "FaceLandmarks"
|
||||
Cohesion: 0.12
|
||||
Nodes (11): DrawBox(), drawDetections(), drawFaceExpressions(), DrawFaceLandmarks(), FaceExpressions(), FaceLandmarks(), isWithFaceDetection(), isWithFaceExpressions() (+3 more)
|
||||
|
||||
### Community 78 - ".toString"
|
||||
Cohesion: 0.15
|
||||
Nodes (8): FaceLandmarks5(), FaceMatch(), getCenterPoint(), la(), Point(), pr(), pt(), vt()
|
||||
|
||||
### Community 79 - "登录反馈与加载优化(2026-10-12)"
|
||||
Cohesion: 0.25
|
||||
@@ -430,25 +433,25 @@ Nodes (9): Ci(), Ei(), Fi(), Ii(), ki(), pe(), ri(), wi() (+1 more)
|
||||
Cohesion: 0.33
|
||||
Nodes (5): 公网部署与复测, 客户反馈修改(2026-10-12), 皱纹数据说明, 逐项处理, 验证
|
||||
|
||||
### Community 93 - ".runAndExtendWithFaceDetection"
|
||||
Cohesion: 0.23
|
||||
Nodes (7): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), ComposableTask(), detectAllFaces(), DetectSingleFaceTask(), Gf()
|
||||
### Community 93 - ".runAndExtendWithFaceDetections"
|
||||
Cohesion: 0.21
|
||||
Nodes (7): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), detectAllFaces(), DetectAllFacesTask(), DetectSingleFaceTask(), extendWithFaceDetection()
|
||||
|
||||
### Community 94 - "第三轮反馈:PSD 对齐、验收及上线记录"
|
||||
Cohesion: 0.33
|
||||
Nodes (5): 原稿与导出可追溯性, 已完成, 本地验证, 生产部署及只读验收, 第三轮反馈:PSD 对齐、验收及上线记录
|
||||
|
||||
### Community 95 - "pn"
|
||||
Cohesion: 0.22
|
||||
Nodes (8): bn(), fn(), ln(), pn(), un(), wl(), wn(), xn()
|
||||
Cohesion: 0.20
|
||||
Nodes (9): bn(), fn(), ln(), pd(), pn(), un(), wl(), wn() (+1 more)
|
||||
|
||||
### Community 96 - "play"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): busyWithFlow(), decode(), drain(), load(), play(), preload(), start(), stopCur() (+1 more)
|
||||
### Community 96 - "onDetect"
|
||||
Cohesion: 0.25
|
||||
Nodes (8): captureHaptic(), checkStill(), detectFrame(), detTick(), faceLuma(), faceOffsetX(), onDetect(), setMask()
|
||||
|
||||
### Community 97 - "account-client.js"
|
||||
Cohesion: 0.57
|
||||
Nodes (7): browser(), clearGuestCache(), ensureGuest(), guest(), init(), rememberKind(), request()
|
||||
Cohesion: 0.53
|
||||
Nodes (8): browser(), clearGuestCache(), ensureGuest(), guest(), init(), logout(), rememberKind(), request()
|
||||
|
||||
### Community 98 - "js/profile-store.js"
|
||||
Cohesion: 0.67
|
||||
@@ -458,18 +461,10 @@ Nodes (3): clean(), create(), key()
|
||||
Cohesion: 0.38
|
||||
Nodes (6): assess(), CaptureQuality, HeadPose, normalize(), scalar(), views
|
||||
|
||||
### Community 100 - ".computeFaceDescriptor"
|
||||
Cohesion: 0.18
|
||||
Nodes (4): ComputeAllFaceDescriptorsTask(), ComputeSingleFaceDescriptorTask(), extendWithFaceDescriptor(), FaceRecognitionNet()
|
||||
|
||||
### Community 101 - "wechat-fixture.cjs"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): crypto, fs, path
|
||||
|
||||
### Community 102 - "extractFaces"
|
||||
Cohesion: 0.14
|
||||
Nodes (9): height(), DetectAllFaceLandmarksTask(), DetectSingleFaceLandmarksTask(), extractFaces(), extractFaceTensors(), FaceLandmark68NetBase(), imageTensorToCanvas(), isTensor3D() (+1 more)
|
||||
|
||||
### Community 107 - "基础信息记忆与肤质拖动"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): 回归命令, 基础信息记忆与肤质拖动, 肤质交互, 范围
|
||||
@@ -483,51 +478,59 @@ Cohesion: 0.24
|
||||
Nodes (12): bindBackdropClose(), closeLoginSuccess(), closeOverlay(), handleCamHidden(), leaveCamera(), photosByView(), popHistorySilently(), requestAnalyze() (+4 more)
|
||||
|
||||
### Community 110 - "doCapture"
|
||||
Cohesion: 0.15
|
||||
Nodes (24): armNudge(), beginDetect(), captureFromVideo(), captureHaptic(), captureStageHint(), detectFrame(), detTick(), doCapture() (+16 more)
|
||||
Cohesion: 0.26
|
||||
Nodes (14): armNudge(), beginDetect(), captureFromVideo(), doCapture(), enableFallback(), hideNudge(), isManualMode(), loadFaceApi() (+6 more)
|
||||
|
||||
### Community 111 - "i"
|
||||
Cohesion: 0.09
|
||||
Nodes (29): b(), be(), bi(), dn(), ee(), Ef(), er(), et() (+21 more)
|
||||
### Community 111 - "n"
|
||||
Cohesion: 0.12
|
||||
Nodes (36): ae(), be(), bf(), bi(), ce(), de(), e(), Ef() (+28 more)
|
||||
|
||||
### Community 112 - ".toString"
|
||||
Cohesion: 0.18
|
||||
Nodes (7): FaceLandmarks5(), FaceMatch(), getCenterPoint(), la(), pr(), pt(), vt()
|
||||
|
||||
### Community 115 - "ao"
|
||||
Cohesion: 0.40
|
||||
Nodes (5): ao(), Do(), io(), so(), To()
|
||||
|
||||
### Community 116 - "TinyYolov2Base"
|
||||
Cohesion: 0.11
|
||||
Nodes (20): boxPredictionLayer(), convLayer(), convWithBatchNorm(), depthwiseConvLayer(), depthwiseSeparableConv$1(), fullyConnectedLayer(), getStridesForLayerIdx(), leaky() (+12 more)
|
||||
### Community 115 - "normalize"
|
||||
Cohesion: 0.07
|
||||
Nodes (28): conv(), conv$1(), convDown(), convLayer(), convLayer$1(), convNoRelu(), convWithBatchNorm(), denseBlock3() (+20 more)
|
||||
|
||||
### Community 117 - "startCamera"
|
||||
Cohesion: 0.29
|
||||
Nodes (8): applyMirror(), effMirror(), getCamStream(), handleCamVisible(), onCamTrackEnded(), renderCamStep(), startCamera(), waitVideoStable()
|
||||
Nodes (7): applyMirror(), effMirror(), getCamStream(), handleCamVisible(), onCamTrackEnded(), startCamera(), waitVideoStable()
|
||||
|
||||
### Community 122 - "readPhotoFile"
|
||||
Cohesion: 0.40
|
||||
Nodes (5): checkStill(), faceLuma(), hideCamStatus(), readPhotoFile(), showCamStatus()
|
||||
### Community 122 - "onPhotoCaptured"
|
||||
Cohesion: 0.27
|
||||
Nodes (10): busyWithFlow(), captureStageHint(), dropHint(), hideCamStatus(), onPhotoCaptured(), play(), readPhotoFile(), renderCamStep() (+2 more)
|
||||
|
||||
### Community 123 - "评分圈内居中、基础信息页固定与正脸语音修复"
|
||||
Cohesion: 0.40
|
||||
Nodes (4): 发布, 范围, 评分圈内居中、基础信息页固定与正脸语音修复, 验证
|
||||
|
||||
### Community 126 - ".forwardInput"
|
||||
Cohesion: 0.22
|
||||
Nodes (9): boxPredictionLayer(), decodeBoxesLayer(), depthwiseConvLayer(), getCenterCoordinatesAndSizesLayer(), getStridesForLayerIdx(), mobileNetV1(), outputLayer(), pointwiseConvLayer() (+1 more)
|
||||
|
||||
### Community 130 - ".predictExpressions"
|
||||
Cohesion: 0.15
|
||||
Nodes (4): extendWithFaceExpressions(), FaceExpressionNet(), PredictAllFaceExpressionsTask(), PredictSingleFaceExpressionsTask()
|
||||
|
||||
### Community 131 - "原始肌龄显示与鸿蒙滚轮兼容"
|
||||
Cohesion: 0.50
|
||||
Nodes (3): 原始肌龄显示与鸿蒙滚轮兼容, 回归, 范围
|
||||
|
||||
### Community 134 - "height"
|
||||
Cohesion: 0.32
|
||||
Nodes (5): height(), FaceLandmark68NetBase(), getSizesForScale(), normalize$1(), rescaleAndNormalize()
|
||||
|
||||
## Knowledge Gaps
|
||||
- **330 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+325 more)
|
||||
- **339 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+334 more)
|
||||
These have ≤1 connection - possible missing edges or undocumented components.
|
||||
- **24 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
- **27 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
|
||||
|
||||
## Suggested Questions
|
||||
_Questions this graph is uniquely positioned to answer:_
|
||||
|
||||
- **Why does `height()` connect `extractFaces` to `app.js`, `customer-feedback.js`, `scan-placeholder.mjs`, `profile-interaction.mjs`, `.forwardInput`?**
|
||||
_High betweenness centrality (0.050) - this node is a cross-community bridge._
|
||||
- **Why does `v()` connect `n` to `report-ui.js`, `vizCompose`, `C`, `face-api.js`?**
|
||||
_High betweenness centrality (0.038) - this node is a cross-community bridge._
|
||||
- **Why does `r()` connect `n` to `face-api.js`, `server.js`, `i`, `.toString`, `getEnv`, `k`, `C`?**
|
||||
_High betweenness centrality (0.037) - this node is a cross-community bridge._
|
||||
- **Why does `height()` connect `height` to `app.js`, `scan-placeholder.mjs`, `__awaiter`, `customer-feedback-third.mjs`, `profile-interaction.mjs`, `showScreen`, `extractFaceTensors`, `extractFaces`, `.forwardInput`?**
|
||||
_High betweenness centrality (0.051) - this node is a cross-community bridge._
|
||||
- **Why does `v()` connect `f` to `report-ui.js`, `face-api.js`, `app.js`, `C`?**
|
||||
_High betweenness centrality (0.043) - this node is a cross-community bridge._
|
||||
- **Why does `fill()` connect `report-ui.js` to `f`?**
|
||||
_High betweenness centrality (0.040) - this node is a cross-community bridge._
|
||||
- **Are the 24 inferred relationships involving `n()` (e.g. with `newRecordId()` and `ae()`) actually correct?**
|
||||
_`n()` has 24 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **Are the 27 inferred relationships involving `r()` (e.g. with `handleExport()` and `handleRecordsQuery()`) actually correct?**
|
||||
@@ -535,4 +538,4 @@ _Questions this graph is uniquely positioned to answer:_
|
||||
- **Are the 9 inferred relationships involving `i()` (e.g. with `da()` and `f()`) actually correct?**
|
||||
_`i()` has 9 INFERRED edges - model-reasoned connections that need verification._
|
||||
- **What connects `source`, `document_size`, `pages` to the rest of the system?**
|
||||
_330 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
_339 weakly-connected nodes found - possible documentation gaps or missing edges._
|
||||
File diff suppressed because one or more lines are too long
+1508
-1125
File diff suppressed because it is too large
Load Diff
+44
-24
@@ -40,8 +40,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/js/app.js": {
|
||||
"mtime": 1791875688.6889038,
|
||||
"ast_hash": "4d2624826f32e0c1af544e450117fcc6",
|
||||
"mtime": 1791878787.1929805,
|
||||
"ast_hash": "63cd1c83937dee7144a854b559f9cfd5",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/js/head-pose.js": {
|
||||
@@ -50,13 +50,13 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/js/skin-score.js": {
|
||||
"mtime": 1791735501.357809,
|
||||
"ast_hash": "783b6c9abbe2bf1832e8eafe5aead7e3",
|
||||
"mtime": 1791878308.0809805,
|
||||
"ast_hash": "59d3331a75a32837e04a21afa24fb34a",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/server.js": {
|
||||
"mtime": 1791873518.1209404,
|
||||
"ast_hash": "d2b79f3c00c8a4eaecc3c74b1c1e4dea",
|
||||
"mtime": 1791878239.2009728,
|
||||
"ast_hash": "fd843f8a1ed62e33c8294acbe78b56d0",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/build_fixtures.py": {
|
||||
@@ -135,13 +135,13 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/README.md": {
|
||||
"mtime": 1791873518.1209404,
|
||||
"ast_hash": "4acf1f915e74619da8124d47eeb4d971",
|
||||
"mtime": 1791878573.716988,
|
||||
"ast_hash": "74712e76c626625674a70aa0dcad5a63",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/index.html": {
|
||||
"mtime": 1791876371.4129393,
|
||||
"ast_hash": "7d998c3c1ba3614130709ca23490946b",
|
||||
"mtime": 1791878732.5729809,
|
||||
"ast_hash": "ac31b30a7a6a79a97355db95323784f9",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/vendor/faceapi/README.md": {
|
||||
@@ -830,8 +830,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/vendor/mobile-select/mobile-select.iife.min.js": {
|
||||
"mtime": 1791559485.790093,
|
||||
"ast_hash": "52578c3b95933f96493d3b879d353421",
|
||||
"mtime": 1791878239.1849728,
|
||||
"ast_hash": "9aeccc95862e05a14439c9cc906af5e5",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"tools/export_psd_assets.py": {
|
||||
@@ -865,8 +865,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/vendor/mobile-select/README.md": {
|
||||
"mtime": 1791559485.790093,
|
||||
"ast_hash": "fe3287899b96f598c60f9bc030a1368e",
|
||||
"mtime": 1791878239.2009728,
|
||||
"ast_hash": "f8b834ff5056a3402914e3c99d4807ff",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"design/ui-20261009/assets-overview.jpg": {
|
||||
@@ -1230,8 +1230,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/js/account-client.js": {
|
||||
"mtime": 1791875481.336928,
|
||||
"ast_hash": "d296f1dfb67a31adfde3221d48439186",
|
||||
"mtime": 1791878732.5729809,
|
||||
"ast_hash": "6e1c148f77744345313e2a7ecf1ea756",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/js/camera-geometry.js": {
|
||||
@@ -1260,8 +1260,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/report-persistence.mjs": {
|
||||
"mtime": 1791873250.4369178,
|
||||
"ast_hash": "1d543844b6961902d3cfcafa713b9a3b",
|
||||
"mtime": 1791878308.0849807,
|
||||
"ast_hash": "00d39c0fc96afe463fb1fbfe08b70656",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/ui-20261009.mjs": {
|
||||
@@ -1530,8 +1530,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/wechat-browser.mjs": {
|
||||
"mtime": 1791875570.3129308,
|
||||
"ast_hash": "3f5bcc6b56b3c28ecd7fb244ca645865",
|
||||
"mtime": 1791879046.0249834,
|
||||
"ast_hash": "af64c3a7fcd5c9338f9b86a584f987b6",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"docs/2026-10-11\u63d0\u4ea4\u524dReview.md": {
|
||||
@@ -1565,8 +1565,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/recommendation.js": {
|
||||
"mtime": 1791735559.0619977,
|
||||
"ast_hash": "589f7046d94485dfc1df20a6129640e0",
|
||||
"mtime": 1791878308.0849807,
|
||||
"ast_hash": "36742c00e21d68daae2e4678b185b3a1",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"docs/\u4ea7\u54c1\u63a8\u8350\u89c4\u5219-\u80a4\u9f84\u7248.md": {
|
||||
@@ -1975,8 +1975,8 @@
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/guest-privacy.mjs": {
|
||||
"mtime": 1791876371.4169395,
|
||||
"ast_hash": "e00620b50cf127df5b774a8a650aaa03",
|
||||
"mtime": 1791878787.1969805,
|
||||
"ast_hash": "b6143cba8c2aff80dd5c4687208b9f36",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/wechat-fixture.cjs": {
|
||||
@@ -1988,5 +1988,25 @@
|
||||
"mtime": 1791876185.0449448,
|
||||
"ast_hash": "905d6f5ae894843f99e66a15ad3d8b33",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/harmony-picker.mjs": {
|
||||
"mtime": 1791878418.4889727,
|
||||
"ast_hash": "299b3a47e9212d7e758fc89cfffc39f8",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"h5/test/raw-skin-age.js": {
|
||||
"mtime": 1791878418.4889727,
|
||||
"ast_hash": "44f22ac698b2249c8611ff302528dbb9",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"docs/\u539f\u59cb\u808c\u9f84\u663e\u793a\u4e0e\u9e3f\u8499\u6eda\u8f6e\u517c\u5bb9-2026-10-13.md": {
|
||||
"mtime": 1791878573.720988,
|
||||
"ast_hash": "7003663bd6ea0a0cf586cb2e3155d925",
|
||||
"semantic_hash": ""
|
||||
},
|
||||
"docs/\u5fae\u4fe1\u9000\u51fa\u767b\u5f55-2026-10-13.md": {
|
||||
"mtime": 1791878884.432984,
|
||||
"ast_hash": "ea59ab0974069d858e08da9789b88bf2",
|
||||
"semantic_hash": ""
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -91,10 +91,10 @@ node server.js # 默认 8080,可用 PORT=xxx 修改
|
||||
3. 评分由 `js/skin-score.js` 引擎完成,公式/锁值/话术/产品推荐以 `docs/三维度评分与话术生成需求文档_V2.3.docx` 为最高契约(V2.3 口径,含 §8.5 护理建议五段):
|
||||
- 6 分类:轮廓 = min(眼袋映射分, 下颌线角度分);皱纹(12 分区);光滑(毛孔/黑头/纹理);肤色水润(色素×水润);敏感(泛红面积取 `red_spot.red_spot_area`/红区分);痘痘(分级映射×丘疹 `acne.count` 计数扣分);分组按实际年龄;
|
||||
- 三维度木桶 min:紧塑力(轮廓+皱纹)、活力感(光滑+肤色水润)、稳定力(敏感+痘痘);
|
||||
- S = 7 + 3×P/100;档位:良好(P≥90)/改善(80–90)/预警(<80);肤龄钳制到实际年龄 ±3 并留痕;三视图版 18 条话术;
|
||||
- S = 7 + 3×P/100;档位:良好(P≥90)/改善(80–90)/预警(<80);生物可视年龄直接展示算法原始肌龄,不再按实际年龄 ±3 修正;三视图版 18 条话术;
|
||||
- 精华推荐三重判据(均分档/最差项约束/年龄组档)取最强档,triggers 记录命中判据。
|
||||
4. 前端 `SkinScore.buildViewModel(data.result, data.side, { realAge, skinType })` → viewModel 直渲报告页:
|
||||
- 头部:综合评分(`vm.total`,缺失回退三维度 S 均分×10)、肤龄 `vm.skinAgeDisplay` + 钳制标记 `vm.ageClampNote`;三维度 S(1 位小数)+ 档位配色(良好绿/改善琥珀/预警红);
|
||||
- 头部:综合评分(`vm.total`,缺失回退三维度 S 均分×10)、肤龄读取 `SkinScore.reportSkinAge(vm, result)`;历史报告同样读取原始肌龄而非旧修正展示值,原值缺失显示 `--`;三维度 S(1 位小数)+ 档位配色(良好绿/改善琥珀/预警红);
|
||||
- 三个 Tab 面板各展示所辖两分类:分类名、S + 档位徽章、话术正文(`vm.cats.*.text`);P 值不直接展示,`P=null` 的分类显示"本次未检出有效数据";
|
||||
- 报告底部:降级标注(`vm.notes` 中与侧脸/下颌线/补拍相关的条目,去掉接口质量码后展示)、护理建议五段(`vm.careAdvice`)、精华推荐卡(`vm.essence.name` + 命中判据;推荐正文已在护理建议第 4 段,卡内不重复);
|
||||
- 近期优先关注 = 最差维度(`vm.focus`);6 分类各配检测可视化图:轮廓=正脸实拍、皱纹=正脸压暗+`texture_enhanced_lines` 皱纹增强线(线图层几乎全透明,直挂白底不可读,故与压暗沉脸合成)、光滑=正脸+色斑/痣/痤疮标记叠加、肤色=`brown_area` 色沉图、敏感=`red_area` 红区图、痘痘=正脸+闭口粉刺/痤疮标记叠加(marks 按原像素坐标叠加到正脸实拍)。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Third-round PSD, 2026-10-12. Measurements include text transforms.
|
||||
Native 248px WeChat header is excluded. Fixed text sizes/line breaks are
|
||||
intentional; flexible photo backgrounds and safe areas accommodate phones. */
|
||||
.account-logout { font-size: 11px; opacity: .8; white-space: nowrap; }
|
||||
.splash-slogan {
|
||||
left: calc(50% - 1.041667px); right: auto;
|
||||
width: 249.305556px; height: 75.347222px;
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@
|
||||
<section class="screen" id="s-history">
|
||||
<div class="history-content">
|
||||
<h1 class="report-title">您的报告</h1>
|
||||
<div class="account-status account-status-history"><span data-auth-status></span></div>
|
||||
<div class="account-status account-status-history"><span data-auth-status></span><button type="button" class="account-logout" data-action="logout-wechat" hidden>退出登录</button></div>
|
||||
<div class="card history-count"><span>测试数量</span><p id="history-count">正在读取报告…</p></div>
|
||||
<p class="identity-note" id="identity-note"></p>
|
||||
<div id="history-list" aria-live="polite"></div>
|
||||
@@ -42,7 +42,7 @@
|
||||
</section>
|
||||
<section class="screen" id="s-splash">
|
||||
<div class="splash-bg"></div>
|
||||
<div class="account-status account-status-home"><span data-auth-status></span></div>
|
||||
<div class="account-status account-status-home"><span data-auth-status></span><button type="button" class="account-logout" data-action="logout-wechat" hidden>退出登录</button></div>
|
||||
<img class="splash-slogan" src="assets/img/ui-20261012/splash-slogan.png" width="718" height="217" loading="lazy" alt="ONE ME,FROM THE INSIDE OUT;以我为始 自有表现">
|
||||
<div class="splash-bottom">
|
||||
<div class="brand-row">
|
||||
|
||||
@@ -56,5 +56,10 @@ window.AccountClient = (function () {
|
||||
return v;
|
||||
});
|
||||
}
|
||||
return { request: request, init: init, browser: browser, guest: guest, ensureGuest: ensureGuest, user: function () { return user; } };
|
||||
function logout() {
|
||||
return request('api/auth/logout', {}).then(function (v) {
|
||||
user = null; guestReady = false; rememberKind(); return v;
|
||||
});
|
||||
}
|
||||
return { request: request, init: init, browser: browser, guest: guest, ensureGuest: ensureGuest, logout: logout, user: function () { return user; } };
|
||||
})();
|
||||
|
||||
+30
-4
@@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
/* ─── 屏幕管理:页面内不放返回按钮,前进时 pushState,微信/浏览器返回键经 popstate 回到上一屏 ─── */
|
||||
var currentScreen = 's-login';
|
||||
var currentScreen = 's-login', reportOwner = null, logoutBusy = false;
|
||||
var fontStylesLoaded = false;
|
||||
function loadAppFont() {
|
||||
if (fontStylesLoaded) return;
|
||||
@@ -79,6 +79,11 @@
|
||||
label.textContent = user ? (user.kind === 'wechat' ? '微信已登录' : '游客模式') : '尚未登录';
|
||||
label.parentElement.dataset.kind = user ? user.kind : '';
|
||||
});
|
||||
document.querySelectorAll('[data-action="logout-wechat"]').forEach(function (button) {
|
||||
button.hidden = !user || user.kind !== 'wechat';
|
||||
button.disabled = logoutBusy;
|
||||
button.textContent = logoutBusy ? '正在退出…' : '退出登录';
|
||||
});
|
||||
}
|
||||
function loginSucceeded() {
|
||||
showScreen('s-splash', 'replace');
|
||||
@@ -94,6 +99,7 @@
|
||||
}
|
||||
function showScreen(id, nav) { // nav: 'push' 新增历史 | 'replace' 替换当前历史 | 缺省不动历史
|
||||
if (id !== 's-login' && !AccountClient.user() && !(/[?&]mock=1/.test(location.search))) id = 's-login';
|
||||
if (id === 's-report' && !IS_MOCK && reportOwner !== (AccountClient.user() && AccountClient.user().id)) id = 's-splash';
|
||||
if (id === 's-profile' || profileOwner !== ProfileStore.key(AccountClient.user())) restoreProfile();
|
||||
if (['s-profile', 's-cam', 's-report'].includes(id)) loadAppFont();
|
||||
if (id === 's-profile' || id === 's-cam') prepareScanArtwork();
|
||||
@@ -1401,6 +1407,7 @@
|
||||
}
|
||||
|
||||
function renderReport(vm, frontPhoto, sidePhoto, payload) {
|
||||
reportOwner = AccountClient.user() && AccountClient.user().id;
|
||||
// 头部:综合评分(vm.total 缺失时回退三维度 S 均分×10)
|
||||
var total = vm.total;
|
||||
if (total == null) {
|
||||
@@ -1412,10 +1419,11 @@
|
||||
$('#stat-score').innerHTML = total != null ? '<b>' + total + '</b><i>分</i>' : '<b>--</b><i>分</i>';
|
||||
var ring = $('#ring-fg');
|
||||
ring.style.strokeDashoffset = total != null ? 326.73 * (1 - Math.max(0, Math.min(100, total)) / 100) : 326.73;
|
||||
$('#stat-age').innerHTML = vm.skinAgeDisplay != null ? '<b>' + vm.skinAgeDisplay + '</b><i>岁</i>' : '<b>--</b><i>岁</i>';
|
||||
var displayedAge = SkinScore.reportSkinAge(vm, payload && payload.result);
|
||||
$('#stat-age').innerHTML = displayedAge != null ? '<b>' + displayedAge + '</b><i>岁</i>' : '<b>--</b><i>岁</i>';
|
||||
// 肌龄对比胶囊(新版设计「比实际年龄年轻2岁」);缺实际年龄或肌龄时不展示
|
||||
var pill = $('#stat-age-note');
|
||||
var diff = vm.skinAgeDisplay != null && vm.realAge ? vm.skinAgeDisplay - vm.realAge : null;
|
||||
var diff = displayedAge != null && vm.realAge ? displayedAge - vm.realAge : null;
|
||||
pill.hidden = diff == null;
|
||||
pill.textContent = diff == null ? '' : diff < 0 ? '比实际年龄年轻' + (-diff) + '岁' : diff > 0 ? '比实际年龄大' + diff + '岁' : '与实际年龄相当';
|
||||
|
||||
@@ -1618,6 +1626,7 @@
|
||||
},
|
||||
'history': function () { openHistory(); },
|
||||
'history-retry': function () { renderHistoryList(); },
|
||||
'logout-wechat': logoutWechat,
|
||||
'open-wechat-login': function () {
|
||||
historySeq++;
|
||||
$('#login-consent').checked = false;
|
||||
@@ -1668,8 +1677,9 @@
|
||||
var btn = event.target.closest('[data-report-id]');
|
||||
if (!btn || btn.disabled) return;
|
||||
btn.disabled = true;
|
||||
var seq = historySeq, owner = AccountClient.user() && AccountClient.user().id;
|
||||
AccountClient.request('api/reports/' + encodeURIComponent(btn.dataset.reportId)).then(function (data) {
|
||||
if (currentScreen !== 's-history') return;
|
||||
if (currentScreen !== 's-history' || seq !== historySeq || owner !== (AccountClient.user() && AccountClient.user().id)) return;
|
||||
var entry = data.report;
|
||||
renderReport(entry.vm, entry.photos[0], entry.photos[1], Object.assign({}, entry.payload, { presentation: entry.presentation }));
|
||||
showScreen('s-report', 'push');
|
||||
@@ -1677,6 +1687,22 @@
|
||||
});
|
||||
|
||||
var loginBusy = false;
|
||||
function logoutWechat() {
|
||||
var user = AccountClient.user();
|
||||
if (logoutBusy || !user || user.kind !== 'wechat') return;
|
||||
if (!window.confirm('确认退出微信登录?\n历史报告将保留,重新微信登录后可查看。')) return;
|
||||
logoutBusy = true; renderAccountStatus();
|
||||
AccountClient.logout().then(function () {
|
||||
historySeq++; reportOwner = null;
|
||||
$('#history-list').textContent = ''; $('#history-count').textContent = ''; $('#identity-note').textContent = '';
|
||||
$('#login-consent').checked = false;
|
||||
$('#login-message').textContent = '已退出微信登录,历史报告已保留。';
|
||||
showScreen('s-login', 'replace');
|
||||
$('[data-action="login-wechat"]').focus({ preventScroll: true });
|
||||
}).catch(function (e) { toast('退出失败:' + e.message); }).finally(function () {
|
||||
logoutBusy = false; renderAccountStatus();
|
||||
});
|
||||
}
|
||||
function loginAllowed() {
|
||||
if (loginBusy) return false;
|
||||
if (!$('#login-consent').checked) { toast('请先阅读并同意用户协议和隐私政策'); return false; }
|
||||
|
||||
+12
-8
@@ -689,14 +689,11 @@
|
||||
var notes = [];
|
||||
var realAge = val(profile.realAge);
|
||||
|
||||
// 肌肤年龄:原值不改写,展示值钳制到 realAge±3 并留痕
|
||||
// 生物可视年龄直接使用算法原始肌龄,不再按实际年龄钳制。
|
||||
var skinAge = val(r.skin_age);
|
||||
var skinAgeDisplay = skinAge, ageClampNote = '';
|
||||
if (realAge == null) {
|
||||
notes.push('未提供实际年龄:不修正展示肌龄;产品推荐仍使用接口原始肌龄');
|
||||
} else if (skinAge != null) {
|
||||
skinAgeDisplay = Math.min(Math.max(skinAge, realAge - 3), realAge + 3);
|
||||
if (skinAgeDisplay !== skinAge) ageClampNote = '触发(原' + skinAge + '→' + skinAgeDisplay + ')';
|
||||
notes.push('未提供实际年龄:肌龄展示与产品推荐均使用接口原始肌龄');
|
||||
}
|
||||
|
||||
var skinTypeCn = skinTypeCnOf(r, profile); // {skin_type_cn}:接口枚举优先,回退用户自选肤质
|
||||
@@ -898,7 +895,14 @@
|
||||
right: { right_jawline_info: { right_jawline_angle: 113.4, right_jawline_angle_level: 1, right_jawline_quality: 1 } } // 偏离 2.6° → 94.8
|
||||
};
|
||||
|
||||
// Historical snapshots may retain an old clamped display value. Read their
|
||||
// original age without modifying the stored snapshot or recalculating copy.
|
||||
function reportSkinAge(vm, result) {
|
||||
if (vm && Object.prototype.hasOwnProperty.call(vm, 'skinAgeRaw')) return val(vm.skinAgeRaw);
|
||||
return val(result && result.skin_age);
|
||||
}
|
||||
return {
|
||||
reportSkinAge: reportSkinAge,
|
||||
buildViewModel: buildViewModel,
|
||||
recommendProduct: recommendProduct,
|
||||
MOCK_RESULT: MOCK_RESULT,
|
||||
@@ -941,7 +945,7 @@ if (typeof require === 'function' && typeof module !== 'undefined' && require.ma
|
||||
ok(vm.cats.acne.P === 85, '痘痘P = min(分级映射85, 100−14.5=85.5) = 85');
|
||||
ok(vm.cats.wrinkles.S === 7.69 && vm.cats.smooth.S === 9.45 && vm.cats.tone.S === 9.45, 'S=7+3×P/100 两位小数(含 9.445→9.45 浮点修正)');
|
||||
ok(vm.dims.firm.P === 23 && vm.dims.vita.P === 81.5 && vm.dims.stab.P === 85, '维度木桶 min:紧塑力23 / 活力感81.5 / 稳定力85');
|
||||
ok(vm.skinAgeRaw === 20 && vm.skinAgeDisplay === 22 && vm.ageClampNote === '触发(原20→22)', '肌肤年龄钳制 20→22 留痕,原值不改写');
|
||||
ok(vm.skinAgeRaw === 20 && vm.skinAgeDisplay === 20 && vm.ageClampNote === '', '肌肤年龄直接展示原值20,不按实际年龄钳制');
|
||||
ok(vm.group === '25+组', '原始肌龄20 → 25+组');
|
||||
ok(vm.essence.name === '25+束紧精华' && vm.recommendation.tier === 1,
|
||||
'肌龄小于32,即使皱纹低分也固定25+,不再使用最差项升档');
|
||||
@@ -974,14 +978,14 @@ if (typeof require === 'function' && typeof module !== 'undefined' && require.ma
|
||||
&& vm2.dims.stab.P === Math.min(vm2.cats.sens.P, vm2.cats.acne.P), '三维度 P = min(旗下两分类)');
|
||||
ok(vm2.group === '25+组', '实际年龄40不影响推荐,原始肌龄20仍为25+组');
|
||||
|
||||
console.log('== T3 钳制边界 ==');
|
||||
console.log('== T3 原始肌龄展示 ==');
|
||||
var r3 = clone(SS.MOCK_RESULT);
|
||||
r3.skin_age = { value: 24 };
|
||||
var vm3 = SS.buildViewModel(r3, SS.MOCK_SIDE, { realAge: 25 });
|
||||
ok(vm3.skinAgeDisplay === 24 && vm3.ageClampNote === '', '±3 以内不钳制、不留痕');
|
||||
r3.skin_age = { value: 40 };
|
||||
var vm3b = SS.buildViewModel(r3, SS.MOCK_SIDE, { realAge: 25 });
|
||||
ok(vm3b.skinAgeDisplay === 28 && vm3b.ageClampNote === '触发(原40→28)' && vm3b.skinAgeRaw === 40, '上边界钳制 40→28');
|
||||
ok(vm3b.skinAgeDisplay === 40 && vm3b.ageClampNote === '' && vm3b.skinAgeRaw === 40, '原始肌龄40不因实际年龄25而改变');
|
||||
|
||||
console.log('== T4 最差项不再覆盖肤龄推荐 ==');
|
||||
var r4 = clone(SS.MOCK_RESULT);
|
||||
|
||||
+2
-4
@@ -514,10 +514,8 @@ function photoUrls(rec) {
|
||||
|
||||
/* 列表用的字段子集(不含 raw_json 与文案全文) */
|
||||
function displaySkinAge(rec) {
|
||||
if (Object.prototype.hasOwnProperty.call(rec, 'skin_age_display')) return rec.skin_age_display;
|
||||
// 旧记录没有展示值,沿用生成时的规则,不重算推荐或改写文件。
|
||||
if (rec.skin_age == null || rec.real_age == null) return rec.skin_age;
|
||||
return Math.min(Math.max(rec.skin_age, rec.real_age - 3), rec.real_age + 3);
|
||||
// Match H5 even for old records; do not rewrite historical files.
|
||||
return SkinScore.reportSkinAge({ skinAgeRaw: rec.skin_age });
|
||||
}
|
||||
function listItem(rec) {
|
||||
return {
|
||||
|
||||
@@ -69,6 +69,7 @@ try {
|
||||
assert.equal(await page.locator('#p-login-success.show').count(), 0, 'guest entry never shows login success');
|
||||
assert.equal(await page.locator('[data-action="switch-login"]').count(), 0, 'switch-login controls are removed');
|
||||
assert.equal(await page.locator('#s-splash [data-auth-status]').textContent(), '游客模式');
|
||||
assert.equal(await page.locator('[data-action="logout-wechat"]:visible').count(), 0, 'guests have no logout control');
|
||||
assert.equal(guestRequests.length, 0, 'guest entry performs no authentication request');
|
||||
assert.equal(await page.evaluate(() => localStorage.getItem('juzi.identity.v1')), null);
|
||||
assert.equal(await page.evaluate(() => localStorage.getItem('juzi.reports.v1')), null);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Real app + actual touch/mouse dispatch with varying UA/capabilities.
|
||||
// Chromium UA regression, not a substitute for HarmonyOS hardware verification.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn, execSync } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
const { chromium } = await import(pathToFileURL(execSync('npm root -g', { encoding: 'utf8' }).trim() + '/playwright/index.mjs').href);
|
||||
const h5 = fileURLToPath(new URL('../', import.meta.url));
|
||||
const dataRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'juzi-harmony-test-'));
|
||||
let child, browser;
|
||||
let base = process.env.H5_BASE_URL;
|
||||
try {
|
||||
if (!base) {
|
||||
base = 'http://127.0.0.1:18116/';
|
||||
child = spawn(process.execPath, ['server.js'], { cwd: h5, env: { ...process.env, PORT: '18116', HOST: '127.0.0.1', JUZI_DATA_ROOT: dataRoot,
|
||||
PUBLIC_BASE_URL: '', WECHAT_APP_ID: '', WECHAT_APP_SECRET: '', SKIN_API_KEY: 'fixture', SKIN_API_SECRET: 'fixture' }, stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(Error('test startup timeout')), 10000);
|
||||
child.stdout.on('data', b => { if (b.toString().includes('已启动')) { clearTimeout(timer); resolve(); } });
|
||||
child.once('exit', code => { clearTimeout(timer); reject(Error('server exited ' + code)); });
|
||||
});
|
||||
}
|
||||
browser = await chromium.launch({ headless: true });
|
||||
const android = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36';
|
||||
const desktop = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
|
||||
const cases = [
|
||||
{ name: 'Android touch', ua: android, touch: true },
|
||||
{ name: 'OpenHarmony 5 touch', ua: 'Mozilla/5.0 (Phone; OpenHarmony 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile', touch: true },
|
||||
{ name: 'OpenHarmony 6 WeChat touch', ua: 'Mozilla/5.0 (Phone;OpenHarmony 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 ArkWeb/6.0.0.42 Mobile MicroMessenger/8.0', touch: true },
|
||||
{ name: 'Desktop UA on touch hardware', ua: desktop, touch: true },
|
||||
{ name: 'Desktop mouse without TouchEvent', ua: desktop, touch: false },
|
||||
{ name: 'Android UA without touch hardware', ua: android, touch: false }
|
||||
];
|
||||
for (const fixture of cases) {
|
||||
const context = await browser.newContext({ viewport: { width: 375, height: 726 }, isMobile: fixture.touch, hasTouch: fixture.touch, userAgent: fixture.ua });
|
||||
const page = await context.newPage(), errors = [], writes = [];
|
||||
page.setDefaultTimeout(30000);
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
if (!fixture.touch) await page.addInitScript(() => { delete window.TouchEvent; });
|
||||
await page.route('**/*', route => {
|
||||
const req = route.request();
|
||||
if (req.method() !== 'GET') { writes.push(req.url()); return route.abort(); }
|
||||
if (/\.woff2?(\?|$)/.test(req.url())) return route.abort();
|
||||
if (req.url().endsWith('/api/auth/session')) return route.fulfill({ json: { ok: true, user: { id: 'harmony-picker-fixture', kind: 'wechat' } } });
|
||||
return route.continue();
|
||||
});
|
||||
await page.route('**/mobile-select.iife.min.js', async route => {
|
||||
const response = await route.fetch();
|
||||
await route.fulfill({ response, body: await response.text() + ';var OriginalMobileSelect=MobileSelect;window.__pickers={};MobileSelect=function(c){var p=new OriginalMobileSelect(c);window.__pickers[c.trigger]=p;return p;};' });
|
||||
});
|
||||
await page.goto(base, { waitUntil: 'domcontentloaded' }); await page.waitForSelector('#s-splash.active');
|
||||
await page.click('[data-action="open-privacy"]'); await page.click('#privacy-check'); await page.click('[data-action="agree-continue"]');
|
||||
await page.waitForSelector('#s-profile.active');
|
||||
const cdp = await context.newCDPSession(page);
|
||||
for (const trigger of ['#pick-birth', '#pick-region']) {
|
||||
await page.click(trigger); await page.waitForTimeout(350);
|
||||
const events = await page.evaluate(t => __pickers[t].eventHandleMap.panel.event, trigger);
|
||||
assert.deepEqual(events, fixture.touch ? ['touchstart', 'touchend', 'touchmove'] : ['mousedown', 'mousemove', 'mouseup'], fixture.name);
|
||||
assert.equal(await page.locator('.ms-mobile-select.ms-show').evaluate(el => !!el.closest('#s-profile')), false, 'profile scroll lock cannot intercept picker touch');
|
||||
for (const column of [0, 1]) {
|
||||
if (trigger === '#pick-region' && column === 1) await page.evaluate(() => __pickers['#pick-region'].locatePosition(0, REGION_DATA.findIndex(p => p[0] === '浙江省')));
|
||||
const before = await page.evaluate(t => JSON.stringify(__pickers[t].getCurValue()), trigger);
|
||||
const wheel = page.locator('.ms-mobile-select.ms-show .ms-wheel').nth(column), box = await wheel.boundingBox();
|
||||
const x = box.x + box.width / 2, y = box.y + box.height / 2;
|
||||
if (fixture.touch) {
|
||||
await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x, y, id: 1 }] });
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
await cdp.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [{ x, y: y - i * 20, id: 1 }] });
|
||||
await page.waitForTimeout(25);
|
||||
}
|
||||
await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
||||
} else {
|
||||
await page.mouse.move(x, y); await page.mouse.down(); await page.mouse.move(x, y - 80, { steps: 4 }); await page.mouse.up();
|
||||
}
|
||||
await page.waitForTimeout(250);
|
||||
assert.notEqual(await page.evaluate(t => JSON.stringify(__pickers[t].getCurValue()), trigger), before, fixture.name + '/' + trigger + '/' + column + ' must move');
|
||||
}
|
||||
const chosen = await page.evaluate(t => __pickers[t].getCurValue(), trigger);
|
||||
await page.click('.ms-mobile-select.ms-show .ms-ensure'); await page.waitForTimeout(250);
|
||||
if (trigger === '#pick-birth') {
|
||||
const value = parseInt(chosen[0]) + '-' + String(parseInt(chosen[1])).padStart(2, '0');
|
||||
assert.equal(await page.locator('#f-birth').inputValue(), value);
|
||||
} else {
|
||||
const value = await page.locator('#f-region').inputValue();
|
||||
assert(value.includes(chosen[0].value) && value.includes(chosen[1].value));
|
||||
}
|
||||
await page.click(trigger); await page.waitForTimeout(300);
|
||||
assert.deepEqual(await page.evaluate(t => __pickers[t].getCurValue(), trigger), chosen, 'reopening retains confirmed values');
|
||||
await page.click('.ms-mobile-select.ms-show .ms-cancel'); await page.waitForTimeout(250);
|
||||
}
|
||||
await page.click('#pick-gender'); await page.click('.ms-mobile-select.ms-show .ms-ensure');
|
||||
await page.click('[data-action="submit-profile"]'); await page.waitForSelector('#p-photoguide.show');
|
||||
assert.deepEqual(errors, []); assert.deepEqual(writes, []);
|
||||
console.log('PASS ' + fixture.name + ': year/month/province/city drag, cascade, confirm/reopen, profile validation');
|
||||
await context.close();
|
||||
}
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
if (child && child.exitCode == null) { const stopped = once(child, 'exit'); child.kill(); await stopped; }
|
||||
fs.rmSync(dataRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const assert = require('node:assert/strict');
|
||||
const SS = require('../js/skin-score');
|
||||
for (const raw of [20, 24, 28, 40, 50, 24.6, null]) {
|
||||
for (const realAge of [25, 30, 60, undefined]) {
|
||||
const result = structuredClone(SS.MOCK_RESULT);
|
||||
if (raw == null) delete result.skin_age; else result.skin_age = { value: raw };
|
||||
const before = JSON.stringify(result);
|
||||
const vm = SS.buildViewModel(result, SS.MOCK_SIDE, { realAge });
|
||||
assert.equal(vm.skinAgeRaw, raw); assert.equal(vm.skinAgeDisplay, raw);
|
||||
assert.equal(vm.ageClampNote, ''); assert.equal(SS.reportSkinAge(vm, result), raw);
|
||||
assert.equal(JSON.stringify(result), before);
|
||||
}
|
||||
}
|
||||
const legacy = { skinAgeRaw: 24, skinAgeDisplay: 57, realAge: 60 };
|
||||
assert.equal(SS.reportSkinAge(legacy), 24);
|
||||
assert.equal(legacy.skinAgeDisplay, 57, 'historical VM is not mutated');
|
||||
assert.equal(SS.reportSkinAge({ skinAgeDisplay: 57 }, { skin_age: { value: 24 } }), 24);
|
||||
assert.equal(SS.reportSkinAge({ skinAgeDisplay: 57 }), null, 'clamped display is never a raw-age fallback');
|
||||
assert.equal(SS.reportSkinAge({ skinAgeRaw: null, skinAgeDisplay: 57 }), null);
|
||||
assert.equal(SS.reportSkinAge({}, { skin_age: '24.6' }), 24.6);
|
||||
console.log('PASS: raw age across actual-age/decimal/missing cases; old display ignored; legacy payload fallback; no mutation or invented age');
|
||||
@@ -54,7 +54,7 @@ for (const realAge of [24, 40, 60, undefined]) {
|
||||
assert.equal(vm.recommendation.skinAge, 24);
|
||||
assert(Math.abs(vm.recommendation.scores.firm - 7.69) < 1e-12);
|
||||
assert.equal(UI.build(vm, raw).product.tier, 1);
|
||||
if (realAge === 60) assert.equal(vm.skinAgeDisplay, 57); // Display isn't the recommendation input.
|
||||
assert.equal(vm.skinAgeDisplay, 24); // Display and recommendation both use raw skin age.
|
||||
count++;
|
||||
}
|
||||
delete raw.skin_age;
|
||||
|
||||
@@ -47,8 +47,8 @@ try {
|
||||
assert.equal(result.report_saved, true);
|
||||
assert(result.vm && result.presentation);
|
||||
assert(result.vm.skinAgeRaw < 32);
|
||||
assert.equal(result.vm.skinAgeDisplay, 57);
|
||||
assert.equal(result.vm.essence.tier, 1); // Raw age, not actual60 or display57.
|
||||
assert.equal(result.vm.skinAgeDisplay, result.vm.skinAgeRaw);
|
||||
assert.equal(result.vm.essence.tier, 1); // Raw age, never actual60.
|
||||
assert.equal(result.presentation.product.tier, 1);
|
||||
const adminRecord = (await (await api.get(base + 'api/admin/records/' + result.record_id)).json()).record;
|
||||
assert.deepEqual(adminRecord.presentation, result.presentation);
|
||||
@@ -58,7 +58,7 @@ try {
|
||||
for (const key of ['firm', 'vita', 'stab']) assert.equal(adminRecord.dims[key].S, result.vm.dims[key].S);
|
||||
const list = await (await api.get(base + 'api/admin/records')).json();
|
||||
assert.equal(list.items[0].skin_age, result.vm.skinAgeRaw);
|
||||
assert.equal(list.items[0].skin_age_display, 57);
|
||||
assert.equal(list.items[0].skin_age_display, result.vm.skinAgeRaw);
|
||||
assert.deepEqual(list.items[0].recommendation, result.vm.recommendation);
|
||||
const csv = await (await api.get(base + 'api/admin/export')).text();
|
||||
assert(csv.includes('原始肌龄(推荐依据),前端展示肌龄'));
|
||||
@@ -74,7 +74,7 @@ try {
|
||||
const row = desktop.locator('#tbl-records tbody tr').first();
|
||||
await row.waitFor();
|
||||
assert((await row.textContent()).includes('原始 ' + result.vm.skinAgeRaw + '岁'));
|
||||
assert((await row.textContent()).includes('展示 57岁'));
|
||||
assert((await row.textContent()).includes('展示 ' + result.vm.skinAgeRaw + '岁'));
|
||||
assert((await row.textContent()).includes("1'me 主场精华 I(25+)"));
|
||||
for (const key of ['firm', 'vita', 'stab']) {
|
||||
assert((await row.textContent()).includes(ReportUI.dimNames[key] + ' ' + result.vm.dims[key].S.toFixed(1) + ' ' + ReportUI.levelMap[result.vm.dims[key].level]));
|
||||
@@ -93,7 +93,7 @@ try {
|
||||
await desktop.waitForFunction(() => document.querySelector('#d-body').textContent.includes('推荐依据'));
|
||||
const detailText = await desktop.locator('#d-body').textContent();
|
||||
assert(detailText.includes(result.vm.recommendation.reason));
|
||||
assert(detailText.includes('前端展示肌龄 57岁'));
|
||||
assert(detailText.includes('前端展示肌龄 ' + result.vm.skinAgeRaw + '岁'));
|
||||
for (const view of result.presentation.views) assert(detailText.includes(view.text));
|
||||
for (const care of result.presentation.care) assert(detailText.includes(care.text));
|
||||
if (process.env.H5_ARTIFACT_DIR) {
|
||||
@@ -146,19 +146,21 @@ try {
|
||||
const legacy = JSON.parse(fs.readFileSync(snapshotFile, 'utf8'));
|
||||
legacy.presentation.product = { ...ReportUI.products[1], image: 'assets/img/ui-20261009/product-35.png' };
|
||||
legacy.presentation.views[0].text = '历史报告原文,不应按新文案重新计算。';
|
||||
legacy.vm.skinAgeDisplay = 57; // Old snapshot retains the retired display clamp.
|
||||
legacy.vm.essence = { name: '35+支撑精华', text: '旧版推荐', triggers: ['最差项约束'] };
|
||||
delete legacy.vm.recommendation;
|
||||
fs.writeFileSync(snapshotFile, JSON.stringify(legacy));
|
||||
record.essence = legacy.vm.essence;
|
||||
delete record.presentation;
|
||||
delete record.recommendation;
|
||||
delete record.skin_age_display;
|
||||
record.skin_age_display = 57;
|
||||
fs.writeFileSync(recordFile, JSON.stringify(record) + '\n');
|
||||
const storedBytes = fs.readFileSync(snapshotFile, 'utf8');
|
||||
const recordBytes = fs.readFileSync(recordFile, 'utf8');
|
||||
await page.click('[data-action="history"]');
|
||||
await page.click('[data-report-id]');
|
||||
await page.waitForSelector('#s-report.active');
|
||||
assert.equal(await page.locator('#stat-age b').textContent(), String(result.vm.skinAgeRaw), 'old saved display clamp must not override original skin age');
|
||||
assert.equal(await page.locator('#product-title').textContent(), ReportUI.products[1].title);
|
||||
assert.equal(await page.locator('#product-image').getAttribute('src'), ReportUI.products[1].image, 'refresh artwork crop, never the historical recommendation/copy');
|
||||
assert((await page.locator('.ditem-para').allTextContents()).includes(legacy.presentation.views[0].text));
|
||||
@@ -166,6 +168,7 @@ try {
|
||||
await desktop.locator('#tbl-records tbody tr').first().click();
|
||||
await desktop.waitForFunction(() => document.querySelector('#d-body').textContent.includes('历史报告原文'));
|
||||
assert((await desktop.locator('#d-body').textContent()).includes('旧版规则(历史记录)'));
|
||||
assert((await desktop.locator('#d-body').textContent()).includes('前端展示肌龄 ' + result.vm.skinAgeRaw + '岁'));
|
||||
assert.equal(fs.readFileSync(snapshotFile, 'utf8'), storedBytes);
|
||||
assert.equal(fs.readFileSync(recordFile, 'utf8'), recordBytes);
|
||||
// Missing skin age must not leave the previous product visible in the H5.
|
||||
@@ -181,6 +184,7 @@ try {
|
||||
await page.waitForSelector('#s-report.active');
|
||||
assert.equal(await page.locator('.card-product').isVisible(), false);
|
||||
assert.equal(await page.locator('#product-unavailable').isVisible(), true);
|
||||
assert.equal(await page.locator('#stat-age b').textContent(), '--');
|
||||
assert.deepEqual(errors, []);
|
||||
console.log('PASS: raw-age recommendation; admin/H5 scores, ages, product, seven-view copy, care, CSV, filters; saved/reloaded and legacy immutable snapshots; missing-age UI; owner isolation/private files/back');
|
||||
} finally {
|
||||
|
||||
@@ -72,6 +72,7 @@ try {
|
||||
await page.click('[data-action="login-confirm"]');
|
||||
assert.equal(await page.locator('#p-login-success.show').count(), 0);
|
||||
assert.equal(await page.locator('#s-splash').evaluate(e => e.inert), false);
|
||||
assert.equal(await page.locator('#s-splash [data-action="logout-wechat"]').isVisible(), true);
|
||||
}
|
||||
for (let device = 0; device < 2; device++) {
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true, userAgent: 'Mozilla/5.0 MicroMessenger/8.0', viewport: { width: 375, height: 726 } });
|
||||
@@ -165,6 +166,48 @@ try {
|
||||
assert.equal(await page.evaluate(() => AccountClient.user()), null);
|
||||
assert.equal(await page.locator('#p-login-success.show').count(), 0, 'session restore must not repeatedly open a success dialog');
|
||||
}
|
||||
if (device === 1) {
|
||||
const reportFile = path.join(root, 'accounts/reports', user.id, saved.id + '.json');
|
||||
const originalBytes = fs.readFileSync(reportFile, 'utf8');
|
||||
const logout = page.locator('#s-history [data-action="logout-wechat"]');
|
||||
assert.equal(await logout.textContent(), '退出登录');
|
||||
page.once('dialog', dialog => { assert(dialog.message().includes('历史报告将保留')); dialog.dismiss(); });
|
||||
await logout.click();
|
||||
assert.equal((await api(page, 'auth/session')).data.user.id, user.id, 'cancel leaves session intact');
|
||||
await page.route('**/api/auth/logout', route => route.fulfill({ status: 503, json: { ok: false, message: '退出测试失败' } }));
|
||||
page.once('dialog', dialog => dialog.accept()); await logout.click();
|
||||
await page.waitForFunction(() => document.querySelector('#toast').textContent.includes('退出失败'));
|
||||
assert.equal(await page.evaluate(() => AccountClient.user().id), user.id, 'failure does not fake local logout');
|
||||
assert.equal((await api(page, 'auth/session')).data.user.id, user.id);
|
||||
await page.unroute('**/api/auth/logout');
|
||||
await page.waitForFunction(() => !document.querySelector('#s-history [data-action="logout-wechat"]').disabled);
|
||||
let heldReport, markHeld;
|
||||
const reportHeld = new Promise(resolve => { markHeld = resolve; });
|
||||
await page.route('**/api/reports/' + saved.id, route => { heldReport = route; markHeld(); }, { times: 1 });
|
||||
await page.click('[data-report-id="RwechatCrossDevice"]'); await reportHeld;
|
||||
page.once('dialog', dialog => dialog.accept()); await logout.click();
|
||||
await page.waitForSelector('#s-login.active');
|
||||
assert.equal(await page.evaluate(() => AccountClient.user()), null);
|
||||
assert.equal((await api(page, 'auth/session')).data.user, null);
|
||||
assert.equal((await api(page, 'reports/' + saved.id)).status, 401);
|
||||
assert.equal(await page.locator('#login-consent').isChecked(), false);
|
||||
assert.equal(fs.readFileSync(reportFile, 'utf8'), originalBytes, 'logout retains exact historical file');
|
||||
await page.goBack(); await page.waitForSelector('#s-login.active');
|
||||
await page.click('[data-action="guest-experience"]'); await page.waitForSelector('#s-splash.active');
|
||||
await page.click('[data-action="history"]'); await page.waitForSelector('#s-history.active');
|
||||
await heldReport.fulfill({ json: { ok: true, report: JSON.parse(originalBytes) } });
|
||||
await page.waitForTimeout(250);
|
||||
assert.equal(await page.locator('#s-report.active').count(), 0, 'late WeChat report response cannot open under guest identity');
|
||||
await page.unroute('**/api/reports/' + saved.id);
|
||||
await page.click('[data-action="open-wechat-login"]');
|
||||
await page.check('#login-consent'); await page.click('[data-action="login-wechat"]');
|
||||
await page.waitForSelector('#s-splash.active'); await confirmLogin(page, 'wechat');
|
||||
assert.equal(await page.evaluate(() => AccountClient.user().id), user.id);
|
||||
await page.click('[data-action="history"]'); await page.waitForSelector('[data-report-id="RwechatCrossDevice"]');
|
||||
assert.deepEqual((await api(page, 'reports/' + saved.id)).data.report, JSON.parse(originalBytes));
|
||||
assert.equal(fs.readFileSync(reportFile, 'utf8'), originalBytes);
|
||||
console.log('PASS WeChat logout: confirm/cancel/failure, server session revoked, back guarded, re-login retains exact report');
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
await context.close();
|
||||
}
|
||||
|
||||
Vendored
+3
-1
@@ -1,4 +1,6 @@
|
||||
# mobile-select 1.4.0
|
||||
|
||||
来源:npm `mobile-select@1.4.0`(MIT,见同目录 `LICENSE`),取 `dist/mobile-select.iife.min.js` 与 `dist/style/mobile-select.css`,未改动。
|
||||
来源:npm `mobile-select@1.4.0`(MIT,见同目录 `LICENSE`),取 `dist/mobile-select.iife.min.js` 与 `dist/style/mobile-select.css`。
|
||||
|
||||
2026-10-13 本地兼容补丁:不再按 UA 名称判断触摸设备,改用 `navigator.maxTouchPoints > 0` 或 `ontouchstart` 能力;解决不带 Android 标识的 OpenHarmony/ArkWeb 被错误绑定鼠标事件的问题。事件坐标按 `touches/changedTouches` 读取,不依赖全局 `TouchEvent` 构造函数存在。上游版本和 MIT 声明保留,升级依赖时须保留此补丁并运行 `h5/test/harmony-picker.mjs` 回归。
|
||||
档案页的出生年月 / 性别 / 地区滚轮由它渲染,样式覆盖写在 `css/style.css` 的 mobile-select 段。
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user