feat: 巨子测肤 H5 原型(按 Figma 设计稿还原)
- design/: Figma 设计稿提取产物 — 设计规范文档、9 张整屏预览、 8 个 SVG 组件切图、15 张位图素材、完整节点数据(full.json) - h5/: 纯静态 H5 实现 — 启动页/隐私弹窗/档案填写(肤质轮播)/ 三连拍照/报告页,375px 基准还原,含压缩素材与 Playwright 验收截图
This commit is contained in:
+185
@@ -0,0 +1,185 @@
|
||||
/* ═════ 巨子测肤 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();
|
||||
})();
|
||||
Reference in New Issue
Block a user