接入上报记录列表 H5(第三个 session 的孤儿页,补提交整合)
h5/reports/index.html(审核中/已通过/未通过三态+计数,真接 /api/v1/report/records)+ logos;android REPORT_RECORDS 路由稍后接 H5 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,601 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<title>上报记录 · 傻瓜比价</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
||||
/* 【去框】原型 html/body 是黑底 + flex 居中(浏览器手机框预览用)。WebView 里直接铺页面色、不居中。
|
||||
height:100% 链让 .device/.screen 能拿到 WebView 真实高度(min-height:100vh 在 WebView 不可靠会坍缩)。 */
|
||||
html, body { height: 100%; }
|
||||
html { background: var(--color-surface-page); }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
||||
background: var(--color-surface-page);
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-primary: #FFD600;
|
||||
--color-on-primary: #1A1A1A;
|
||||
|
||||
--color-surface-page: #F5F5F5;
|
||||
--color-surface-card: #FFFFFF;
|
||||
--color-surface-muted: #FAFAFA;
|
||||
|
||||
--color-ink: #1A1A1A;
|
||||
--color-ink-muted: #666666;
|
||||
--color-ink-subtle: #999999;
|
||||
--color-ink-disabled: #CCCCCC;
|
||||
|
||||
--color-divider: #F0F0F0;
|
||||
--color-divider-strong: #E5E5E5;
|
||||
|
||||
--color-price-lowest: #FF3D00;
|
||||
--color-price-lowest-bg: #FFEBEE;
|
||||
|
||||
--color-state-success-dark: #2E7D32;
|
||||
--color-state-success-bg: #E8F5E9;
|
||||
--color-state-warning-bg: #FFF3E0;
|
||||
--color-state-warning-ink: #B57400;
|
||||
--color-state-danger: #E53935;
|
||||
--color-state-danger-bg: #FFEBEE;
|
||||
|
||||
--color-coin-deep: #5A3A00;
|
||||
|
||||
--radius: 14px;
|
||||
--radius-sm: 10px;
|
||||
--radius-pill: 999px;
|
||||
--shadow-card: 0 1px 2px rgba(0,0,0,.03);
|
||||
--font-num: 'DIN Alternate', 'DIN Pro', 'SF Pro Display', system-ui;
|
||||
}
|
||||
|
||||
/* 【去框】原型 .device 是 375x812 的手机预览框(圆角 + 阴影 + 固定宽高)。
|
||||
WebView 里改成全屏自适应容器:满宽、最少占满视口高、无圆角/阴影。 */
|
||||
.device {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
background: var(--color-surface-page);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.screen {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 【调试返回】原型左下角的浏览器调试返回按钮,WebView 里不需要 */
|
||||
#debugBack { display: none; }
|
||||
|
||||
/* 【去框】原型假状态栏(9:41 + 信号格),App 顶部有原生状态栏/标题栏,这里隐藏 */
|
||||
.statusbar { display: none; }
|
||||
|
||||
.topnav {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
background: var(--color-surface-page);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.topnav-back {
|
||||
width: 32px; height: 32px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer;
|
||||
background: none; border: none;
|
||||
}
|
||||
.topnav-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.topnav-right {
|
||||
margin-left: auto;
|
||||
padding: 6px 4px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-muted);
|
||||
font-weight: 400;
|
||||
background: none; border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 14px 24px;
|
||||
}
|
||||
.scroll::-webkit-scrollbar { display: none; }
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 4px 0 12px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.filter-row::-webkit-scrollbar { display: none; }
|
||||
.filter-chip {
|
||||
flex-shrink: 0;
|
||||
padding: 6px 14px;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: var(--color-ink-muted);
|
||||
background: var(--color-surface-card);
|
||||
border: .5px solid var(--color-divider-strong);
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-chip.active {
|
||||
background: var(--color-ink);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
border-color: var(--color-ink);
|
||||
}
|
||||
.filter-chip .count { margin-left: 4px; opacity: .7; font-weight: 400; }
|
||||
|
||||
.rec-card {
|
||||
background: var(--color-surface-card);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: var(--shadow-card);
|
||||
overflow: hidden;
|
||||
}
|
||||
.rec-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.rec-store {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.4;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.rec-dish {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-muted);
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.badge svg { width: 12px; height: 12px; }
|
||||
.badge-pending { background: var(--color-state-warning-bg); color: var(--color-state-warning-ink); }
|
||||
.badge-success { background: var(--color-state-success-bg); color: var(--color-state-success-dark); }
|
||||
.badge-danger { background: var(--color-state-danger-bg); color: var(--color-state-danger); }
|
||||
|
||||
.rec-compare {
|
||||
margin-top: 12px;
|
||||
background: var(--color-surface-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.rec-compare-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.rec-compare-label {
|
||||
color: var(--color-ink);
|
||||
font-weight: 400;
|
||||
width: 56px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rec-compare-pl {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px 2px 2px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: .5px solid var(--color-divider-strong);
|
||||
}
|
||||
.rec-compare-pl img {
|
||||
width: 16px; height: 16px;
|
||||
border-radius: 3px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rec-compare-pl span {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink);
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.rec-compare-price {
|
||||
margin-left: auto;
|
||||
font-family: var(--font-num);
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.rec-compare-price .cur { font-size: 11px; font-weight: 600; margin-right: 1px; }
|
||||
.rec-compare-row.reported .rec-compare-price { color: var(--color-price-lowest); }
|
||||
|
||||
.rec-reward {
|
||||
margin-top: 12px;
|
||||
background: linear-gradient(90deg, #FFF8E1 0%, #FFFDE7 100%);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--color-coin-deep);
|
||||
font-weight: 400;
|
||||
}
|
||||
.rec-reward-num {
|
||||
font-family: var(--font-num);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rec-reject {
|
||||
margin-top: 12px;
|
||||
background: var(--color-state-danger-bg);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
color: var(--color-state-danger);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rec-reject b { font-weight: 600; }
|
||||
|
||||
.list-end {
|
||||
text-align: center;
|
||||
padding: 24px 0 8px;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-disabled);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 空态(未登录 / 无记录)—— 原型没有,照"比价记录页"补一个轻量居中空态,沿用其字号/颜色 */
|
||||
.rec-empty {
|
||||
display: none;
|
||||
padding: 96px 24px;
|
||||
text-align: center;
|
||||
color: var(--color-ink-subtle);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
<script src="../shared/bridge.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="device" id="app">
|
||||
<div id="debugBack" onclick="goBack()">返回</div>
|
||||
|
||||
<div class="screen active" id="reports">
|
||||
<!-- 【去框】假状态栏:App 内有原生状态栏,已 display:none 隐藏,保留 DOM 防脚本引用报错 -->
|
||||
<div class="statusbar">
|
||||
<span>9:41</span>
|
||||
<span class="statusbar-right">
|
||||
<svg width="18" height="10" viewBox="0 0 18 10" fill="#1A1A1A"><rect x="0" y="6" width="3" height="4" rx=".5"/><rect x="5" y="4" width="3" height="6" rx=".5"/><rect x="10" y="2" width="3" height="8" rx=".5"/><rect x="15" y="0" width="3" height="10" rx=".5"/></svg>
|
||||
<svg width="16" height="11" viewBox="0 0 16 11" fill="#1A1A1A"><path d="M8 11l8-8c-4.5-4-11.5-4-16 0l8 8z"/></svg>
|
||||
<svg width="24" height="11" viewBox="0 0 24 11" fill="none" stroke="#1A1A1A"><rect x=".5" y=".5" width="20" height="10" rx="2.5"/><rect x="2" y="2" width="17" height="7" rx="1.2" fill="#1A1A1A"/><rect x="22" y="3.5" width="1.5" height="4" rx=".5" fill="#1A1A1A"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="topnav">
|
||||
<!-- 返回交给原生接管(占位 console.log),保留按钮视觉 -->
|
||||
<button class="topnav-back" onclick="goBack()" aria-label="返回">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
||||
</button>
|
||||
<div class="topnav-title">上报记录</div>
|
||||
</div>
|
||||
|
||||
<div class="scroll">
|
||||
<!-- 四态筛选:count 用 counts;点击本地按 status 过滤已拉全量 records 重渲染(不重新请求) -->
|
||||
<div class="filter-row" id="filterRow">
|
||||
<div class="filter-chip active" data-status="all" onclick="onFilterClick(this)">全部<span class="count" data-count="all">0</span></div>
|
||||
<div class="filter-chip" data-status="pending" onclick="onFilterClick(this)">审核中<span class="count" data-count="pending">0</span></div>
|
||||
<div class="filter-chip" data-status="approved" onclick="onFilterClick(this)">已通过<span class="count" data-count="approved">0</span></div>
|
||||
<div class="filter-chip" data-status="rejected" onclick="onFilterClick(this)">未通过<span class="count" data-count="rejected">0</span></div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片列表(JS 动态渲染,替代原型 4 张写死 rec-card) -->
|
||||
<div id="recList"></div>
|
||||
|
||||
<!-- 列表末尾「— 已显示全部 N 条 —」,N = 当前筛选后条数 -->
|
||||
<div class="list-end" id="listEnd"></div>
|
||||
|
||||
<!-- 空态(未登录 / 无记录),由 JS 控制显隐 + 文案 -->
|
||||
<div class="rec-empty" id="recEmpty"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ============================================================
|
||||
* 数据层(与「比价记录页」records/index.html 完全同款写法)
|
||||
* SGBridge.getToken(同源相对路径,无 getApiBase)、401→requestLogin、
|
||||
* 无 token→空态、无 Bridge→假数据预览;snake_case → 渲染字段
|
||||
* ============================================================ */
|
||||
|
||||
// ===== 平台 → logo 映射 =====
|
||||
// 原型用 ../../assets/shared/logos/{meituan-waimai,taobao-shanguang,jd-waimai}.png;
|
||||
// 原封不动拷进本页 logos/(保留原型文件名 -waimai/-shanguang,与原型逐字一致)。
|
||||
// 原最低价 / 上报价的平台是动态的(original_platform_id / reported_platform_id),先归一化再取图。
|
||||
var PLATFORM_LOGOS = {
|
||||
'meituan-waimai': 'logos/meituan-waimai.png',
|
||||
'taobao-shanguang': 'logos/taobao-shanguang.png',
|
||||
'jd-waimai': 'logos/jd-waimai.png',
|
||||
};
|
||||
|
||||
// ===== 平台别名归一化(复用「比价记录页」PLATFORM_ALIASES 思路)=====
|
||||
// 后端 platform_id 是短名(meituan/taobao_flash/jd_waimai),统一归一成规范 id 再取 logo;找不到返回 null。
|
||||
var PLATFORM_ALIASES = {};
|
||||
[
|
||||
['meituan-waimai', ['meituan','meituan-waimai','meituan_waimai','美团','美团外卖']],
|
||||
['taobao-shanguang', ['taobao_flash','taobao-shanguang','taobao_shanguang','taobao','淘宝闪购','淘宝']],
|
||||
['jd-waimai', ['jd_waimai','jd_waimai_standalone','jd-waimai','jingdong-waimai','jingdong_waimai','jingdong','jd','京东外卖','京东','京东到家']],
|
||||
].forEach(function (pair) {
|
||||
pair[1].forEach(function (a) { PLATFORM_ALIASES[a.toLowerCase()] = pair[0]; });
|
||||
});
|
||||
function resolvePlatformId(idOrName) {
|
||||
return PLATFORM_ALIASES[String(idOrName || '').toLowerCase()] || null;
|
||||
}
|
||||
// 取平台 logo:归一化命中→对应 png;命中不了→空串(img 不显示也不破图)
|
||||
function platformLogo(idOrName) {
|
||||
var id = resolvePlatformId(idOrName);
|
||||
return id ? PLATFORM_LOGOS[id] : '';
|
||||
}
|
||||
|
||||
// ===== 状态映射:后端 status → badge class + 文案 + 图标 =====
|
||||
// 图标 svg 照搬原型三个 badge 内对应的 svg(审核中=时钟 / 已通过=对勾 / 未通过=叉)。
|
||||
var STATUS_MAP = {
|
||||
pending: { cls: 'badge-pending', text: '审核中',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>' },
|
||||
approved: { cls: 'badge-success', text: '已通过',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7"/></svg>' },
|
||||
rejected: { cls: 'badge-danger', text: '未通过',
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></svg>' },
|
||||
};
|
||||
|
||||
// ===== 价格:后端是分(cents),显示 = (cents/100).toFixed(2) =====
|
||||
function fenToYuan(cents) {
|
||||
return (Number(cents || 0) / 100).toFixed(2);
|
||||
}
|
||||
|
||||
// ===== 假数据(仅电脑浏览器预览用;装进 App 后走真接口)=====
|
||||
// 字段用后端真实会返回的 snake_case(顺带验证归一化 + 分换算)。覆盖三态 + 不同平台。
|
||||
var MOCK = {
|
||||
records: [
|
||||
{
|
||||
store_name: '麦稻中式健康菜(首都机场店)',
|
||||
dish_summary: '秘制口水鸡、干锅土豆片、白米饭',
|
||||
status: 'pending',
|
||||
original_platform_id: 'taobao_flash', original_platform_name: '淘宝闪购', original_price_cents: 1910,
|
||||
reported_platform_id: 'meituan', reported_platform_name: '美团外卖', reported_price_cents: 1750,
|
||||
},
|
||||
{
|
||||
store_name: '蜀大侠火锅(朝阳大悦城店)',
|
||||
dish_summary: '毛肚拼盘、鲜鸭血、招牌牛肉',
|
||||
status: 'approved', reward_coins: 1000,
|
||||
original_platform_id: 'meituan', original_platform_name: '美团外卖', original_price_cents: 18800,
|
||||
reported_platform_id: 'jd_waimai', reported_platform_name: '京东外卖', reported_price_cents: 15600,
|
||||
},
|
||||
{
|
||||
store_name: '和府捞面(国贸店)',
|
||||
dish_summary: '草本汤养鸡汤面、卤蛋',
|
||||
status: 'rejected', reject_reason: '截图模糊,无法确认商品和价格信息。请重新上传清晰截图。',
|
||||
original_platform_id: 'meituan', original_platform_name: '美团外卖', original_price_cents: 4200,
|
||||
reported_platform_id: 'taobao_flash', reported_platform_name: '淘宝闪购', reported_price_cents: 3850,
|
||||
},
|
||||
{
|
||||
store_name: '瑞幸咖啡(望京 SOHO 店)',
|
||||
dish_summary: '生椰拿铁、丝绒拿铁',
|
||||
status: 'approved', reward_coins: 1000,
|
||||
original_platform_id: 'jd_waimai', original_platform_name: '京东外卖', original_price_cents: 2280,
|
||||
reported_platform_id: 'taobao_flash', reported_platform_name: '淘宝闪购', reported_price_cents: 1790,
|
||||
},
|
||||
],
|
||||
counts: { all: 4, pending: 1, approved: 2, rejected: 1 },
|
||||
};
|
||||
|
||||
// ===== 数据加载:App 里走真接口,浏览器预览走假数据 =====
|
||||
// 同源部署:fetch 走相对路径 /api/v1/...(不依赖原生给域名,无 getApiBase)。
|
||||
// 返回 null 表示未登录(无 token)→ 空态"登录后查看上报记录"。
|
||||
async function loadReports() {
|
||||
var hasBridge = window.SGBridge && typeof SGBridge.getToken === 'function';
|
||||
if (hasBridge) {
|
||||
var token = SGBridge.getToken();
|
||||
if (!token) return null; // 未登录 → 空态
|
||||
try {
|
||||
// status 不传 = 全部(四态切换在本地过滤,不重新请求)
|
||||
var res = await fetch('/api/v1/report/records',
|
||||
{ headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (res.status === 401) { SGBridge.requestLogin && SGBridge.requestLogin(); return null; }
|
||||
var data = await res.json();
|
||||
return {
|
||||
records: data.records || [],
|
||||
counts: data.counts || computeCounts(data.records || []),
|
||||
};
|
||||
} catch (e) { /* 网络失败,落回预览数据 */ }
|
||||
}
|
||||
return MOCK; // 浏览器预览(无 Bridge)
|
||||
}
|
||||
|
||||
// counts 兜底:万一后端没带 counts,就本地数一遍
|
||||
function computeCounts(records) {
|
||||
var c = { all: records.length, pending: 0, approved: 0, rejected: 0 };
|
||||
records.forEach(function (r) { if (c[r.status] != null) c[r.status]++; });
|
||||
return c;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 渲染(用原型的 class,把 4 张写死卡片改成动态生成)
|
||||
* ============================================================ */
|
||||
|
||||
var RECORDS = []; // null = 未登录态
|
||||
var COUNTS = { all: 0, pending: 0, approved: 0, rejected: 0 };
|
||||
var currentStatus = 'all'; // 当前筛选:all|pending|approved|rejected
|
||||
|
||||
function esc(s){ return String(s == null ? '' : s).replace(/[&<>"]/g, function(c){ return {'&':'&','<':'<','>':'>','"':'"'}[c]; }); }
|
||||
|
||||
// 单行「原最低价 / 您上报的」比价行
|
||||
// label:「原最低价」=original / 「您上报的」=reported(走红色,由 .reported 类控制)
|
||||
function renderCompareRow(kind, platformId, platformName, cents) {
|
||||
var logo = platformLogo(platformId || platformName);
|
||||
var label = (kind === 'reported') ? '您上报的' : '原最低价';
|
||||
// platform_name 直接用后端中文名显示
|
||||
return '<div class="rec-compare-row ' + kind + '">' +
|
||||
'<span class="rec-compare-label">' + label + '</span>' +
|
||||
'<span class="rec-compare-pl">' +
|
||||
(logo ? '<img src="' + logo + '" alt="">' : '') +
|
||||
'<span>' + esc(platformName || '') + '</span>' +
|
||||
'</span>' +
|
||||
'<span class="rec-compare-price"><span class="cur">¥</span>' + fenToYuan(cents) + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// 单张上报卡片(结构/class 完全对齐原型)
|
||||
function renderRecCard(rec) {
|
||||
var st = STATUS_MAP[rec.status] || STATUS_MAP.pending;
|
||||
|
||||
var head = '<div class="rec-head">' +
|
||||
'<div>' +
|
||||
'<div class="rec-store">' + esc(rec.store_name || '') + '</div>' +
|
||||
'<div class="rec-dish">' + esc(rec.dish_summary || '') + '</div>' +
|
||||
'</div>' +
|
||||
'<span class="badge ' + st.cls + '">' + st.icon + st.text + '</span>' +
|
||||
'</div>';
|
||||
|
||||
var compare = '<div class="rec-compare">' +
|
||||
renderCompareRow('original', rec.original_platform_id, rec.original_platform_name, rec.original_price_cents) +
|
||||
renderCompareRow('reported', rec.reported_platform_id, rec.reported_platform_name, rec.reported_price_cents) +
|
||||
'</div>';
|
||||
|
||||
// 通过 → 金币到账横幅(用后端 reward_coins);未通过 → 原因;审核中 → 都不追加
|
||||
var extra = '';
|
||||
if (rec.status === 'approved') {
|
||||
extra = '<div class="rec-reward"><span>+</span>' +
|
||||
'<span class="rec-reward-num">' + Number(rec.reward_coins || 0) + '</span>' +
|
||||
'<span>金币已到账</span></div>';
|
||||
} else if (rec.status === 'rejected') {
|
||||
extra = '<div class="rec-reject"><b>未通过原因:</b>' + esc(rec.reject_reason || '') + '</div>';
|
||||
}
|
||||
|
||||
var card = document.createElement('div');
|
||||
card.className = 'rec-card';
|
||||
card.innerHTML = head + compare + extra;
|
||||
return card;
|
||||
}
|
||||
|
||||
// 刷新四态 chip 的 count 数字
|
||||
function renderCounts() {
|
||||
['all', 'pending', 'approved', 'rejected'].forEach(function (k) {
|
||||
var el = document.querySelector('.count[data-count="' + k + '"]');
|
||||
if (el) el.textContent = (COUNTS[k] != null ? COUNTS[k] : 0);
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染列表 + 末尾「已显示全部 N 条」+ 空态
|
||||
function renderList() {
|
||||
var listEl = document.getElementById('recList');
|
||||
var endEl = document.getElementById('listEnd');
|
||||
var emptyEl = document.getElementById('recEmpty');
|
||||
if (!listEl) return;
|
||||
listEl.innerHTML = '';
|
||||
|
||||
// 未登录态:清空列表,空态提示登录
|
||||
if (RECORDS === null) {
|
||||
endEl.style.display = 'none';
|
||||
emptyEl.style.display = 'block';
|
||||
emptyEl.textContent = '登录后查看上报记录';
|
||||
return;
|
||||
}
|
||||
|
||||
// 本地按当前 status 过滤已拉全量(all = 不过滤)
|
||||
var shown = (currentStatus === 'all')
|
||||
? RECORDS
|
||||
: RECORDS.filter(function (r) { return r.status === currentStatus; });
|
||||
|
||||
if (shown.length === 0) {
|
||||
endEl.style.display = 'none';
|
||||
emptyEl.style.display = 'block';
|
||||
emptyEl.textContent = '暂无上报记录';
|
||||
return;
|
||||
}
|
||||
|
||||
emptyEl.style.display = 'none';
|
||||
shown.forEach(function (rec) { listEl.appendChild(renderRecCard(rec)); });
|
||||
|
||||
// N = 当前筛选后条数
|
||||
endEl.style.display = 'block';
|
||||
endEl.textContent = '— 已显示全部 ' + shown.length + ' 条 —';
|
||||
}
|
||||
|
||||
// 点 chip:切 active + 本地按 status 过滤重渲染(不重新请求)
|
||||
function onFilterClick(el) {
|
||||
if (RECORDS === null) return; // 未登录态不响应筛选
|
||||
document.querySelectorAll('.filter-chip').forEach(function (c) { c.classList.remove('active'); });
|
||||
el.classList.add('active');
|
||||
currentStatus = el.getAttribute('data-status') || 'all';
|
||||
renderList();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 导航占位(App 原生接管返回 / 跳转),同「比价记录页」用 console.log 兜底
|
||||
* ============================================================ */
|
||||
function navigate(id) { console.log('[stub] navigate(App 内由原生处理跳转):', id); }
|
||||
window.go = navigate;
|
||||
// 顶栏返回箭头:调原生 SGBridge.closePage() 回上一页(WebViewScreen 的 onBack → popBackStack);浏览器预览仅 log
|
||||
function goBack() {
|
||||
if (window.SGBridge && typeof SGBridge.closePage === 'function') { SGBridge.closePage(); }
|
||||
else { console.log('[stub] goBack(浏览器预览,无原生返回)'); }
|
||||
}
|
||||
window.goBack = goBack;
|
||||
function goBackToProfile() { goBack(); } // 从「我的」进来的返回同样走 closePage 回上一页
|
||||
window.goBackToProfile = goBackToProfile;
|
||||
|
||||
/* ============================================================
|
||||
* 启动:拉数据 → 渲染
|
||||
* ============================================================ */
|
||||
(async function init(){
|
||||
var data = await loadReports();
|
||||
if (data === null) {
|
||||
// 未登录(无 token)→ 空态
|
||||
RECORDS = null;
|
||||
COUNTS = { all: 0, pending: 0, approved: 0, rejected: 0 };
|
||||
} else {
|
||||
RECORDS = data.records || [];
|
||||
COUNTS = data.counts || computeCounts(RECORDS);
|
||||
}
|
||||
renderCounts();
|
||||
renderList();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 627 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Reference in New Issue
Block a user