feat: 完善淘宝小程序拍照、报告与登录流程
接入本地实时识别和自动三连拍,调整报告裁切、商品跳转、分享标题及居中页眉;完善游客会话、后台渠道隔离、部署脚本与回归测试。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* ═════ 05-07 拍照页 ═════
|
||||
对齐 H5 h5/css/style.css 的 #s-cam 静态态:
|
||||
.cam-step top 7.1%,21.5px/500,letter-spacing 1u,text-shadow
|
||||
.cam-step top 7.1%,21.5px/700,letter-spacing 1u,text-shadow
|
||||
.cam-guide bottom 22.4%,14.5px,分隔线 15u 高
|
||||
.cam-controls bottom 5.2%,高 62u,内边距 56u,快门 62u
|
||||
单位换算:calc(N * var(--u)) → 2N rpx */
|
||||
@@ -14,7 +14,7 @@
|
||||
.cam-bg {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
background: #262626;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ─── 顶部步骤文案 ─── */
|
||||
@@ -25,7 +25,7 @@
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 21.5px;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 2rpx;
|
||||
text-shadow: 0 1px 10px rgba(0, 0, 0, .4);
|
||||
@@ -43,26 +43,13 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* ─── 取景蒙版:H5 用 SVG 镂空出人脸轮廓;这里用「椭圆开窗 + 巨大 box-shadow」实现同样的暗框 + 中间透亮 ─── */
|
||||
/* H5 原始轮廓生成的透明 PNG;额外边缘遮罩保持不同比例视口的暗框。 */
|
||||
.cam-mask {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cam-hole {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 440rpx;
|
||||
height: 580rpx;
|
||||
margin-left: -220rpx;
|
||||
margin-top: -290rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 3000rpx rgba(0, 0, 0, .82);
|
||||
}
|
||||
.mask-1 { transform: translateX(-26rpx) rotate(-4deg); }
|
||||
.mask-2 { transform: translateX(26rpx) rotate(4deg); }
|
||||
|
||||
/* ─── 底部引导条 ─── */
|
||||
.cam-guide {
|
||||
@@ -101,7 +88,7 @@
|
||||
z-index: 2;
|
||||
}
|
||||
.cam-side {
|
||||
width: 120rpx;
|
||||
width: 60rpx;
|
||||
height: 124rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -113,7 +100,7 @@
|
||||
width: 124rpx;
|
||||
height: 124rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #fff;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -126,6 +113,36 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cam-side image { width:60rpx; height:60rpx; }
|
||||
.cam-side-switch { position:relative; }
|
||||
.cam-switch-label { position:absolute; top:calc(50% + 34rpx); left:50%; transform:translateX(-50%); white-space:nowrap; font-size:10px; line-height:1.3; }
|
||||
.cam-verifying { position:absolute; top:0; right:0; bottom:0; left:0; z-index:100; display:flex; flex-direction:column; align-items:center; justify-content:center; background:#101823; color:#fff; font-size:14px; gap:32rpx; }
|
||||
.cam-shutter-auto { visibility:hidden; }
|
||||
.cam-live-hint { position:absolute; bottom:27%; left:24rpx; right:24rpx; text-align:center; color:#fff; font-size:17px; font-weight:500; z-index:3; }
|
||||
.cam-live-hint.good { color:#34C759; }
|
||||
.cam-shutter image { width:124rpx; height:124rpx; }
|
||||
.cam-mask-image { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); }
|
||||
.cam-mask-image.mirror { transform:translate(-50%,-50%) scaleX(-1); }
|
||||
.cam-live-scan { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); overflow:hidden; -webkit-mask-size:100% 100%; mask-size:100% 100%; }
|
||||
.cam-live-scan.mirror { transform:translate(-50%,-50%) scaleX(-1); }
|
||||
.scan-front { -webkit-mask-image:url('../../assets/camera/hole-front.png'); mask-image:url('../../assets/camera/hole-front.png'); }
|
||||
.scan-right { -webkit-mask-image:url('../../assets/camera/hole-right.png'); mask-image:url('../../assets/camera/hole-right.png'); }
|
||||
.scan-left { -webkit-mask-image:url('../../assets/camera/hole-left.png'); mask-image:url('../../assets/camera/hole-left.png'); }
|
||||
.cam-live-band { position:absolute; top:10%; left:0; right:0; height:12%; background:linear-gradient(to bottom,rgba(245,240,120,0),rgba(245,240,120,.32),rgba(245,240,120,.95)); animation:camera-scan 2.2s ease-in-out infinite; }
|
||||
@keyframes camera-scan { from {transform:translateY(0);} to {transform:translateY(440%);} }
|
||||
.cam-mask-edge { position:absolute; background:rgba(0,0,0,.85); }
|
||||
.edge-top { top:0; left:0; right:0; } .edge-bottom { bottom:0; left:0; right:0; }
|
||||
.edge-left { left:0; top:0; bottom:0; } .edge-right { right:0; top:0; bottom:0; }
|
||||
.cam-next { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:460rpx; box-sizing:content-box; padding:44rpx 32rpx 40rpx; border-radius:24rpx; background:rgba(0,0,0,.72); color:#fff; text-align:center; z-index:5; }
|
||||
.cam-next-ok { margin:0 auto 24rpx; border-radius:50%; width:80rpx; height:80rpx; line-height:80rpx; background:#34C759; font-size:26px; }
|
||||
.cam-next-done { font-size:17px; font-weight:500; }
|
||||
.cam-next-tip { margin-top:16rpx; white-space:pre-line; font-size:14px; line-height:1.5; }
|
||||
.cam-countdown { position:absolute; top:45%; left:50%; transform:translate(-50%,-50%); color:#fff; font:600 88px/1 Arial,sans-serif; z-index:8; }
|
||||
.cam-fill { position:absolute; top:0; left:0; right:0; bottom:0; background:#fff; z-index:9; }
|
||||
.cam-enable { position:absolute; bottom:16%; left:50%; transform:translateX(-50%); padding:16rpx 32rpx; border:1px solid #fff; border-radius:8rpx; color:#fff; z-index:4; }
|
||||
.cam-settings { position:absolute; top:24rpx; left:24rpx; color:#fff; font-size:12px; z-index:4; }
|
||||
.compress-canvas { position:fixed; left:200vw; top:0; pointer-events:none; }
|
||||
|
||||
/* ─── 提示/错误 ─── */
|
||||
.cam-hint {
|
||||
position: absolute;
|
||||
@@ -179,7 +196,7 @@
|
||||
.cam-analyzing {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, .78);
|
||||
background: #000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -188,15 +205,27 @@
|
||||
}
|
||||
.cam-analyzing-text {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-size: 17px;
|
||||
letter-spacing: 1rpx;
|
||||
padding:0 40rpx;
|
||||
text-align:center;
|
||||
z-index:2;
|
||||
}
|
||||
.cam-cancel {
|
||||
margin-top: 32rpx;
|
||||
position:absolute; bottom:20rpx; left:0; right:0; text-align:center;
|
||||
color: rgba(255, 255, 255, .7);
|
||||
font-size: 13px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.scan-stage { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); }
|
||||
.scan-artwork { width:100%; height:100%; }
|
||||
.scan-face { position:absolute; left:0; top:0; right:0; bottom:0; -webkit-mask-image:url('../../assets/camera/scan-face-mask.png'); mask-image:url('../../assets/camera/scan-face-mask.png'); -webkit-mask-size:100% 100%; mask-size:100% 100%; overflow:hidden; }
|
||||
.scan-beam { position:absolute; left:0; right:0; top:20%; height:18%; background:linear-gradient(to bottom,#F5F078 0%,#F5F078 35%,rgba(245,240,120,0) 100%); animation:report-scan 1s ease-in-out infinite; }
|
||||
@keyframes report-scan { 0%,100% {transform:translateY(0);} 50% {transform:translateY(166%);} }
|
||||
.scan-flip-icon { position:absolute; left:77.314815%; top:87.900526%; width:8.888889%; height:4.591105%; background:#000; display:flex; align-items:center; justify-content:center; }
|
||||
.scan-flip-icon image { width:83.333333%; height:83.333333%; }
|
||||
.analysis-stage { position:absolute; bottom:100rpx; left:32rpx; right:32rpx; text-align:center; color:#bbb; font-size:12px; }
|
||||
.analysis-retry,.analysis-retake { color:#fff; font-size:14px; padding:20rpx 36rpx; margin-top:24rpx; border:1px solid #777; border-radius:10rpx; z-index:2; }
|
||||
|
||||
/* ─── 权限诊断(开发版) ─── */
|
||||
.cam-diag-entry {
|
||||
@@ -239,6 +268,10 @@
|
||||
padding: 14rpx 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cam-probe-toolbar { position:fixed; z-index:50; left:24rpx; right:24rpx; padding:14rpx 18rpx; border-radius:12rpx; background:rgba(12,39,56,.94); color:#fff; }
|
||||
.cam-probe-version { margin-bottom:10rpx; font-size:22rpx; line-height:30rpx; }
|
||||
.cam-probe-actions { display:flex; gap:14rpx; }
|
||||
.cam-probe-actions button { flex:1; margin:0; padding:0 8rpx; font-size:25rpx; line-height:64rpx; background:#F5F078; color:#142632; }
|
||||
|
||||
/* ─── 调试模式结果 ─── */
|
||||
.cam-debug {
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
<!-- ═════ 05-07 拍照页 ═════
|
||||
两种形态,由 config.nativeCamera 决定:
|
||||
① cameraEnabled=true → 真机取景:<camera> 铺满 + 椭圆镂空蒙版 + 快门直接 takePhoto(对齐 H5 #s-cam)
|
||||
② cameraEnabled=false → 系统相机:静态版面 + 快门调 my.chooseImage
|
||||
关键:<camera> 不能嵌 <cover-view>(微信专有组件,淘宝上会导致整页崩溃),
|
||||
所以相机是兄弟节点,覆盖层用普通 view + box-shadow 镂空。 -->
|
||||
<view class="cam">
|
||||
<import src="../../components/brand-header.axml" />
|
||||
<template is="brand-header" data="{{brandHeader: brandHeader}}" />
|
||||
<!-- H5 三步取景。普通前置帧在本地识别人脸并自动抓拍,AR 为显式可选分支。
|
||||
所有覆盖层为兄弟节点。 -->
|
||||
<view class="cam" style="top:{{brandHeader.height}}px;">
|
||||
<view class="cam-verifying" a:if="{{identityPending}}">
|
||||
<view>{{identityError || '正在确认登录状态…'}}</view>
|
||||
<view a:if="{{identityError}}" onTap="onRetryIdentity">重试</view>
|
||||
</view>
|
||||
<!-- ①真机取景:相机铺满,不加任何子节点 -->
|
||||
<camera a:if="{{cameraEnabled}}" id="skin-camera" class="cam-device"
|
||||
device-position="{{devicePosition}}" flash="off"
|
||||
<ar-camera a:if="{{cameraEnabled && useAR}}" id="skin-ar-camera-{{cameraSession}}" class="cam-device" data-camera-session="{{cameraSession}}"
|
||||
devicePosition="{{arDevicePosition}}" mode="faceDetection2D" useCapturedImage="{{true}}"
|
||||
onInit="onARInit" onARFrame="onARFrame" onError="onCameraError" onStop="onCameraStop" />
|
||||
<camera a:if="{{cameraEnabled && !useAR}}" id="{{nativeCameraId}}" class="cam-device" data-camera-session="{{cameraSession}}"
|
||||
mode="normal" device-position="{{devicePosition}}" frame-size="{{nativeFrameSize}}" flash="off"
|
||||
onError="onCameraError" onStop="onCameraStop" />
|
||||
|
||||
<!-- ②系统相机兜底:H5 相机静态态(白底 + 85% 黑蒙版 ≈ #262626) -->
|
||||
<view class="cam-bg" a:if="{{!cameraEnabled}}" />
|
||||
|
||||
<!-- 取景蒙版:H5 用 SVG 镂空;这里用带巨大 box-shadow 的椭圆开窗,视觉等效 -->
|
||||
<!-- PNGs are rasterized from the exact H5 SVG paths, including feature lines. -->
|
||||
<view class="cam-mask">
|
||||
<view class="cam-hole mask-{{maskStep}}" />
|
||||
<view class="cam-mask-edge edge-top" style="height:{{maskInsetY}}px" />
|
||||
<view class="cam-mask-edge edge-bottom" style="height:{{maskInsetY}}px" />
|
||||
<view class="cam-mask-edge edge-left" style="width:{{maskInsetX}}px" />
|
||||
<view class="cam-mask-edge edge-right" style="width:{{maskInsetX}}px" />
|
||||
<image class="cam-mask-image {{cameraEnabled && devicePosition === 'back' && maskStep ? 'mirror' : ''}}" style="{{maskStageStyle}}" src="../../assets/camera/mask-{{maskName}}{{cameraEnabled ? '-live' : ''}}.png" mode="scaleToFill" />
|
||||
<view a:if="{{cameraEnabled && autoCapture && !doneTip && !analyzing}}" class="cam-live-scan scan-{{maskName}} {{devicePosition === 'back' && maskStep ? 'mirror' : ''}}" style="{{maskStageStyle}}"><view class="cam-live-band" /></view>
|
||||
</view>
|
||||
|
||||
<view class="cam-next" a:if="{{doneTip && !analyzing}}">
|
||||
<view class="cam-next-ok">✓</view>
|
||||
<view class="cam-next-done">{{doneTip}}</view>
|
||||
<view class="cam-next-tip">{{nextTip}}</view>
|
||||
</view>
|
||||
<view class="cam-countdown" a:if="{{countdown}}">{{countdown}}</view>
|
||||
<view class="cam-fill" a:if="{{fillLight}}" />
|
||||
|
||||
<view class="cam-step">
|
||||
<view class="cam-step-no">{{stepText}}</view>
|
||||
<view class="cam-step-tip">{{stepTip}}</view>
|
||||
@@ -40,36 +58,57 @@
|
||||
</view>
|
||||
|
||||
<!-- 分析中 -->
|
||||
<view class="cam-analyzing" a:if="{{analyzing}}">
|
||||
<view class="cam-analyzing-text">{{debugMode ? '正在验证本地图片压缩…' : '正在生成您的肌肤报告…'}}</view>
|
||||
<view class="cam-cancel" onTap="onCancel">{{debugMode ? '退出验收' : '返回,稍后查看历史报告'}}</view>
|
||||
<view class="cam-analyzing" a:if="{{analyzing || analysisError}}">
|
||||
<view class="scan-stage" style="{{scanStageStyle}}" a:if="{{!debugMode && !analysisError}}">
|
||||
<image class="scan-artwork" src="../../assets/camera/scan-base.png" mode="scaleToFill" onError="onScanError" />
|
||||
<view class="scan-face"><view class="scan-beam" /></view>
|
||||
<view class="scan-flip-icon"><image src="../../assets/camera/icon-switch-camera.png" mode="aspectFit" /></view>
|
||||
</view>
|
||||
<view class="cam-analyzing-text" a:if="{{debugMode || analysisError || scanError}}">{{analysisError || (debugMode ? '正在验证本地图片压缩…' : '检测中…')}}</view>
|
||||
<view class="analysis-stage" a:if="{{!analysisError}}">{{analysisStage}}</view>
|
||||
<view class="analysis-retry" a:if="{{analysisError}}" onTap="onRetryAnalysis">重试查看本次检测</view>
|
||||
<view class="analysis-retake" a:if="{{analysisError}}" onTap="onRestart">重新拍摄</view>
|
||||
<view class="cam-cancel" onTap="onCancel">{{debugMode ? '退出验收' : '取消'}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部控制条:相册 / 快门 / 翻转 -->
|
||||
<view class="cam-controls" a:if="{{!previewUrl && !analyzing}}">
|
||||
<view class="cam-side" onTap="onAlbum">相册</view>
|
||||
<view class="cam-shutter" onTap="onShutter">
|
||||
<view class="cam-shutter-dot" />
|
||||
<view class="cam-controls" a:if="{{!previewUrl && !analyzing && !analysisError && !doneTip}}">
|
||||
<view class="cam-side" onTap="onAlbum"><image src="../../assets/camera/icon-album.png" mode="aspectFit" /></view>
|
||||
<view class="cam-shutter {{autoCapture && cameraEnabled ? 'cam-shutter-auto' : ''}}" onTap="onShutter">
|
||||
<image src="../../assets/camera/icon-shutter.png" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="cam-side" onTap="{{cameraEnabled ? 'onFlip' : 'onOpenSettings'}}">{{cameraEnabled ? '翻转' : '授权设置'}}</view>
|
||||
<view class="cam-side cam-side-switch" onTap="onFlip"><image src="../../assets/camera/icon-switch-camera.png" mode="aspectFit" /><text class="cam-switch-label">切换镜头</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 提示/错误文案 -->
|
||||
<view class="cam-hint" a:if="{{permissionMessage && !previewUrl && !analyzing}}">{{permissionMessage}}</view>
|
||||
<view class="cam-live-hint {{liveGood ? 'good' : ''}}" a:if="{{cameraEnabled && autoCapture && !doneTip && !analyzing}}">{{liveHint}}</view>
|
||||
<view class="cam-enable" a:if="{{cameraSupported && !cameraEnabled && !identityPending && !previewUrl && !doneTip && !debugComplete && !analyzing && !analysisError && (camError || permissionMessage)}}" onTap="onEnableCamera">重新开启相机</view>
|
||||
<view class="cam-settings" a:if="{{permissionMessage && !analyzing}}" onTap="onOpenSettings">授权设置</view>
|
||||
|
||||
<!-- Must remain mounted and laid out; display:none/hidden causes empty exports on phones. -->
|
||||
<canvas id="compress-canvas" canvas-id="compress-canvas" width="{{compressWidth}}" height="{{compressHeight}}" class="compress-canvas" style="width:{{compressWidth}}px;height:{{compressHeight}}px" />
|
||||
|
||||
<!-- 开发版:权限诊断 -->
|
||||
<view class="cam-diag-entry" a:if="{{showDiagnose}}" onTap="onDiagnose">权限诊断</view>
|
||||
<view class="cam-diag" a:if="{{diagText}}" catchTap="noop">
|
||||
<view class="cam-probe-toolbar" a:if="{{localFaceProbe}}" style="top:{{probeToolbarTop}}px;">
|
||||
<view class="cam-probe-version">自动三连拍验收 · {{captureProbeVersion}}</view>
|
||||
<view class="cam-probe-actions">
|
||||
<button size="mini" onTap="onCopyCaptureDiagnostics">复制识别诊断</button>
|
||||
<button size="mini" onTap="onCancel">退出验收</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cam-diag-entry" a:if="{{showDiagnose && !localFaceProbe}}" onTap="onDiagnose">权限诊断</view>
|
||||
<view class="cam-diag" a:if="{{diagText}}" style="{{localFaceProbe ? 'top:' + (probeToolbarTop + 96) + 'px;' : ''}}" catchTap="noop">
|
||||
<scroll-view class="cam-diag-scroll" scroll-y>
|
||||
<text class="cam-diag-text">{{diagText}}</text>
|
||||
<text class="cam-diag-text" selectable="{{true}}">{{diagText}}</text>
|
||||
</scroll-view>
|
||||
<view class="cam-diag-close" onTap="onCloseDiagnose">关闭</view>
|
||||
</view>
|
||||
|
||||
<!-- 调试模式结果页 -->
|
||||
<view class="cam-debug" a:if="{{debugComplete}}">
|
||||
<view class="cam-debug" a:if="{{debugComplete}}" style="{{localFaceProbe ? 'padding-top:' + (probeToolbarTop + 96) + 'px;' : ''}}">
|
||||
<view class="cam-debug-title">三步拍摄与 JPEG 压缩已完成</view>
|
||||
<view class="cam-debug-note">请核对实际照片方向是否为正脸、右脸、左脸;本页不判断姿态是否合格。</view>
|
||||
<view class="cam-debug-note">{{localFaceProbe ? '三张照片已通过实时识别自动拍摄;请核对正脸、右脸、左脸顺序和实际方向。照片只在本页临时预览,不上传、不生成报告。' : '请核对实际照片方向是否为正脸、右脸、左脸;本页不判断姿态是否合格。'}}</view>
|
||||
<view class="cam-debug-item" a:for="{{debugPhotos}}" a:key="key">
|
||||
<image class="cam-debug-img" src="{{item.photo}}" mode="aspectFit" />
|
||||
<view class="cam-debug-meta">{{item.title}} · JPEG {{item.width}}×{{item.height}} · 约{{item.kilobytes}}KB</view>
|
||||
|
||||
+798
-67
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"defaultTitle": "1'me在线肌肤测试",
|
||||
"titleBarColor": "#101823",
|
||||
"defaultTitle": "",
|
||||
"titleBarColor": "#FFFFFF",
|
||||
"backgroundColor": "#101823",
|
||||
"disableScroll": true
|
||||
"disableScroll": true,
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarForceEnable": true,
|
||||
"transparentTitle": "always",
|
||||
"gestureBack": "NO"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user