Files
juzi/h5/js/app.js
T
xsl e4e85d2805 feat: 巨子测肤 H5 原型(按 Figma 设计稿还原)
- design/: Figma 设计稿提取产物 — 设计规范文档、9 张整屏预览、
  8 个 SVG 组件切图、15 张位图素材、完整节点数据(full.json)
- h5/: 纯静态 H5 实现 — 启动页/隐私弹窗/档案填写(肤质轮播)/
  三连拍照/报告页,375px 基准还原,含压缩素材与 Playwright 验收截图
2026-09-21 23:25:01 +08:00

186 lines
7.0 KiB
JavaScript

/* ═════ 巨子测肤 H5 — 交互逻辑 ═════ */
(function () {
'use strict';
var $ = function (sel, root) { return (root || document).querySelector(sel); };
/* ─── Toast ─── */
var toastEl = $('#toast'), toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove('show'); }, 1800);
}
/* ─── 屏幕管理 ─── */
var currentScreen = 's-splash';
function showScreen(id) {
document.querySelectorAll('.screen').forEach(function (s) {
s.classList.toggle('active', s.id === id);
});
var sc = document.getElementById(id);
sc.scrollTop = 0;
currentScreen = id;
}
/* ─── 弹窗管理 ─── */
function openOverlay(id) { $('#' + id).classList.add('show'); }
function closeOverlay(id) { $('#' + id).classList.remove('show'); }
/* ─── 肤质轮播 ─── */
var SKINS = [
{ img: 'assets/img/skin-0-dry.png', name: '干性肌肤', desc: '全脸干燥,容易紧绷、缺乏光泽' },
{ img: 'assets/img/skin-1-mixeddry.png', name: '混合偏干肌肤', desc: '两颊偏干,T区油脂较少或偶尔出油' },
{ img: 'assets/img/skin-2-neutral.png', name: '中性肌肤', desc: '水油平衡,肤感舒适,无明显干燥出油' },
{ img: 'assets/img/skin-3-oily.png', name: '油性肌肤', desc: '全脸易出油,容易泛油光、毛孔明显' },
{ img: 'assets/img/skin-4-mixedoily.png', name: '混合偏油肌肤', desc: 'T区易出油,两颊相对清爽或轻微干燥' }
];
var skinIndex = 0;
var track = $('#skin-track'), dotsBox = $('#skin-dots');
SKINS.forEach(function (s, i) {
var item = document.createElement('div');
item.className = 'skin-item';
item.innerHTML =
'<img src="' + s.img + '" alt="' + s.name + '">' +
'<span class="skin-name">' + s.name + '</span>' +
'<p class="skin-desc">' + s.desc + '</p>';
track.appendChild(item);
var dot = document.createElement('i');
if (i === 0) dot.className = 'on';
dot.addEventListener('click', function () { goSkin(i); });
dotsBox.appendChild(dot);
});
function goSkin(i) {
skinIndex = Math.max(0, Math.min(SKINS.length - 1, i));
track.style.transform = 'translateX(-' + skinIndex * 100 + '%)';
dotsBox.querySelectorAll('i').forEach(function (d, k) {
d.classList.toggle('on', k === skinIndex);
});
}
// 触摸滑动
var touchX = null;
$('#skin-carousel').addEventListener('touchstart', function (e) {
touchX = e.touches[0].clientX;
}, { passive: true });
$('#skin-carousel').addEventListener('touchend', function (e) {
if (touchX === null) return;
var dx = e.changedTouches[0].clientX - touchX;
if (Math.abs(dx) > 30) goSkin(skinIndex + (dx < 0 ? 1 : -1));
touchX = null;
}, { passive: true });
/* ─── 地区选项 ─── */
var REGIONS = ['北京', '上海', '广州', '深圳', '杭州', '成都', '重庆', '武汉', '西安', '南京', '天津', '苏州', '长沙', '郑州', '青岛', '其他'];
var regionSel = $('#f-region');
REGIONS.forEach(function (r) {
var o = document.createElement('option');
o.textContent = r;
regionSel.appendChild(o);
});
/* ─── 拍照流程 ─── */
var CAM_STEPS = [
{ bg: 'assets/img/cam-front.jpg', step: '1/3<br>请单人正脸出镜', guides: ['正脸居中', '取下眼镜', '光线充足'] },
{ bg: 'assets/img/cam-left.jpg', step: '2/3<br>请单人右脸出镜', guides: ['右脸居中', '取下眼镜', '光线充足'] },
{ bg: 'assets/img/cam-right.jpg', step: '3/3<br>请单人左脸出镜', guides: ['左脸居中', '取下眼镜', '光线充足'] }
];
var camStep = 0;
function renderCamStep() {
var cfg = CAM_STEPS[camStep];
$('#cam-bg').style.backgroundImage = 'url(' + cfg.bg + ')';
$('#cam-step').innerHTML = cfg.step;
$('#cam-guide').innerHTML = cfg.guides
.map(function (g) { return '<span>' + g + '</span>'; })
.join('<i></i>');
}
function shutter() {
var flash = $('#cam-flash');
flash.classList.add('on');
setTimeout(function () { flash.classList.remove('on'); }, 220);
setTimeout(function () {
if (camStep < CAM_STEPS.length - 1) {
camStep++;
renderCamStep();
} else {
camStep = 0;
renderCamStep();
openOverlay('p-analyzing');
setTimeout(function () {
closeOverlay('p-analyzing');
showScreen('s-report');
}, 1600);
}
}, 260);
}
/* ─── 隐私勾选 ─── */
var agreed = false;
$('#privacy-row').addEventListener('click', function (e) {
e.preventDefault();
agreed = !agreed;
$('#privacy-check').classList.toggle('checked', agreed);
});
/* ─── 报告页 Tab ─── */
$('#detail-tabs').addEventListener('click', function (e) {
var btn = e.target.closest('.dtab');
if (!btn) return;
var idx = btn.dataset.tab;
document.querySelectorAll('.dtab').forEach(function (t) { t.classList.toggle('active', t === btn); });
document.querySelectorAll('.dpanel').forEach(function (p) {
p.classList.toggle('active', p.dataset.panel === idx);
});
});
/* ─── 动作分发 ─── */
var actions = {
'open-privacy': function () { openOverlay('p-privacy'); },
'close-privacy': function () { closeOverlay('p-privacy'); },
'agree-continue': function () {
if (!agreed) { toast('请先阅读并同意隐私协议'); return; }
closeOverlay('p-privacy');
showScreen('s-profile');
},
'back-splash': function () { showScreen('s-splash'); },
'submit-profile': function () {
var age = $('#f-age').value.trim();
if (!age || !/^\d{1,2}$/.test(age) || +age < 1 || +age > 99) { toast('请输入有效年龄'); return; }
if (!$('#f-gender').value) { toast('请选择性别'); return; }
if (!regionSel.value) { toast('请选择所在地区'); return; }
openOverlay('p-photoguide');
},
'close-photoguide': function () { closeOverlay('p-photoguide'); },
'start-camera': function () {
closeOverlay('p-photoguide');
camStep = 0;
renderCamStep();
showScreen('s-cam');
},
'cam-back': function () {
if (camStep > 0) { camStep--; renderCamStep(); }
else { showScreen('s-profile'); }
},
'shutter': shutter,
'album': function () { toast('相册功能开发中'); },
'flip': function () { toast('切换镜头功能开发中'); },
'back-profile': function () { showScreen('s-profile'); },
'more': function () { toast('了解更多功能开发中'); }
};
document.addEventListener('click', function (e) {
var el = e.target.closest('[data-action]');
if (!el) return;
var fn = actions[el.dataset.action];
if (fn) fn();
});
/* ─── 初始化 ─── */
renderCamStep();
})();