Compare commits

...
2 Commits
Author SHA1 Message Date
Ubuntu 7155f23213 fix: 用户测试 agent 反馈(协议标题遮挡/气泡重叠/脚注技术文案/图层可读性)
- 隐私协议正文上留返回键位,标题不再被悬浮返回键遮挡
- 姿态提示气泡从底部改到步骤文案下方(原位置压住「取下眼镜」引导文字)
- 报告脚注只展示用户可感知的降级项(下颌线/侧脸/补拍类),引擎内部技术项(上限锁/字段未返回)不再面向用户泄漏
- 肤色/敏感图改为正脸×图层正片叠底(multiply):色沉与红区落在脸上,解决直挂「悬浮面具感/白底过淡」
- e2e 全量绿
2026-09-27 02:20:33 +08:00
Ubuntu d09d29946d feat: 真机反馈 13 项修改(下拉/拍照页/语音时机/预热/报告样式/翻转镜头)
1. 城市改自定义下拉(原生 select 各端样式不一)
2. 姿态提示浮层下移,不再压住脸部轮廓
3. 倒计时 321 前不再抢播「保持不动」(被切断听着像吞字)
4. 相机预热:分辨率稳定两拍后才出实时画面+放开检测,开头 1-2s 大脸闪变消除
5. 隐私协议页去掉头部文字条,返回键改悬浮
6. 肌龄小字常显「与实际年龄相差X岁」,钳制时追加「触发(原X→Y)」(vm 增 realAge 字段)
7. 删除设计稿没有的「6个维度观察/状态动态流动」两段文字
8. 维度卡关注行改设计稿两行式:灰小字+深色口号(稳定力口号用设计稿原文,另两条待确认)
9. Tab 黄色指示条宽度跟随文字(原固定 78px)
10. 抓拍只取取景框可见区域(object-fit cover 中心裁切),照片=所见画幅;拍照页顶栏黑条删除;无进展提示上移不再压快门
11. 相册选择器「拍照」项为系统行为,代码层已是图库意图(无 capture),详见回复
12. 切换镜头实现:前后置切换+镜像/姿态参数跟随,替换 Toast 占位
13. 报告分类卡去掉得分与档位徽章(设计稿只有话术),图片改 100px 见方圆角 10
e2e 全量绿(E0-E7)
2026-09-27 01:56:04 +08:00
8 changed files with 178 additions and 69 deletions
+64
View File
@@ -594,3 +594,67 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; }
.his-score b { font-size: 22px; font-weight: 500; color: var(--ink); }
.his-score span { font-size: 11px; color: var(--grey); margin-left: 2px; }
.his-del { flex: none; font-size: 11px; color: var(--grey-2); padding: 6px 4px; }
/* ═══════════ 第6轮:真机反馈 13 项 ═══════════ */
/* #2 姿态提示浮层:移到取景框下方(原 29% 与脸部轮廓/下巴重叠) */
.cam-hint { bottom: 21%; }
/* #10 无进展提示与快门重叠:上移 */
.cam-nudge { bottom: 16%; }
/* #4/#10 拍照页顶栏取消,返回键改悬浮白圆(对齐设计稿) */
.cam-back-float {
position: absolute; top: 12px; left: 12px; z-index: 3;
background: rgba(255,255,255,.9); border-color: transparent;
}
/* #12 预览镜像改为类切换(前置镜像/后置不镜像) */
#cam-video { transform: none; }
#cam-video.mirror { transform: scaleX(-1); }
/* #1 城市自定义下拉(替代原生 select) */
.rs { position: relative; }
.rs-current {
width: 100%; display: flex; align-items: center; justify-content: space-between;
background: #fff; border: 1px solid var(--line); border-radius: 8px;
padding: 12px 14px; font-size: 13px; color: var(--ink); text-align: left;
}
.rs-current .chev { transition: transform .2s; }
.rs.open .rs-current .chev { transform: rotate(225deg); }
.rs-value.rs-placeholder { color: #B3B3B3; }
.rs-list {
position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 30;
max-height: 264px; overflow-y: auto; -webkit-overflow-scrolling: touch;
background: #fff; border: 1px solid var(--line); border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,.08);
}
.rs-list li { padding: 12px 14px; font-size: 13px; color: var(--ink); }
.rs-list li + li { border-top: 1px solid #F3F3F3; }
.rs-list li:active { background: #F7F7F7; }
/* #5 隐私页去头部条,返回键悬浮 */
.policy-float-back {
position: absolute; top: 12px; left: 12px; z-index: 2;
background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.12);
}
.overlay-policy .policy-page { position: relative; }
/* #8 维度卡关注行:灰小字 + 深色口号(对齐设计稿) */
.dims-note { margin-top: 12px; }
.dims-note-l1 { font-size: 10px; font-weight: 400; color: var(--grey); }
.dims-note-l2 { font-size: 11.5px; font-weight: 500; color: var(--ink); line-height: 1.8; }
/* #9 Tab 黄色指示条:宽度跟随文字(原固定 78px 过宽) */
.dtab { padding: 8px 0 0; }
.dtab.active::after { content: none; }
.dtab-tx { position: relative; display: inline-block; padding: 0 5px 9px; }
.dtab.active .dtab-tx::after {
content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
height: 7px; border-radius: 3.5px; background: var(--yellow);
}
/* #13 分类卡图片对齐设计稿(约 100px 见方、圆角 10) */
.ditem-img { width: 100px; height: 100px; border-radius: 10px; }
/* ═══════════ 第7轮:用户测试 agent 反馈 ═══════════ */
/* P1-1 隐私页标题被悬浮返回键遮挡:正文上留返回键位 */
#p-policy .policy-body { padding-top: 52px; }
/* P1-2 姿态提示气泡压住底部引导文字:改到步骤文案下方、取景框上方 */
.cam-hint { bottom: auto; top: 26%; }
+12 -25
View File
@@ -58,12 +58,14 @@
<div class="field">
<label class="field-label">您的所在地区</label>
<div class="field-select-wrap">
<select id="f-region" class="field-input field-select">
<option value="" selected disabled>请选择</option>
</select>
<i class="chev down"></i>
<div class="rs" id="region-rs">
<button type="button" class="rs-current" id="rs-current">
<span id="rs-value" class="rs-value rs-placeholder">请选择</span>
<i class="chev down"></i>
</button>
<ul class="rs-list" id="rs-list" hidden></ul>
</div>
<input type="hidden" id="f-region">
</div>
<div class="field">
@@ -88,10 +90,7 @@
<!-- ═══════════ 05-07 拍照页 ═══════════ -->
<section class="screen" id="s-cam">
<div class="cam-bg" id="cam-bg"></div>
<header class="nav nav-dark">
<button class="nav-back" data-action="cam-back" aria-label="返回"><i class="chev left"></i></button>
<span class="nav-title">1'me在线肌肤测试</span>
</header>
<button class="nav-back nav-dark cam-back-float" data-action="cam-back" aria-label="返回"><i class="chev left"></i></button>
<div class="cam-dots" id="cam-dots"><i></i><i></i><i></i></div>
<p class="cam-step" id="cam-step">1/3<br>请单人正脸出镜</p>
<!-- 取景蒙版:SVG mask 镂空压暗 + 轮廓线框;data-step 切换三种轮廓
@@ -158,9 +157,6 @@
</div>
</div>
<p class="dims-intro">我们从6个维度观察了你的肌肤,当下优先关注:紧致、光泽、纹路、耐受、细腻、水润</p>
<p class="dims-disclaimer">*状态是动态流动的,和作息、压力、季节都有关系,仅代表此刻的状态参考。</p>
<div class="card card-dims">
<div class="dims-row">
<div class="dim"><span class="dim-name">紧塑力</span><span class="dim-score" id="dim-firm">-.-</span><span class="dim-lv" id="dim-firm-lv"></span></div>
@@ -172,9 +168,9 @@
<div class="card card-detail">
<div class="detail-tabs" id="detail-tabs">
<button class="dtab active" data-tab="0">紧塑力</button>
<button class="dtab" data-tab="1">活力感</button>
<button class="dtab" data-tab="2">稳定力</button>
<button class="dtab active" data-tab="0"><span class="dtab-tx">紧塑力</span></button>
<button class="dtab" data-tab="1"><span class="dtab-tx">活力感</span></button>
<button class="dtab" data-tab="2"><span class="dtab-tx">稳定力</span></button>
</div>
<div class="detail-panels">
<div class="dpanel active" data-panel="0">
@@ -182,7 +178,6 @@
<h3 class="ditem-title">轮廓</h3>
<img id="ditem-img1" src="assets/img/det-1.jpg" loading="lazy" class="ditem-img" alt="轮廓检测图">
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-contour">-.-</span><i class="cat-badge" id="lv-contour"></i></p>
<p class="ditem-para" id="ditem-para1">检测完成后显示分析结果。</p>
</div>
</div>
@@ -190,7 +185,6 @@
<h3 class="ditem-title">皱纹</h3>
<img id="ditem-img2" src="assets/img/det-2.jpg" loading="lazy" class="ditem-img" alt="皱纹检测图">
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-wrinkles">-.-</span><i class="cat-badge" id="lv-wrinkles"></i></p>
<p class="ditem-para" id="ditem-para2">检测完成后显示分析结果。</p>
</div>
</div>
@@ -200,7 +194,6 @@
<h3 class="ditem-title">光滑</h3>
<img id="viz-smooth" loading="lazy" class="ditem-img" alt="光滑检测标记图" hidden>
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-smooth">-.-</span><i class="cat-badge" id="lv-smooth"></i></p>
<p class="ditem-para" id="tx-smooth">检测完成后显示分析结果。</p>
</div>
</div>
@@ -208,7 +201,6 @@
<h3 class="ditem-title">肤色水润</h3>
<img id="viz-tone" loading="lazy" class="ditem-img" alt="肤色色沉图" hidden>
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-tone">-.-</span><i class="cat-badge" id="lv-tone"></i></p>
<p class="ditem-para" id="tx-tone">检测完成后显示分析结果。</p>
</div>
</div>
@@ -218,7 +210,6 @@
<h3 class="ditem-title">敏感</h3>
<img id="viz-sens" loading="lazy" class="ditem-img" alt="敏感红区图" hidden>
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-sens">-.-</span><i class="cat-badge" id="lv-sens"></i></p>
<p class="ditem-para" id="tx-sens">检测完成后显示分析结果。</p>
</div>
</div>
@@ -226,7 +217,6 @@
<h3 class="ditem-title">痘痘</h3>
<img id="viz-acne" loading="lazy" class="ditem-img" alt="痘痘检测标记图" hidden>
<div class="ditem-text">
<p class="cat-head"><span class="cat-s" id="s-acne">-.-</span><i class="cat-badge" id="lv-acne"></i></p>
<p class="ditem-para" id="tx-acne">检测完成后显示分析结果。</p>
</div>
</div>
@@ -304,10 +294,7 @@
<!-- ═══════════ 弹层:隐私政策与服务协议(全文) ═══════════ -->
<div class="overlay overlay-policy" id="p-policy">
<div class="policy-page">
<header class="policy-header">
<button class="nav-back" data-action="close-policy" aria-label="返回"><i class="chev left"></i></button>
<span class="policy-header-title">隐私政策与服务协议</span>
</header>
<button class="nav-back policy-float-back" data-action="close-policy" aria-label="返回"><i class="chev left"></i></button>
<div class="policy-body">
<h1 class="policy-title">Face⁺⁺个人信息与隐私保护政策</h1>
+101 -44
View File
@@ -142,14 +142,27 @@
if (drag.suppress) { e.preventDefault(); e.stopPropagation(); drag.suppress = false; }
}, true);
/* ─── 地区选项 ─── */
/* ─── 地区选项(自定义下拉:原生 select 各端样式不一,改自绘弹层) ─── */
var REGIONS = ['北京', '上海', '广州', '深圳', '杭州', '成都', '重庆', '武汉', '西安', '南京', '天津', '苏州', '长沙', '郑州', '青岛', '其他'];
var regionSel = $('#f-region');
REGIONS.forEach(function (r) {
var o = document.createElement('option');
o.textContent = r;
regionSel.appendChild(o);
var regionSel = $('#f-region'); // hidden input,存选中值
var rsBox = $('#region-rs'), rsList = $('#rs-list'), rsValue = $('#rs-value');
rsList.innerHTML = REGIONS.map(function (r) { return '<li data-v="' + r + '">' + r + '</li>'; }).join('');
function rsClose() { rsList.hidden = true; rsBox.classList.remove('open'); }
$('#rs-current').addEventListener('click', function (e) {
e.stopPropagation();
var willOpen = rsList.hidden;
rsList.hidden = !willOpen;
rsBox.classList.toggle('open', willOpen);
});
rsList.addEventListener('click', function (e) {
var li = e.target.closest('li[data-v]');
if (!li) return;
regionSel.value = li.dataset.v;
rsValue.textContent = li.dataset.v;
rsValue.classList.remove('rs-placeholder');
rsClose();
});
document.addEventListener('click', rsClose); // 点空白收起
/* ─── 实时相机(getUserMedia)+ 系统相机降级 ─── */
var IS_MOCK = /[?&]mock=1/.test(location.search); // mock:不加载 face-api、不跑检测,analyze 直返内置样本
@@ -158,9 +171,11 @@
var photos = []; // 三连拍(dataURL),与 CAM_STEPS 同序:[0]正脸 [1]右脸(头左转) [2]左脸(头右转)
// 约束逐级放宽:facingMode 裸值在部分 Android 抛 OverconstrainedError,先 ideal 再退 video:true,最后才降级
var camFacing = 'user'; // 当前镜头:'user' 前置 | 'environment' 后置(翻转按钮切换)
var camMirrored = true; // 预览是否镜像(前置镜像,后置不镜像)
function getCamStream() {
return navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: 'user' }, width: { ideal: 1280 }, height: { ideal: 1280 } },
video: { facingMode: { ideal: camFacing }, width: { ideal: 1280 }, height: { ideal: 1280 } },
audio: false
}).catch(function (err) {
if (err && err.name === 'OverconstrainedError') {
@@ -169,6 +184,18 @@
throw err;
});
}
// 相机预热:部分设备开头 1~2s 以低分辨率出画(画面显脸巨大),等分辨率稳定两拍再放开取景与检测
function waitVideoStable(done) {
var lastW = 0, stableN = 0, tries = 0;
var t = setInterval(function () {
var w = camVideo.videoWidth;
tries++;
if (w && w === lastW) {
if (++stableN >= 2) { clearInterval(t); done(); }
} else { stableN = 0; lastW = w; }
if (tries > 40) { clearInterval(t); done(); } // 12s 兜底,异常也不卡死
}, 300);
}
function startCamera() {
if (camStream) { stopCamera(); }
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { enableFallback(); return; }
@@ -178,14 +205,18 @@
if (token !== camSeqCam) { stream.getTracks().forEach(function (t) { t.stop(); }); return; } // 期间已退出拍照页:立即释放
camStream = stream;
camVideo.srcObject = stream;
camLive = true;
camVideo.classList.toggle('mirror', camMirrored);
// iOS 来电/切后台等系统打断会结束 track:挂 ended 监听走自动重连
stream.getTracks().forEach(function (t) {
t.onended = function () { onCamTrackEnded(token); };
});
$('#s-cam').classList.add('cam-live');
beginDetect(); // 确认相机可用后才加载检测模型(无相机权限设备不白加载)
maybeStartDetect(); // 模型已就绪时即刻开跑检测循环
waitVideoStable(function () { // 预热完成前不出实时画面(静态示意底图还在),避免开头"大脸"闪变
if (token !== camSeqCam) return;
camLive = true;
$('#s-cam').classList.add('cam-live');
beginDetect(); // 确认相机可用后才加载检测模型(无相机权限设备不白加载)
maybeStartDetect(); // 模型已就绪时即刻开跑检测循环
});
}).catch(function () {
if (token !== camSeqCam) return;
enableFallback();
@@ -218,13 +249,18 @@
function captureFromVideo() {
var vw = camVideo.videoWidth, vh = camVideo.videoHeight;
if (!vw || !vh) return null;
var scale = Math.min(1, 1280 / Math.max(vw, vh));
var cw = Math.round(vw * scale), ch = Math.round(vh * scale);
// 只取取景框可见区域(object-fit:cover 的中心裁切):照片 = 用户当时看到的画面
var box = camVideo.getBoundingClientRect();
var scaleFit = Math.max(box.width / vw, box.height / vh);
var visW = Math.min(vw, box.width / scaleFit), visH = Math.min(vh, box.height / scaleFit);
var sx = (vw - visW) / 2, sy = (vh - visH) / 2;
var scale = Math.min(1, 1280 / Math.max(visW, visH));
var cw = Math.round(visW * scale), ch = Math.round(visH * scale);
var canvas = document.createElement('canvas');
canvas.width = cw; canvas.height = ch;
var ctx = canvas.getContext('2d');
ctx.translate(cw, 0); ctx.scale(-1, 1); // 镜像
ctx.drawImage(camVideo, 0, 0, cw, ch);
if (camMirrored) { ctx.translate(cw, 0); ctx.scale(-1, 1); } // 前置镜像,与预览一致
ctx.drawImage(camVideo, sx, sy, visW, visH, 0, 0, cw, ch);
return canvas.toDataURL('image/jpeg', 0.88);
}
// 文件 → 统一压成最长边 1280 的 JPEG → 推进流程(降级系统相机与相册选图共用)
@@ -343,12 +379,12 @@
m.classList.toggle('no-face', cls === 'no-face');
m.classList.toggle('ok', cls === 'ok');
}
function setHint(text, ok) {
function setHint(text, ok, silent) {
var h = $('#cam-hint');
if (!text) { h.classList.remove('show', 'ok'); lastHint = ''; return; }
if (text !== lastHint) {
h.textContent = text; // 同文案不重复触发布局
voice.play(VOICE.hint[text]); // 语音同步(模块内 4s 去重)
if (!silent) voice.play(VOICE.hint[text]); // 语音同步(模块内 4s 去重)
}
h.classList.add('show');
h.classList.toggle('ok', !!ok);
@@ -388,7 +424,7 @@
c.textContent = cdN;
c.classList.add('show');
setMask('ok'); // 轮廓保持绿色
setHint('保持不动', true);
setHint('保持不动', true, /* silent= */true); // 倒计时前不再抢播「保持不动」(会被 321 立刻切断,听着像吞字)
voice.play(VOICE.countdown); // 「三、二、一」与数字同步起播
cdTimer = setTimeout(cdTick, 1000);
}
@@ -456,8 +492,8 @@
if (ratio < FACE_NEAR) hint = '请靠近一点';
else if (ratio > FACE_FAR) hint = '请远一点';
else {
// 前置预览为镜像画面 → mirrored=true;距离不合格时角度不判 ok
var pose = HeadPose.check(d.landmarks.positions, CAM_STEPS[camStep].target, true);
// 镜像标志跟随镜头(前置 true/后置 false);距离不合格时角度不判 ok
var pose = HeadPose.check(d.landmarks.positions, CAM_STEPS[camStep].target, camMirrored);
hint = pose.hint;
ok = pose.ok;
}
@@ -680,19 +716,8 @@
function setCat(key, cat) {
var dom = CAT_DOM[key];
var s = $(dom.s), lv = $(dom.lv);
if (cat.P == null) { // 缺数据分类:显示说明,不显示分数
s.textContent = '本次未检出有效数据';
s.className = 'cat-s cat-na';
lv.style.display = 'none';
} else {
s.textContent = cat.S.toFixed(1);
s.className = 'cat-s';
lv.style.display = '';
lv.textContent = cat.level;
lv.className = 'cat-badge ' + (LV_CLASS[cat.level] || '');
}
$(dom.tx).textContent = cat.text;
// 设计稿分类卡只有话术正文:不展示 S 与档位徽章;缺数据分类以说明代正文
$(dom.tx).textContent = cat.P == null ? '本次未检出有效数据' : cat.text;
}
/* ─── 报告页检测可视化(face_maps 图层 + marks 坐标叠加,数据驱动) ─── */
@@ -706,8 +731,8 @@
{ key: 'mole', color: '#282828' }, // 痣
{ key: 'acne', color: '#D30C0C' } // 痤疮
] },
tone: { img: '#viz-tone', map: 'brown_area' }, // 肤色:色沉图
sens: { img: '#viz-sens', map: 'red_area' }, // 敏感:红区图
tone: { img: '#viz-tone', blendMap: 'brown_area' }, // 肤色:正脸×色沉图正片叠底(直挂是悬浮面具感,合成后色沉落在脸上)
sens: { img: '#viz-sens', blendMap: 'red_area' }, // 敏感:正脸×红区图正片叠底(直挂白底过淡不可读)
acne: { img: '#viz-acne', photo: true, marks: [ // 痘痘:正脸+闭口粉刺/痤疮标记
{ key: 'closed_comedones', color: '#E8A13D' }, // 闭口粉刺
{ key: 'acne', color: '#D30C0C' }, // 痤疮
@@ -793,7 +818,11 @@
var ctx = cv.getContext('2d');
ctx.drawImage(photo, 0, 0, w, h);
if (opts && opts.dim) { ctx.fillStyle = 'rgba(0,0,0,' + opts.dim + ')'; ctx.fillRect(0, 0, w, h); } // 压暗底色让增强线可读
if (overlay) ctx.drawImage(overlay, 0, 0, w, h);
if (overlay) {
if (opts && opts.blend) ctx.globalCompositeOperation = opts.blend; // 正片叠底:白底图层(红区/色沉)叠到脸上
ctx.drawImage(overlay, 0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
}
var scaled = vizScaleMarks(marks, w / photo.naturalWidth);
var lw = Math.max(3, Math.round(w / 200)); // 线宽随图走,卡片缩略后仍可见
var minBox = Math.round(w / 36); // 过小标记放大到最小框,保证缩略可见
@@ -836,11 +865,18 @@
var cfg = VIZ_CAT[key];
var imgEl = $(cfg.img);
if (!imgEl) continue;
if (cfg.map) { // 整幅图层(色沉图/红区图):图层缺失 → 保留占位
if (cfg.map) { // 整幅图层:图层缺失 → 保留占位
var u = vizMapURL(maps[cfg.map]);
if (u) { imgEl.src = u; imgEl.hidden = false; }
continue;
}
if (cfg.blendMap) { // 图层×正脸正片叠底(色沉/红区):无照片时直挂图层;图层缺失 → 保留占位
var bu = vizMapURL(maps[cfg.blendMap]);
if (!bu) continue;
if (!frontPhoto) { imgEl.src = bu; imgEl.hidden = false; continue; }
vizCompose(frontPhoto, bu, [], imgEl, { blend: 'multiply' });
continue;
}
if (cfg.overlay) { // 透明增强图层(皱纹):有照片则合成(可按 cfg.dim 压暗),无照片单挂图层;图层缺失 → 保留占位
var ov = vizMapURL(maps[cfg.overlay]);
if (!ov) continue;
@@ -866,15 +902,28 @@
}
$('#stat-score').textContent = total != null ? total + '分' : '--分';
$('#stat-age').textContent = vm.skinAgeDisplay != null ? vm.skinAgeDisplay + '岁' : '--岁';
$('#stat-age-note').textContent = vm.ageClampNote || ''; // 肤龄钳制留痕(小字灰)
// 肌龄小字:常显「与实际年龄相差X岁」,触发钳制时追加留痕(契约格式「触发(原X→Y)」不变)
var ageNote = '';
if (vm.skinAgeDisplay != null && vm.realAge != null) {
var ageDiff = Math.abs(vm.skinAgeDisplay - vm.realAge);
ageNote = ageDiff === 0 ? '与实际年龄相符' : '与实际年龄相差' + ageDiff + '岁';
}
if (vm.ageClampNote) ageNote += (ageNote ? ' · ' : '') + vm.ageClampNote;
$('#stat-age-note').textContent = ageNote;
// 三维度:S 值(1 位小数) + 档位(良好绿/改善琥珀/预警红)
setDim('#dim-firm', '#dim-firm-lv', vm.dims.firm);
setDim('#dim-vita', '#dim-vita-lv', vm.dims.vita);
setDim('#dim-stab', '#dim-stab-lv', vm.dims.stab);
// 关注行(设计稿两行:灰小字「近期优先关注:X」+ 深色维度口号)
var DIM_SLOGAN = {
'紧塑力': '紧塑提拉力,轮廓上扬不松垮',
'活力感': '活力透亮感,水润澎弹有光泽',
'稳定力': '强韧稳态力,锁水保湿抵御刺激' // 设计稿原文
};
$('#dims-note').innerHTML = vm.focus.name
? '近期优先关注:' + esc(vm.focus.name) + '<br>' + esc(vm.focus.tip)
: '近期优先关注:<br>---';
? '<span class="dims-note-l1">近期优先关注:' + esc(vm.focus.name) + '</span><br><span class="dims-note-l2">' + esc(DIM_SLOGAN[vm.focus.name] || '') + '</span>'
: '<span class="dims-note-l1">近期优先关注:---</span>';
// 三个 Tab 面板:各所辖两个分类(S + 档位徽章 + 话术正文)
var keys = ['contour', 'wrinkles', 'smooth', 'tone', 'sens', 'acne'];
@@ -883,10 +932,12 @@
// 检测可视化:轮廓=正脸实拍,其余按 VIZ_CAT 取 face_maps 图层或叠加 marks(payload 缺失时各卡保留占位)
renderViz(payload, frontPhoto);
// 降级标注(下颌线未参与评分等,非空才展示)
// 降级标注:只展示用户可感知/可执行的(下颌线、侧脸、补拍类);
// 引擎内部技术项(上限锁未执行/字段未返回等)不面向用户展示
var notesEl = $('#vm-notes');
if (vm.notes && vm.notes.length) {
notesEl.innerHTML = vm.notes.map(function (t) { return '*' + esc(t); }).join('<br>');
var userNotes = (vm.notes || []).filter(function (t) { return /下颌线|侧脸|补拍|重拍/.test(t); });
if (userNotes.length) {
notesEl.innerHTML = userNotes.map(function (t) { return '*' + esc(t); }).join('<br>');
notesEl.style.display = '';
} else {
notesEl.style.display = 'none';
@@ -990,7 +1041,13 @@
shutterLock = true; // 与降级快门同策略:前置上锁,change/cancel 解锁
albumFile.click();
},
'flip': function () { toast('切换镜头功能开发中'); },
'flip': function () { // 切换前后镜头
if (camFallback) { toast('系统相机模式下请在系统相机内切换'); return; }
if (!camStream) { toast('相机启动中,请稍候'); return; }
camFacing = camFacing === 'user' ? 'environment' : 'user';
camMirrored = camFacing === 'user';
startCamera(); // 复用启停与预热(camSeqCam 防竞态)
},
'back-profile': function () { showScreen('s-profile'); },
'history': function () { openHistory(); },
'close-history': function () { closeOverlay('p-history'); },
+1
View File
@@ -722,6 +722,7 @@
skinAgeRaw: skinAge,
skinAgeDisplay: skinAgeDisplay,
ageClampNote: ageClampNote,
realAge: realAge,
dims: dims,
cats: cats,
group: group,
Binary file not shown.

After

Width:  |  Height:  |  Size: 926 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB