feat: 小程序端-人脸认证流程+钱包明细详情

- me-home.html 用户卡片新增「未认证/已认证」人脸标签,未认证点击跳转人脸录入
- me-account.html 增加演示切换开关用于切换人脸认证状态
- 新增 me-face-verify.html(人脸认证引导/采集)和 me-face-info.html(认证信息展示)
- me-wallet.html 钱包页扩展收支明细/账户卡片等内容
- 新增 me-wallet-detail.html 钱包明细详情页

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-08-03 18:40:00 +08:00
co-authored by Claude Opus 4.7
parent 836b05383d
commit 799be643b2
6 changed files with 930 additions and 34 deletions
@@ -64,6 +64,26 @@
.info-value.gold { color: #d4a017; }
.info-row.editable { cursor: pointer; }
.edit-icon { font-size: 12px; font-style: normal; opacity: 0.6; }
/* 演示切换栏 */
.demo-bar {
display: flex; align-items: center; gap: 8px; padding: 10px 16px;
background: #fffbe6; border-bottom: 1px solid #ffe58f; font-size: 12px; color: #8c6d1f;
}
.demo-bar .demo-label { font-weight: 600; }
.demo-bar .demo-state { font-weight: 600; min-width: 48px; }
/* 切换开关 */
.toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
.toggle-switch input { display: none; }
.toggle-slider {
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
background: #d9d9d9; border-radius: 24px; cursor: pointer; transition: 0.3s;
}
.toggle-slider::before {
content: ''; position: absolute; left: 3px; bottom: 3px; width: 18px; height: 18px;
background: #fff; border-radius: 50%; transition: 0.3s;
}
input:checked + .toggle-slider { background: #52c41a; }
input:checked + .toggle-slider::before { transform: translateX(20px); }
</style>
</head>
<body>
@@ -86,6 +106,15 @@
</div>
<span class="avatar-tip">点击修改头像</span>
</div>
<!-- [原型演示] 人脸认证状态切换 -->
<div class="demo-bar">
<span class="demo-label">演示切换:</span>
<label class="toggle-switch">
<input type="checkbox" id="faceToggle" onchange="toggleFaceState()">
<span class="toggle-slider"></span>
</label>
<span class="demo-state" id="demoStateLabel">未认证</span>
</div>
<!-- 信息列表 -->
<div class="info-group">
<div class="info-row"><span class="info-label">姓名</span><span class="info-value">张三</span></div>
@@ -97,9 +126,47 @@
<div class="info-row"><span class="info-label">单位</span><span class="info-value">CQ能源</span></div>
<div class="info-row"><span class="info-label">部门</span><span class="info-value">采油一厂</span></div>
<div class="info-row"><span class="info-label">会员等级</span><span class="info-value gold">黄金会员</span></div>
<!-- [交互] 已认证:点击查看人脸信息/重新录入;未认证:点击前往录入 -->
<div class="info-row editable" id="faceRow" onclick="handleFaceClick()">
<span class="info-label">人脸信息</span>
<span class="info-value" id="faceValue" style="color:#fa8c16;">未认证<i class="edit-icon"></i></span>
</div>
</div>
</div>
<div class="home-indicator"></div>
</div>
<script>
var isFaceVerified = false;
/* [交互] 切换开关:改变人脸认证状态 */
function toggleFaceState() {
isFaceVerified = document.getElementById('faceToggle').checked;
updateFaceRow();
}
/* [交互] 根据状态更新人脸信息行 */
function updateFaceRow() {
var valEl = document.getElementById('faceValue');
var labelEl = document.getElementById('demoStateLabel');
if (isFaceVerified) {
valEl.style.color = '#52c41a';
valEl.innerHTML = '已认证<i class="edit-icon"></i>';
labelEl.textContent = '已认证';
} else {
valEl.style.color = '#fa8c16';
valEl.innerHTML = '未认证<i class="edit-icon"></i>';
labelEl.textContent = '未认证';
}
}
/* [交互] 点击人脸信息行:已认证→人脸详情页;未认证→人脸录入页 */
function handleFaceClick() {
if (isFaceVerified) {
window.location.href = 'me-face-info.html';
} else {
window.location.href = 'me-face-verify.html';
}
}
</script>
</body>
</html>
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人脸信息 | 智能餐养小程序</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #2a2a2a; display: flex; justify-content: center; align-items: flex-start;
min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
padding: 24px 0 40px;
}
.phone-shell {
width: 390px; height: 844px; background: #fff; border-radius: 44px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3), inset 0 0 0 2px #e0e0e0;
overflow: hidden; position: relative; display: flex; flex-direction: column;
}
.status-bar {
height: 44px; background: #fff; display: flex; align-items: center;
justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; color: #333;
}
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar {
height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px;
border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0;
}
.nav-back { font-size: 20px; color: #333; text-decoration: none; width: 32px; }
.nav-title { flex: 1; text-align: center; font-size: 16px; font-weight: 600; color: #333; }
.nav-placeholder { width: 32px; }
.page-content { flex: 1; overflow-y: auto; background: #f5f5f5; }
.page-content::-webkit-scrollbar { display: none; }
.home-indicator {
height: 20px; display: flex; align-items: center; justify-content: center; background: #fff; flex-shrink: 0;
}
.home-indicator::after { content: ''; width: 120px; height: 5px; background: #1a1a1a; border-radius: 3px; opacity: 0.2; }
/* 人脸照片区域 */
.face-photo-section {
background: #fff; padding: 30px 16px; display: flex; flex-direction: column;
align-items: center; margin-bottom: 8px;
}
.face-photo-wrap {
width: 120px; height: 120px; border-radius: 50%;
background: linear-gradient(135deg, #e6f7ff, #bae7ff);
display: flex; align-items: center; justify-content: center;
font-size: 60px; border: 4px solid #1677ff; margin-bottom: 10px;
position: relative;
}
.face-photo-badge {
position: absolute; bottom: 2px; right: 2px; width: 28px; height: 28px;
background: #52c41a; border-radius: 50%; display: flex; align-items: center;
justify-content: center; font-size: 14px; color: #fff;
border: 2px solid #fff;
}
/* 信息列表 */
.info-group { background: #fff; margin-bottom: 8px; }
.info-row {
display: flex; align-items: center; justify-content: space-between;
padding: 16px; border-bottom: 0.5px solid #f5f5f5;
}
.info-row:last-child { border-bottom: none; }
.info-label { font-size: 14px; color: #666; }
.info-value { font-size: 14px; color: #333; font-weight: 500; }
.info-value.green { color: #52c41a; }
/* 重新录入按钮 */
.btn-section { padding: 24px 16px; text-align: center; }
.btn-reenroll {
display: inline-block; padding: 13px 40px; border: 1px solid #1677ff;
border-radius: 22px; background: #fff; color: #1677ff;
font-size: 15px; cursor: pointer; text-decoration: none;
}
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">09:41</span>
<div class="icons"><span>📶</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<a class="nav-back" href="me-account.html"></a>
<span class="nav-title">人脸信息</span>
<span class="nav-placeholder"></span>
</div>
<div class="page-content">
<!-- 人脸照片 -->
<div class="face-photo-section">
<div class="face-photo-wrap">
🙂
<span class="face-photo-badge"></span>
</div>
</div>
<!-- 人脸信息详情 -->
<div class="info-group">
<div class="info-row"><span class="info-label">认证状态</span><span class="info-value green">已认证</span></div>
<div class="info-row"><span class="info-label">录入时间</span><span class="info-value">2026-08-01 14:30</span></div>
<div class="info-row"><span class="info-label">清晰度</span><span class="info-value green">95%</span></div>
</div>
<!-- [交互] 点击跳转人脸重新录入 -->
<div class="btn-section">
<a class="btn-reenroll" href="me-face-verify.html">重新录入</a>
</div>
</div>
<div class="home-indicator"></div>
</div>
</body>
</html>
@@ -0,0 +1,317 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人脸认证 | 智能餐养小程序</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #2a2a2a; display: flex; justify-content: center; align-items: flex-start;
min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
padding: 24px 0 40px;
}
.phone-shell {
width: 390px; height: 844px; background: #fff; border-radius: 44px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3), inset 0 0 0 2px #e0e0e0;
overflow: hidden; position: relative; display: flex; flex-direction: column;
}
.status-bar {
height: 44px; background: #fff; display: flex; align-items: center;
justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; color: #333;
}
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar {
height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px;
border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0;
}
.nav-back { font-size: 20px; color: #333; text-decoration: none; width: 32px; }
.nav-title { flex: 1; text-align: center; font-size: 16px; font-weight: 600; color: #333; }
.nav-placeholder { width: 32px; }
.page-content { flex: 1; overflow-y: auto; background: #f5f5f5; }
.page-content::-webkit-scrollbar { display: none; }
.home-indicator {
height: 20px; display: flex; align-items: center; justify-content: center; background: #fff; flex-shrink: 0;
}
.home-indicator::after { content: ''; width: 120px; height: 5px; background: #1a1a1a; border-radius: 3px; opacity: 0.2; }
/* 步骤指示器 */
.step-bar {
display: flex; align-items: center; justify-content: center; padding: 20px 40px; background: #fff;
}
.step-dot {
width: 28px; height: 28px; border-radius: 50%; background: #e8e8e8;
display: flex; align-items: center; justify-content: center;
font-size: 12px; color: #999; font-weight: 600;
}
.step-dot.active { background: #1677ff; color: #fff; }
.step-dot.done { background: #52c41a; color: #fff; }
.step-line { flex: 1; height: 2px; background: #e8e8e8; margin: 0 8px; }
.step-line.done { background: #52c41a; }
/* 主体区域 */
.verify-body {
display: flex; flex-direction: column; align-items: center; padding: 20px 16px;
}
/* 摄像头取景框 */
.camera-frame {
width: 240px; height: 320px; position: relative; margin: 0 auto 20px;
border-radius: 50% / 40%; overflow: hidden;
background: #1a1a1a; box-shadow: 0 0 0 4px rgba(22,119,255,0.3), 0 8px 32px rgba(0,0,0,0.2);
}
.camera-frame::before {
content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0;
border: 2px dashed rgba(255,255,255,0.3); border-radius: inherit;
margin: 12px; pointer-events: none;
}
/* 取景框四角装饰 */
.camera-corners { position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; }
.camera-corner {
position: absolute; width: 20px; height: 20px;
border-color: #1677ff; border-style: solid; border-width: 0;
}
.camera-corner--tl { top: 10px; left: 10px; border-top-width: 3px; border-left-width: 3px; border-radius: 6px 0 0 0; }
.camera-corner--tr { top: 10px; right: 10px; border-top-width: 3px; border-right-width: 3px; border-radius: 0 6px 0 0; }
.camera-corner--bl { bottom: 10px; left: 10px; border-bottom-width: 3px; border-left-width: 3px; border-radius: 0 0 0 6px; }
.camera-corner--br { bottom: 10px; right: 10px; border-bottom-width: 3px; border-right-width: 3px; border-radius: 0 0 6px 0; }
/* 人脸占位 */
.face-placeholder {
position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
display: flex; flex-direction: column; align-items: center; gap: 8px; color: rgba(255,255,255,0.5);
}
.face-placeholder .face-icon { font-size: 60px; line-height: 1; }
.face-placeholder .face-tip { font-size: 12px; }
/* 扫描线动画 */
.scan-line {
position: absolute; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, transparent, rgba(22,119,255,0.8), transparent);
animation: scanAnim 2s ease-in-out infinite;
}
@keyframes scanAnim {
0%, 100% { top: 15%; }
50% { top: 85%; }
}
/* 提示文字 */
.verify-tip {
font-size: 14px; color: #666; text-align: center; margin-bottom: 24px; line-height: 1.6;
}
.verify-tip .icon { font-size: 18px; }
/* 认证按钮 */
.btn-start {
width: 200px; padding: 14px 0; border: none; border-radius: 24px;
background: linear-gradient(135deg, #1677ff, #4096ff);
color: #fff; font-size: 16px; font-weight: 600; cursor: pointer;
box-shadow: 0 6px 20px rgba(22,119,255,0.3);
letter-spacing: 2px; display: block; margin: 0 auto; text-align: center;
text-decoration: none;
}
.btn-start:disabled {
background: #ccc; box-shadow: none; cursor: not-allowed;
}
/* 认证结果卡片 */
.result-card {
width: 100%; background: #fff; border-radius: 12px; padding: 24px 16px;
text-align: center; margin-bottom: 16px;
}
.result-card .result-icon { font-size: 56px; line-height: 1; margin-bottom: 10px; }
.result-card .result-title { font-size: 18px; font-weight: 700; color: #333; margin-bottom: 6px; }
.result-card .result-desc { font-size: 13px; color: #999; margin-bottom: 16px; }
.result-card .result-meta { display: flex; justify-content: center; gap: 20px; }
.result-card .meta-item { text-align: center; }
.result-card .meta-val { font-size: 14px; font-weight: 600; color: #333; }
.result-card .meta-label { font-size: 11px; color: #999; }
.btn-group { display: flex; gap: 12px; justify-content: center; }
.btn-outline {
padding: 12px 28px; border: 1px solid #1677ff; border-radius: 22px;
background: #fff; color: #1677ff; font-size: 14px; cursor: pointer;
text-decoration: none; display: inline-block;
}
.btn-primary {
padding: 12px 28px; border: none; border-radius: 22px;
background: linear-gradient(135deg, #1677ff, #4096ff);
color: #fff; font-size: 14px; cursor: pointer; font-weight: 500;
text-decoration: none; display: inline-block;
}
/* 注意事项 */
.notice-box {
background: #fffbe6; border: 1px solid #ffe58f; border-radius: 8px;
padding: 12px 14px; margin-top: 16px; width: 100%;
}
.notice-box .notice-title { font-size: 13px; font-weight: 600; color: #d48806; margin-bottom: 6px; }
.notice-box .notice-item { font-size: 12px; color: #8c6d1f; line-height: 1.8; padding-left: 8px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">09:41</span>
<div class="icons"><span>📶</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<a class="nav-back" href="me-home.html" id="btnBack"></a>
<span class="nav-title">人脸认证</span>
<span class="nav-placeholder"></span>
</div>
<div class="page-content">
<!-- 步骤指示器 -->
<div class="step-bar">
<div class="step-dot active" id="step1">1</div>
<div class="step-line" id="line1"></div>
<div class="step-dot" id="step2">2</div>
<div class="step-line" id="line2"></div>
<div class="step-dot" id="step3">3</div>
</div>
<!-- [交互] 阶段一:准备认证 -->
<div class="verify-body" id="phasePrepare">
<div class="camera-frame">
<div class="camera-corners">
<span class="camera-corner camera-corner--tl"></span>
<span class="camera-corner camera-corner--tr"></span>
<span class="camera-corner camera-corner--bl"></span>
<span class="camera-corner camera-corner--br"></span>
</div>
<div class="face-placeholder">
<span class="face-icon">👤</span>
<span class="face-tip">请将面部置于取景框内</span>
</div>
</div>
<p class="verify-tip">
<span class="icon">💡</span> 请确保光线充足、面部无遮挡<br>按提示完成眨眼、张嘴等动作
</p>
<!-- [交互] 点击开始人脸认证采集 -->
<button class="btn-start" onclick="startVerify()">开始认证</button>
</div>
<!-- [交互] 阶段二:认证进行中(默认隐藏) -->
<div class="verify-body" id="phaseCapturing" style="display:none;">
<div class="camera-frame" style="box-shadow: 0 0 0 4px rgba(22,119,255,0.6), 0 8px 32px rgba(0,0,0,0.2);">
<div class="camera-corners">
<span class="camera-corner camera-corner--tl" style="border-color:#52c41a;"></span>
<span class="camera-corner camera-corner--tr" style="border-color:#52c41a;"></span>
<span class="camera-corner camera-corner--bl" style="border-color:#52c41a;"></span>
<span class="camera-corner camera-corner--br" style="border-color:#52c41a;"></span>
</div>
<div class="scan-line" id="scanLine"></div>
<div class="face-placeholder">
<span class="face-icon">🙂</span>
<span class="face-tip" id="captureTip">正在采集中…</span>
</div>
</div>
<p class="verify-tip" style="color:#1677ff;font-weight:500;">
<span class="icon">📷</span> 请保持面部稳定,不要移动
</p>
<!-- [交互] 模拟采集进度,完成后自动跳转结果 -->
<div style="width:200px;height:4px;background:#e8e8e8;border-radius:2px;margin:0 auto 20px;overflow:hidden;">
<div id="progressBar" style="width:0%;height:100%;background:linear-gradient(90deg,#1677ff,#52c41a);border-radius:2px;transition:width 0.3s;"></div>
</div>
</div>
<!-- [交互] 阶段三:认证结果(默认隐藏) -->
<div class="verify-body" id="phaseResult" style="display:none;">
<div class="result-card">
<div class="result-icon" id="resultIcon"></div>
<div class="result-title" id="resultTitle">认证成功</div>
<div class="result-desc" id="resultDesc">人脸信息已录入,可用于后续快速验证</div>
<div class="result-meta">
<div class="meta-item"><div class="meta-val" id="metaTime">--</div><div class="meta-label">认证时间</div></div>
<div class="meta-item"><div class="meta-val" style="color:#52c41a;" id="metaScore">--</div><div class="meta-label">清晰度</div></div>
</div>
</div>
<div class="btn-group">
<!-- [交互] 点击重新采集人脸 -->
<button class="btn-outline" onclick="redoVerify()">重新采集</button>
<!-- [交互] 点击返回账号信息页 -->
<a class="btn-primary" href="me-account.html">完成</a>
</div>
<div class="notice-box">
<div class="notice-title">📌 温馨提示</div>
<div class="notice-item">• 人脸信息仅用于身份核验,加密存储不外泄</div>
<div class="notice-item">• 如需更新人脸信息,可在"账号信息→人脸信息"中重新录入</div>
</div>
</div>
</div>
<div class="home-indicator"></div>
</div>
<script>
/* [交互] 开始认证:切换到采集阶段,模拟采集进度 */
function startVerify() {
document.getElementById('phasePrepare').style.display = 'none';
document.getElementById('phaseCapturing').style.display = 'flex';
document.getElementById('step1').classList.remove('active');
document.getElementById('step1').classList.add('done');
document.getElementById('step1').textContent = '✓';
document.getElementById('step2').classList.add('active');
document.getElementById('line1').classList.add('done');
/* 模拟采集进度 0→100%,约3秒完成 */
var bar = document.getElementById('progressBar');
var tip = document.getElementById('captureTip');
var tips = ['正在采集中…', '请眨眼…', '请张嘴…', '请缓慢转头…'];
var idx = 0;
var p = 0;
var timer = setInterval(function() {
p += Math.random() * 12 + 4;
if (p >= 100) {
p = 100; bar.style.width = '100%';
clearInterval(timer);
tip.textContent = '采集完成 ✓';
setTimeout(showResult, 500);
} else {
bar.style.width = p + '%';
if (p > 25 && idx === 0) { tip.textContent = tips[1]; idx++; }
if (p > 50 && idx === 1) { tip.textContent = tips[2]; idx++; }
if (p > 75 && idx === 2) { tip.textContent = tips[3]; idx++; }
}
}, 180);
}
/* [交互] 显示认证结果 */
function showResult() {
document.getElementById('phaseCapturing').style.display = 'none';
document.getElementById('phaseResult').style.display = 'flex';
document.getElementById('step2').classList.remove('active');
document.getElementById('step2').classList.add('done');
document.getElementById('step2').textContent = '✓';
document.getElementById('step3').classList.add('active');
document.getElementById('line2').classList.add('done');
/* 模拟结果数据 */
var now = new Date();
document.getElementById('metaTime').textContent =
now.getFullYear()+'-'+
String(now.getMonth()+1).padStart(2,'0')+'-'+
String(now.getDate()).padStart(2,'0')+' '+
String(now.getHours()).padStart(2,'0')+':'+
String(now.getMinutes()).padStart(2,'0');
document.getElementById('metaScore').textContent = (92 + Math.floor(Math.random()*7)) + '%';
}
/* [交互] 重新采集:回到准备阶段 */
function redoVerify() {
if (!confirm('确定重新采集人脸信息吗?')) return;
document.getElementById('phaseResult').style.display = 'none';
document.getElementById('phasePrepare').style.display = 'flex';
/* 重置步骤条 */
['step1','step2','step3'].forEach(function(id) {
var el = document.getElementById(id);
el.classList.remove('done','active');
el.textContent = id === 'step1' ? '1' : id === 'step2' ? '2' : '3';
});
document.getElementById('step1').classList.add('active');
document.getElementById('line1').classList.remove('done');
document.getElementById('line2').classList.remove('done');
}
</script>
</body>
</html>
@@ -53,6 +53,16 @@
}
.user-dept { font-size: 12px; opacity: 0.8; margin-top: 3px; }
.user-extra { display: flex; gap: 12px; margin-top: 4px; font-size: 11px; opacity: 0.85; }
/* 人脸认证标签 */
.face-tag {
display: inline-flex; align-items: center; gap: 3px;
padding: 1px 7px; border-radius: 10px; font-size: 10px; font-weight: 500;
text-decoration: none; vertical-align: middle;
}
.face-tag--verified { background: rgba(82, 196, 26, 0.25); color: #d9f7be; }
.face-tag--unverified {
background: rgba(250, 140, 22, 0.25); color: #ffd591;
}
.user-arrow {
width: 28px; display: flex; align-items: center; justify-content: center;
font-size: 18px; opacity: 0.7; flex-shrink: 0; cursor: pointer;
@@ -112,10 +122,12 @@
<div class="user-meta">
<div class="user-name-row">
<span class="user-name">张三</span>
<span class="vip-badge">黄金会员</span>
<!-- [交互] 未认证时点击跳转人脸录入页;已认证时切换为 face-tag--verified -->
<a class="face-tag face-tag--unverified" href="me-face-verify.html">未认证</a>
</div>
<div class="user-dept">CQ能源 · 采油一厂</div>
<div class="user-extra">
<span class="vip-badge">黄金会员</span>
<span>积分 <b>1,280</b></span>
<span>余额 <b>¥368.50</b></span>
</div>
@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交易详情 | 智能餐养小程序</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #2a2a2a; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; padding: 24px 0 40px; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.3), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0; }
.status-bar .time { font-size: 15px; font-weight: 700; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0; position: relative; }
.nav-bar .back { font-size: 22px; color: #1677ff; cursor: pointer; text-decoration: none; line-height: 1; }
.nav-bar .title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; }
.page-content { flex: 1; overflow-y: auto; background: #f5f5f5; }
.page-content::-webkit-scrollbar { display: none; }
/* 交易头部 */
.detail-header { background: #fff; padding: 24px 16px; text-align: center; }
.detail-icon-wrap { width: 56px; height: 56px; border-radius: 16px; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; }
.detail-icon-wrap.food { background: #e6f7ff; }
.detail-icon-wrap.recharge { background: #f6ffed; }
.detail-name { font-size: 16px; font-weight: 500; color: #333; margin-bottom: 8px; }
.detail-amount-big { font-size: 32px; font-weight: 700; letter-spacing: -1px; }
.detail-amount-big.expense { color: #333; }
.detail-amount-big.income { color: #52c41a; }
/* 信息卡片 */
.info-card { background: #fff; margin: 12px 16px; border-radius: 12px; overflow: hidden; }
.info-card .card-title { font-size: 13px; font-weight: 600; color: #333; padding: 14px 16px 8px; }
.info-row { display: flex; padding: 10px 16px; font-size: 14px; border-bottom: 0.5px solid #f5f5f5; }
.info-row:last-child { border-bottom: none; }
.info-label { width: 80px; color: #999; flex-shrink: 0; }
.info-value { flex: 1; color: #333; text-align: right; }
.info-value.success { color: #52c41a; }
.home-indicator { height: 34px; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8px; background: #f5f5f5; flex-shrink: 0; }
.home-indicator::after { content: ''; width: 134px; height: 5px; background: #000; border-radius: 3px; opacity: 0.2; }
</style>
</head>
<body>
<div class="phone-shell">
<!-- 状态栏 -->
<div class="status-bar">
<span class="time">09:41</span>
<div class="icons">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M2 9l10-7 10 7v11a2 2 0 01-2 2h-4v-6H8v6H4a2 2 0 01-2-2V9z" fill="#333"/></svg>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M1 9l2-2c5.5-5.5 14-5.5 19.5 0l2 2M5 13l2-2c3.5-3.5 9-3.5 12.5 0l2 2M9 17l3-3 3 3" stroke="#333" stroke-width="2"/></svg>
<svg width="20" height="12" viewBox="0 0 20 12"><rect x="0" y="1" width="16" height="10" rx="2" stroke="#333" fill="none" stroke-width="1.2"/><rect x="2" y="3" width="10" height="6" rx="1" fill="#333"/><path d="M17 4v4a2 2 0 002-2v0a2 2 0 00-2-2z" fill="#333"/></svg>
</div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<a class="back" href="me-wallet.html">&#8249;</a>
<span class="title">交易详情</span>
</div>
<!-- 页面内容 -->
<div class="page-content">
<!-- 交易头部 -->
<div class="detail-header">
<div class="detail-icon-wrap" id="detailIcon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-name" id="detailName">午餐消费</div>
<div class="detail-amount-big expense" id="detailAmount">-¥28.50</div>
</div>
<!-- 交易信息 -->
<div class="info-card">
<div class="card-title">交易信息</div>
<div class="info-row">
<span class="info-label">交易时间</span>
<span class="info-value" id="infoTime">2026年8月3日 11:32:15</span>
</div>
<div class="info-row">
<span class="info-label">交易单号</span>
<span class="info-value" id="infoOrderNo">NM20260803113215001</span>
</div>
<div class="info-row">
<span class="info-label">支付流水号</span>
<span class="info-value" id="infoPayNo">WX2026080311321500123456</span>
</div>
<div class="info-row">
<span class="info-label">交易类型</span>
<span class="info-value" id="infoType">消费支出</span>
</div>
<div class="info-row" id="merchantRow" style="display:none">
<span class="info-label">商户名称</span>
<span class="info-value" id="infoMerchant">二号食堂</span>
</div>
<div class="info-row">
<span class="info-label">支付方式</span>
<span class="info-value" id="infoMethod">微信支付</span>
</div>
<div class="info-row">
<span class="info-label">交易前余额</span>
<span class="info-value" id="infoBefore">¥256.50</span>
</div>
<div class="info-row">
<span class="info-label">交易后余额</span>
<span class="info-value" id="infoAfter">¥228.00</span>
</div>
<div class="info-row">
<span class="info-label">交易状态</span>
<span class="info-value success" id="infoStatus">交易成功</span>
</div>
</div>
</div>
<!-- 底部指示器 -->
<div class="home-indicator"></div>
</div>
<script>
/* 从 URL hash 解析交易参数 */
(function() {
var hash = window.location.hash.replace('#', '');
if (!hash) return;
var params = {};
hash.split('&').forEach(function(pair) {
var parts = pair.split('=');
if (parts.length === 2) {
params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
});
var type = params.type || 'meal';
/* 类型图标 */
var iconWrap = document.getElementById('detailIcon');
var iconSvg = iconWrap.querySelector('svg');
if (type === 'recharge') {
iconWrap.className = 'detail-icon-wrap recharge';
iconSvg.innerHTML = '<circle cx="12" cy="12" r="9" stroke="#52c41a" stroke-width="1.5"/><path d="M12 8v8M8 12h8" stroke="#52c41a" stroke-width="1.5" stroke-linecap="round"/>';
}
/* 交易名称 */
if (params.name) document.getElementById('detailName').textContent = params.name;
/* 金额 */
var amountEl = document.getElementById('detailAmount');
if (params.amount) {
amountEl.textContent = params.amount;
amountEl.className = 'detail-amount-big ' + (params.amount.indexOf('+') === 0 ? 'income' : 'expense');
}
/* 时间 */
if (params.time) document.getElementById('infoTime').textContent = params.time;
/* 单号 */
if (params.orderNo) document.getElementById('infoOrderNo').textContent = params.orderNo;
/* 支付流水号 */
if (params.payNo) document.getElementById('infoPayNo').textContent = params.payNo;
/* 交易类型文本 */
if (type === 'recharge') {
document.getElementById('infoType').textContent = '余额充值';
} else {
document.getElementById('infoType').textContent = '消费支出';
/* 消费类型显示商户名称 */
if (params.merchant) {
document.getElementById('infoMerchant').textContent = params.merchant;
document.getElementById('merchantRow').style.display = 'flex';
}
}
/* 支付方式 */
if (params.payMethod) document.getElementById('infoMethod').textContent = params.payMethod;
/* 余额 */
if (params.balance) document.getElementById('infoAfter').textContent = '¥' + params.balance;
if (params.before) document.getElementById('infoBefore').textContent = '¥' + params.before;
})();
</script>
</body>
</html>
@@ -25,14 +25,25 @@
.action-btn.primary { background: #fff; color: #1677ff; }
.action-btn.secondary { background: rgba(255,255,255,0.25); color: #fff; border: 1px solid rgba(255,255,255,0.5); }
.section-title { font-size: 15px; font-weight: 600; color: #333; padding: 16px 16px 12px; }
.transaction-list { background: #fff; margin: 0 16px; border-radius: 10px; overflow: hidden; }
.transaction-item { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 0.5px solid #f5f5f5; }
.transaction-item:last-child { border-bottom: none; }
.transaction-info .transaction-name { font-size: 15px; color: #333; }
.transaction-info .transaction-time { font-size: 12px; color: #999; margin-top: 4px; }
.transaction-amount { font-size: 15px; font-weight: 500; }
.transaction-amount.expense { color: #333; }
.transaction-amount.income { color: #52c41a; }
/* 月份分组 */
.month-group { margin: 0 16px 4px; }
.month-label { font-size: 13px; color: #999; padding: 12px 0 8px; }
/* 明细列表 */
.detail-list { background: #fff; border-radius: 10px; overflow: hidden; }
.detail-item { display: flex; align-items: center; padding: 14px 16px; border-bottom: 0.5px solid #f5f5f5; cursor: pointer; transition: background 0.15s; }
.detail-item:active { background: #f5f5f5; }
.detail-item:last-child { border-bottom: none; }
.detail-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-right: 12px; }
.detail-icon.food { background: #e6f7ff; }
.detail-icon.recharge { background: #f6ffed; }
.detail-info { flex: 1; min-width: 0; }
.detail-name { font-size: 15px; color: #333; }
.detail-time { font-size: 12px; color: #999; margin-top: 4px; }
.detail-right { text-align: right; flex-shrink: 0; margin-left: 12px; }
.detail-amount { font-size: 15px; font-weight: 500; }
.detail-amount.expense { color: #333; }
.detail-amount.income { color: #52c41a; }
.detail-balance { font-size: 12px; color: #999; margin-top: 4px; }
.home-indicator { height: 34px; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8px; background: #f5f5f5; flex-shrink: 0; }
.home-indicator::after { content: ''; width: 134px; height: 5px; background: #000; border-radius: 3px; opacity: 0.2; }
</style>
@@ -67,36 +78,170 @@
</div>
</div>
<!-- 最近交易 -->
<div class="section-title">最近交易</div>
<div class="transaction-list">
<div class="transaction-item">
<div class="transaction-info">
<div class="transaction-name">午餐消费</div>
<div class="transaction-time">今天 11:32</div>
<!-- 余额明细 -->
<div class="section-title">余额明细</div>
<!-- 2026年8月 -->
<div class="month-group">
<div class="month-label">2026年8月</div>
<div class="detail-list">
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">午餐消费</div>
<div class="detail-time">8月3日 11:32</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥28.50</div>
<div class="detail-balance">余额 ¥228.00</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">早餐消费</div>
<div class="detail-time">8月3日 07:45</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥15.00</div>
<div class="detail-balance">余额 ¥256.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">午餐消费</div>
<div class="detail-time">8月1日 12:10</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥32.00</div>
<div class="detail-balance">余额 ¥271.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon recharge">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="#52c41a" stroke-width="1.5"/><path d="M12 8v8M8 12h8" stroke="#52c41a" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">充值</div>
<div class="detail-time">8月1日 09:00</div>
</div>
<div class="detail-right">
<div class="detail-amount income">+¥200.00</div>
<div class="detail-balance">余额 ¥303.50</div>
</div>
</div>
<div class="transaction-amount expense">-¥28.5</div>
</div>
<div class="transaction-item">
<div class="transaction-info">
<div class="transaction-name">早餐消费</div>
<div class="transaction-time">今天 07:45</div>
</div>
<!-- 2026年7月 -->
<div class="month-group">
<div class="month-label">2026年7月</div>
<div class="detail-list">
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">午餐消费</div>
<div class="detail-time">7月31日 11:45</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥35.00</div>
<div class="detail-balance">余额 ¥103.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">晚餐消费</div>
<div class="detail-time">7月30日 18:20</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥42.00</div>
<div class="detail-balance">余额 ¥138.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">早餐消费</div>
<div class="detail-time">7月30日 07:30</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥12.00</div>
<div class="detail-balance">余额 ¥180.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon recharge">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="#52c41a" stroke-width="1.5"/><path d="M12 8v8M8 12h8" stroke="#52c41a" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">充值</div>
<div class="detail-time">7月28日 10:15</div>
</div>
<div class="detail-right">
<div class="detail-amount income">+¥100.00</div>
<div class="detail-balance">余额 ¥192.50</div>
</div>
</div>
<div class="transaction-amount expense">-¥15.0</div>
</div>
<div class="transaction-item">
<div class="transaction-info">
<div class="transaction-name">充值</div>
<div class="transaction-time">昨天</div>
</div>
<!-- 2026年6月 -->
<div class="month-group">
<div class="month-label">2026年6月</div>
<div class="detail-list">
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">午餐消费</div>
<div class="detail-time">6月30日 12:00</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥28.00</div>
<div class="detail-balance">余额 ¥92.50</div>
</div>
</div>
<div class="transaction-amount income">+¥100.0</div>
</div>
<div class="transaction-item">
<div class="transaction-info">
<div class="transaction-name">午餐消费</div>
<div class="transaction-time">昨天</div>
<div class="detail-item">
<div class="detail-icon food">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zm4-4h8v4H6V4z" stroke="#1677ff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">早餐消费</div>
<div class="detail-time">6月29日 08:00</div>
</div>
<div class="detail-right">
<div class="detail-amount expense">-¥10.00</div>
<div class="detail-balance">余额 ¥120.50</div>
</div>
</div>
<div class="detail-item">
<div class="detail-icon recharge">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="#52c41a" stroke-width="1.5"/><path d="M12 8v8M8 12h8" stroke="#52c41a" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
<div class="detail-info">
<div class="detail-name">充值</div>
<div class="detail-time">6月25日 14:30</div>
</div>
<div class="detail-right">
<div class="detail-amount income">+¥50.00</div>
<div class="detail-balance">余额 ¥130.50</div>
</div>
</div>
<div class="transaction-amount expense">-¥35.0</div>
</div>
</div>
</div>
@@ -104,5 +249,68 @@
<!-- 底部指示器 -->
<div class="home-indicator"></div>
</div>
<script>
/* 明细条目点击跳转至交易详情页 */
(function() {
var items = document.querySelectorAll('.detail-item');
items.forEach(function(item, index) {
item.addEventListener('click', function() {
var name = item.querySelector('.detail-name').textContent;
var amount = item.querySelector('.detail-amount').textContent;
var shortTime = item.querySelector('.detail-time').textContent;
var balance = item.querySelector('.detail-balance').textContent.replace('余额 ', '').replace('¥', '');
var icon = item.querySelector('.detail-icon');
var type = icon.classList.contains('recharge') ? 'recharge' : 'meal';
/* 从月份分组标签提取年份,拼接完整时间 */
var monthGroup = item.closest('.month-group');
var monthLabel = monthGroup.querySelector('.month-label').textContent;
var year = monthLabel.match(/\d{4}/)[0];
var timeMatch = shortTime.match(/(\d+)月(\d+)日\s+(\d+:\d+)/);
var sec = String(index * 7 % 60).padStart(2, '0');
var fullTime = year + '年' + timeMatch[1] + '月' + timeMatch[2] + '日 ' + timeMatch[3] + ':' + sec;
/* 计算交易前余额 */
var amountNum = parseFloat(amount.replace(/[^0-9.-]/g, ''));
var balanceNum = parseFloat(balance.replace(/[^0-9.]/g, ''));
var beforeNum = type === 'recharge' ? (balanceNum - amountNum) : (balanceNum - amountNum);
var before = beforeNum.toFixed(2);
/* 生成单号 */
var now = new Date();
var orderNo = 'NM' + now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0') +
String(now.getSeconds()).padStart(2, '0') +
String(index).padStart(3, '0');
/* 支付方式:消费用微信支付,充值交替使用不同方式 */
var payMethods = ['微信支付', '现金支付', '微信支付'];
var payMethod = type === 'meal' ? '微信支付' : payMethods[index % payMethods.length];
var params = [
'type=' + encodeURIComponent(type),
'name=' + encodeURIComponent(name),
'amount=' + encodeURIComponent(amount),
'time=' + encodeURIComponent(fullTime),
'balance=' + encodeURIComponent(balance),
'before=' + encodeURIComponent(before),
'orderNo=' + encodeURIComponent(orderNo),
'payMethod=' + encodeURIComponent(payMethod)
];
/* 消费类型添加商户名称 */
if (type === 'meal') {
params.push('merchant=' + encodeURIComponent('二号食堂'));
}
window.location.href = 'me-wallet-detail.html#' + params.join('&');
});
});
})();
</script>
</body>
</html>
</html>