Files
shaguabijia-app-server/h5/records/index.html
T
陈世睿 0bed761c00 上报最低价流程对齐原型/原生 + 接桥
toast改感谢您的反馈/D校验文案/提交失败解析后端detail/模拟键盘补小数点后2位上限/取消改closePage真返回/上报记录入口接SGBridge.navigate(reportRecords)/头部未读红点默认隐藏

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:12:00 +08:00

1788 lines
105 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
:root {
/* === 摘自 DESIGN.md === */
--color-primary: #FFD600;
--color-primary-dark: #FFB300;
--color-on-primary: #1A1A1A;
--color-ink: #1A1A1A;
--color-ink-secondary: #333333;
--color-ink-muted: #666666;
--color-ink-subtle: #999999;
--color-ink-disabled: #CCCCCC;
--color-ink-on-dark: #FFFFFF;
--color-surface-card: #FFFFFF;
--color-surface-page: #FFFDF5;
--color-surface-muted: #FAFAFA;
--color-divider: #F0F0F0;
--color-divider-strong: #E5E5E5;
--color-price-lowest: #FF3D00;
--color-price-lowest-bg: #FFEBEE;
--color-platform-taobao-shanguang: #FF6A00;
--color-platform-jd-waimai: #E1251B;
--color-platform-meituan-waimai: #FFC300;
--rounded-sm: 8px;
--rounded-md: 12px;
--rounded: 14px;
--rounded-pill: 999px;
--font-num: 'DIN Alternate', 'Helvetica Neue', sans-serif;
--shadow-cta-warm: inset 0 1px 0 rgba(255,255,255,.7), 0 2px 6px rgba(255,179,0,.35);
}
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
/* 【去框】原型 body 是黑底 + flex 居中(浏览器预览用)。WebView 里直接铺页面色、不居中。 */
body {
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
background: #F5F5F5;
color: var(--color-ink);
}
/* 【去框】原型 .device 是 375x812 的手机预览框(圆角 + 阴影 + 固定宽高)。
WebView 里改成全屏自适应容器:满宽、最少占满视口高、无圆角/阴影。 */
.device { position: relative; width: 100%; height: 100%; min-height: 100vh; background: #F5F5F5; overflow: hidden; }
/* status bar —— App 顶部有原生状态栏/标题栏,这里隐藏原型自带的假状态栏 */
.status-bar { display: none; }
/* header —— 标题居中 + 右侧搜索按钮 */
.hist-header { height: 44px; background: #F5F5F5; display: flex; align-items: center; justify-content: center; padding: 0 16px; position: relative; }
.hist-header-title { font-size: 17px; font-weight: 600; color: var(--color-ink); }
.hist-header-back { display: none; position: absolute; left: 0; top: 0; width: 44px; height: 44px; align-items: center; justify-content: center; background: transparent; border: none; padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hist-header-back:active { opacity: .5; }
.hist-header-back svg { width: 24px; height: 24px; display: block; }
.hist-header-search { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.hist-header-search:active { opacity: .6; }
.hist-header-cancel { display: none; position: absolute; left: 12px; top: 50%; transform: translateY(-50%); height: 32px; padding: 0 8px; align-items: center; font-size: 15px; color: var(--color-ink-secondary); cursor: pointer; }
.hist-header-cancel:active { opacity: .5; }
.device.selecting .hist-header-cancel { display: flex; }
.hist-header-reports { display: none; position: absolute; right: 12px; top: 50%; transform: translateY(-50%); height: 32px; padding: 0 4px; align-items: center; gap: 4px; font-size: 14px; font-weight: 400; color: var(--color-ink-muted); background: none; border: none; font-family: inherit; cursor: pointer; }
.hist-header-reports:active { opacity: .5; }
.hist-header-reports-dot { width: 5px; height: 5px; border-radius: 50%; background: #FF3D00; flex-shrink: 0; align-self: flex-start; margin-top: 6px; }
.device.selecting .hist-header-reports { display: inline-flex; }
/* scroll area —— 【去框】原型用 absolute + 顶/底偏移定位(top:132 = header44+filter44+留白;bottom:60 = 给原生 tab-bar 留位)。
WebView 隐藏了底部 tab-bar,故 bottom 改 0。其余偏移沿用原型不动。 */
.hist-scroll { position: absolute; top: 88px; left: 0; right: 0; bottom: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; padding: 8px 12px 24px; }
.device.profile-entry .hist-header-back { display: flex; }
.device.profile-entry .hist-scroll { bottom: 0; padding-bottom: 32px; }
.device.profile-entry .tab-bar { display: none; }
.device.profile-entry.logged-out .hist-empty-logout { bottom: 0; }
/* 顶部筛选 tab(仿淘宝订单页 tab + 下划线) */
.hist-filter { position: absolute; left: 0; right: 0; top: 44px; height: 44px; display: flex; align-items: center; padding: 0 16px; gap: 22px; background: #F5F5F5; z-index: 2; }
/* tab 字号字重对齐店名 15/600,激活态只改颜色 + 黄下划线,不改字号字重(避免跳字) */
.hist-filter-tab { font-size: 15px; color: var(--color-ink-muted); font-weight: 600; cursor: pointer; position: relative; height: 44px; line-height: 44px; padding: 0 2px; transition: color .15s ease; }
.hist-filter-tab:active { opacity: .6; }
.hist-filter-tab.active { color: var(--color-ink); }
.hist-filter-tab.active::after { content: ''; position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%); width: 20px; height: 3px; background: var(--color-primary); border-radius: 2px; }
.device.searching .hist-filter,
.device.selecting .hist-filter { display: none; }
.hist-scroll::-webkit-scrollbar { display: none; }
/* item —— 白卡 + 弱阴影 */
.hist-item { background: #FFFFFF; border-radius: 14px; padding: 14px 14px 10px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,.04); position: relative; }
/* item head: 店名 + 已下单状态;默认日期放在标题下方 */
.hist-item-head { display: flex; align-items: center; gap: 6px; }
.hist-store { font-size: 15px; font-weight: 600; color: var(--color-ink); line-height: 1.3; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hist-date { font-size: 11px; color: var(--color-ink-subtle); font-weight: 400; line-height: 1.3; white-space: nowrap; }
.hist-item-meta { display: flex; align-items: center; gap: 6px; min-width: 0; }
.device.time-title .hist-item > .hist-item-meta { margin-top: 3px; }
.device.time-chip .hist-item-head .hist-item-meta { flex-shrink: 0; height: 16px; padding: 0 5px; border-radius: 4px; background: var(--color-surface-muted); }
.device.time-chip .hist-date { font-size: 10px; color: var(--color-ink-muted); }
.device.time-hidden .hist-item-meta { display: none; }
/* 已下单标记:浅绿小 chip,紧跟在店名后 */
.hist-ordered-chip { display: inline-flex; align-items: center; height: 16px; padding: 0 5px; border-radius: 3px; background: #E8F5E9; color: #2E7D32; font-size: 10px; font-weight: 600; flex-shrink: 0; }
/* dish 两种模式:
- dish-full:≤ 3 件,完整列出菜名,无入口
- dish-fold:≥ 4 件,菜名串单行 ellipsis + 等 N 件 + › 入口,点击在当前卡片内展开
*/
.hist-dish-full { font-size: 12px; color: var(--color-ink-muted); margin-top: 8px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.hist-dish-fold { position: relative; display: flex; align-items: center; margin-top: 8px; padding-right: 18px; cursor: pointer; user-select: none; min-width: 0; }
.hist-dish-fold:active { opacity: .55; }
.hist-dish-fold-names { font-size: 12px; color: var(--color-ink-muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.4; }
.hist-dish-fold-count { font-size: 12px; color: var(--color-ink-subtle); margin-left: 4px; flex-shrink: 0; line-height: 1.4; }
.hist-dish-fold-arrow { position: absolute; right: 0; top: 50%; width: 12px; height: 12px; color: var(--color-ink-disabled); display: flex; align-items: center; justify-content: center; transform: translateY(-50%); transition: transform .16s ease, color .16s ease; transform-origin: center; }
.hist-dish-fold-arrow svg { width: 12px; height: 12px; display: block; stroke: currentColor; overflow: visible; }
.hist-item.dish-expanded .hist-dish-fold { align-items: flex-start; }
.hist-item.dish-expanded .hist-dish-fold-names { white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.45; }
.hist-item.dish-expanded .hist-dish-fold-count { padding-top: 1px; }
.hist-item.dish-expanded .hist-dish-fold-arrow { top: 3px; color: var(--color-ink-subtle); transform: rotate(180deg); }
/* 单选模式下禁用 fold 入口,避免点击展开菜单与选中行冲突 */
.device.selecting .hist-dish-fold { pointer-events: none; }
.device.selecting .hist-dish-fold-arrow { visibility: hidden; }
/* menu sheet —— 菜品清单底部弹窗 */
.menu-mask { position: absolute; inset: 0; background: rgba(0,0,0,.45); z-index: 9; opacity: 0; pointer-events: none; transition: opacity .25s; }
.menu-mask.show { opacity: 1; pointer-events: auto; }
.menu-sheet { position: fixed; left: 0; right: 0; bottom: 0; background: #FFFFFF; border-top-left-radius: 18px; border-top-right-radius: 18px; padding: 8px 16px 28px; z-index: 10; transform: translateY(100%); transition: transform .3s cubic-bezier(.2,.8,.2,1); max-height: 70%; display: flex; flex-direction: column; }
.menu-sheet.show { transform: translateY(0); }
.menu-grip { width: 36px; height: 4px; background: rgba(0,0,0,.18); border-radius: 2px; margin: 8px auto 14px; }
.menu-title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.menu-title { font-size: 16px; font-weight: 600; color: var(--color-ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-count { font-size: 12px; color: var(--color-ink-subtle); flex-shrink: 0; }
.menu-date { font-size: 12px; color: var(--color-ink-subtle); margin-bottom: 8px; }
.menu-list { overflow-y: auto; flex: 1; min-height: 0; -webkit-overflow-scrolling: touch; }
.menu-list::-webkit-scrollbar { display: none; }
.menu-dish { display: flex; align-items: baseline; justify-content: space-between; padding: 13px 0; border-bottom: 0.5px solid var(--color-divider); font-size: 14px; line-height: 1.4; }
.menu-dish:last-child { border-bottom: 0; }
.menu-dish-name { color: var(--color-ink); flex: 1; min-width: 0; padding-right: 12px; }
.menu-dish-qty { color: var(--color-ink-subtle); font-family: var(--font-num); flex-shrink: 0; }
.menu-close { margin-top: 12px; height: 44px; background: var(--color-surface-muted); border-radius: 999px; display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 600; color: var(--color-ink); cursor: pointer; }
.menu-close:active { opacity: .7; }
/* platform grid —— 固定 3 列等宽,固定顺序:美团 / 淘宝 / 京东。"最低"角标跟着实际最低价走 */
.hist-platforms { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
.hist-pl-cell { position: relative; display: flex; flex-direction: column; gap: 6px; padding: 10px 10px; border: 0.5px solid var(--color-divider-strong); border-radius: 10px; min-width: 0; background: var(--color-surface-card); }
.hist-pl-cell.lowest { border-color: var(--color-price-lowest); }
.hist-pl-head { display: flex; align-items: center; gap: 5px; min-width: 0; }
.hist-pl-logo { width: 14px; height: 14px; border-radius: 3px; object-fit: cover; flex-shrink: 0; }
.hist-pl-name { font-size: 11px; color: var(--color-ink-secondary); font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.hist-pl-price { font-family: var(--font-num); font-size: 15px; font-weight: 600; color: var(--color-ink); line-height: 1; }
.hist-pl-cell.lowest .hist-pl-price { color: var(--color-price-lowest); font-size: 16px; font-weight: 700; }
.hist-pl-cell.lowest .hist-pl-name { color: var(--color-ink); font-weight: 600; }
/* "最低" badge 浮在卡片顶部外沿,避开 name 区域 */
.hist-pl-corner { display: none; position: absolute; top: -7px; right: 6px; padding: 2px 6px; background: var(--color-price-lowest); color: #fff; font-size: 10px; font-weight: 600; border-radius: 4px; line-height: 1.4; z-index: 2; box-shadow: 0 1px 3px rgba(255,61,0,.25); }
.hist-pl-cell.lowest .hist-pl-corner { display: block; }
/* === 状态:未安装 平台 APP(用户没装该平台,引导下载) === */
.hist-pl-cell.not-installed { background: var(--color-surface-muted); border-style: dashed; }
.hist-pl-cell.not-installed .hist-pl-logo { opacity: .5; filter: grayscale(.3); }
.hist-pl-cell.not-installed .hist-pl-name { color: var(--color-ink-subtle); }
/* === 状态:此店在该平台不存在 === */
.hist-pl-cell.no-store { background: var(--color-surface-muted); }
.hist-pl-cell.no-store .hist-pl-logo { opacity: .35; filter: grayscale(1); }
.hist-pl-cell.no-store .hist-pl-name { color: var(--color-ink-subtle); }
/* 状态文字,替代 price 显示 */
.hist-pl-status { font-size: 12px; font-weight: 600; color: var(--color-ink-muted); line-height: 1; display: inline-flex; align-items: center; gap: 3px; }
.hist-pl-cell.not-installed { cursor: pointer; }
.hist-pl-cell.not-installed:active { background: #F0F0F0; }
.hist-pl-cell.not-installed .hist-pl-status { color: var(--color-ink-secondary); }
.hist-pl-cell.not-installed .hist-pl-status .dl-ico { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; color: var(--color-primary-dark); margin-left: 1px; }
.hist-pl-cell.no-store .hist-pl-status { color: var(--color-ink-subtle); font-weight: 400; font-size: 11px; }
.hist-card-actions { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 6px; min-height: 28px; }
.device.time-compact .hist-card-actions { justify-content: space-between; }
.hist-recompare-btn { height: 28px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--color-primary); background: #FFFCE5; color: var(--color-ink); font-size: 12px; font-weight: 600; font-family: inherit; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255,255,255,.7); }
.hist-recompare-btn:active { transform: scale(.96); background: #FFF7C2; }
.device.selecting .hist-card-actions { display: none; }
/* ============ 搜索态(点搜索图标进入) ============ */
/* 默认隐藏的搜索栏,搜索态时覆盖在 header 上 */
.hist-search-bar { display: none; position: absolute; inset: 0; padding: 0 16px 0 12px; align-items: center; gap: 10px; background: #F5F5F5; }
.device.searching .hist-search-bar { display: flex; }
.device.searching .hist-header-title { visibility: hidden; }
.device.searching .hist-header-search { display: none; }
.hist-search-input-wrap { flex: 1; display: flex; align-items: center; height: 34px; background: var(--color-surface-muted); border: 0.5px solid var(--color-divider-strong); border-radius: 17px; padding: 0 12px; gap: 6px; min-width: 0; }
.hist-search-input-wrap svg { flex-shrink: 0; color: var(--color-ink-subtle); }
.hist-search-input { flex: 1; border: 0; outline: 0; background: transparent; font-size: 14px; color: var(--color-ink); min-width: 0; padding: 0; }
.hist-search-input::placeholder { color: var(--color-ink-subtle); }
.hist-search-clear { width: 16px; height: 16px; border-radius: 50%; background: var(--color-ink-disabled); color: #fff; display: none; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 11px; line-height: 1; }
.device.searching.has-input .hist-search-clear { display: flex; }
.hist-search-cancel { font-size: 15px; color: var(--color-ink); cursor: pointer; flex-shrink: 0; padding: 4px 0; }
.hist-search-cancel:active { opacity: .55; }
/* 搜索态:隐藏反馈 FAB 和底部 tab,让搜索结果占满 */
.device.searching .fb-fab { display: none; }
.device.searching .tab-bar { display: none; }
.device.searching .hist-scroll { bottom: 0; }
/* 最近搜索 chips —— 搜索态且无输入时显示 */
.hist-search-history { display: none; padding: 16px 0 8px; }
.device.searching:not(.has-input) .hist-search-history { display: block; }
.hist-search-history-title { font-size: 13px; font-weight: 600; color: var(--color-ink-secondary); margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between; }
.hist-search-history-clear { font-size: 12px; color: var(--color-ink-subtle); font-weight: 400; cursor: pointer; }
.hist-search-history-clear:active { opacity: .55; }
.hist-search-history-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.hist-search-chip { height: 28px; padding: 0 12px; background: var(--color-surface-muted); border-radius: 14px; display: inline-flex; align-items: center; font-size: 13px; color: var(--color-ink-secondary); cursor: pointer; }
.hist-search-chip:active { background: var(--color-divider); }
.hist-search-history-empty { font-size: 13px; color: var(--color-ink-subtle); padding: 4px 0; }
/* 列表条目搜索过滤 + 无结果空态 */
.device.searching .hist-item.search-hide { display: none; }
/* 搜索态但还没输入:完全不显示原列表(只露最近搜索 chips) */
.device.searching:not(.has-input) .hist-item { display: none; }
.hist-empty { display: none; padding: 80px 24px; text-align: center; color: var(--color-ink-subtle); font-size: 14px; line-height: 1.6; }
.device.searching.no-results .hist-empty { display: block; }
.device.searching:not(.has-input) .hist-empty { display: none; }
/* 命中关键词高亮 */
.search-hl { color: var(--color-price-lowest); font-weight: 700; }
/* 反馈低价相关样式集中在 style 末尾,本节预留 */
/* ======= Tab bar — 4 tab + 中央 AI 领券 FAB ======= */
/* 【隐藏底栏】WebView 里 App 有原生底栏,原型这个 H5 底栏整体隐藏 */
.tab-bar { display: none; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; cursor: pointer; position: relative; z-index: 1; }
.tab-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.tab-label { font-size: 11px; color: var(--color-ink-secondary); font-weight: 400; }
.tab-item.active .tab-label { color: var(--color-ink); font-weight: 700; }
.tab-icon .i-outline { display: block; }
.tab-icon .i-filled { display: none; }
.tab-item.active .tab-icon .i-outline { display: none; }
.tab-item.active .tab-icon .i-filled { display: block; }
.welfare-tab-dot {
position: absolute;
left: 50%;
top: 9px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #FF3B30;
border: 1px solid #FFFFFF;
box-shadow: 0 2px 5px rgba(255, 59, 48, .32);
transform: translateX(7px);
display: none;
pointer-events: none;
z-index: 3;
}
.tab-item.has-welfare-dot .welfare-tab-dot { display: block; }
/* ======= 转场动画(与其他模块一致) ======= */
@keyframes nav-push-out { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-30%); opacity: .7; } }
@keyframes nav-pop-out { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
@keyframes nav-modal-out { from { transform: translateY(0); opacity: 1; } to { transform: translateY(100%); opacity: 0; } }
@keyframes nav-replace-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes nav-fade-out { from { opacity: 1; } to { opacity: 0; } }
body.transitioning-push { animation: nav-push-out 280ms ease-in forwards; }
body.transitioning-pop { animation: nav-pop-out 280ms ease-in forwards; }
body.transitioning-modal { animation: nav-modal-out 280ms ease-in forwards; }
body.transitioning-replace { animation: nav-replace-out 200ms ease-out forwards; }
body.transitioning-fade { animation: nav-fade-out 400ms ease-in-out forwards; }
body.transitioning-tab { /* no animation */ }
/* ============ 未登录态:骨架模糊 + 居中登录卡(保留 tab / 上报 / 搜索骨架,点任意元素引导登录,不展示任何真实数据) ============ */
/* 骨架占位列表(模糊处理,不是真实记录)—— 【去框】bottom 60→0(无原生 tab-bar 占位) */
.hist-logout-skeleton { display: none; position: absolute; top: 88px; left: 0; right: 0; bottom: 0; overflow: hidden; padding: 8px 12px 24px; filter: blur(3px); pointer-events: none; }
.device.logged-out .hist-logout-skeleton { display: block; }
.hist-sk-item { background: #FFFFFF; border-radius: 14px; padding: 14px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,.04); }
.hist-sk-line { height: 12px; border-radius: 6px; background: linear-gradient(90deg,#ECECEC 0%,#F4F4F4 50%,#ECECEC 100%); }
.hist-sk-line.w60 { width: 60%; }
.hist-sk-line.w50 { width: 50%; }
.hist-sk-line.w30 { width: 30%; }
.hist-sk-line.sm { height: 9px; margin-top: 8px; }
.hist-sk-cells { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; margin-top: 14px; }
.hist-sk-cell { height: 46px; border-radius: 10px; background: linear-gradient(90deg,#EEE 0%,#F6F6F6 50%,#EEE 100%); border: .5px solid #ECECEC; }
/* 半透明蒙层,压住骨架,提示"锁定" —— 【去框】bottom 60→0 */
.hist-logout-scrim { display: none; position: absolute; top: 88px; left: 0; right: 0; bottom: 0; background: rgba(245,245,245,.45); z-index: 6; cursor: pointer; }
.device.logged-out .hist-logout-scrim { display: block; }
/* 居中登录卡容器:本身不拦点击(让 tab / 上报 / 骨架区的点击穿透到下层引导登录),只有卡片可点 —— 【去框】bottom 60→0 */
.hist-empty-logout { display: none; position: absolute; top: 44px; left: 0; right: 0; bottom: 0; align-items: center; justify-content: center; padding: 0 24px; z-index: 8; pointer-events: none; }
.hist-login-card { pointer-events: auto; }
.device.logged-out .hist-empty-logout { display: flex; }
.device.logged-out .hist-scroll { display: none; }
/* 从「我的」深链进入(无底部 tab bar)时,骨架/蒙层铺到底 */
.device.profile-entry.logged-out .hist-logout-skeleton,
.device.profile-entry.logged-out .hist-logout-scrim { bottom: 0; }
/* 登录卡本体 */
.hist-login-card { width: 248px; background: #FFFFFF; border-radius: 18px; padding: 24px 22px 22px; text-align: center; box-shadow: 0 12px 40px rgba(0,0,0,.16); }
.hist-login-card .lk { width: 60px; height: 60px; border-radius: 50%; background: #FFFDE7; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; border: 1.5px solid #FFE066; }
.hist-login-card .lk svg { width: 28px; height: 28px; }
.hist-login-card .empty-title { font-size: 16px; font-weight: 700; color: var(--color-ink); margin-bottom: 18px; line-height: 1.3; }
.hist-login-card .empty-cta { height: 40px; padding: 0 30px; background: var(--color-primary); color: var(--color-on-primary); border: 0; border-radius: 999px; font-size: 14px; font-weight: 700; box-shadow: 0 4px 12px rgba(255,179,0,.4), inset 0 1px 0 rgba(255,255,255,.6); cursor: pointer; transition: transform .12s; font-family: inherit; }
.hist-login-card .empty-cta:active { transform: scale(.96); }
/* AI 领券 toast 反馈(点击中央 FAB 时) */
.toast { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 14px 22px; background: rgba(0,0,0,.78); color: #fff; font-size: 15px; font-weight: 500; border-radius: 12px; opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 100; max-width: 75%; text-align: center; }
.toast.show { opacity: 1; }
/* ======================================== */
/* 反馈低价 · 6 套 FAB 样式 + 单选模式 + 反馈抽屉 */
/* ======================================== */
/* —— 反馈低价 CTA —— 顶部右侧,位于搜索图标下方 */
.fb-fab { position: absolute; top: 96px; right: 16px; bottom: auto; height: 30px; padding: 0 12px; background: #FFFCE5; color: var(--color-on-primary); border-radius: 999px; font-size: 12px; font-weight: 700; font-family: inherit; display: flex; align-items: center; justify-content: center; border: 1px solid var(--color-primary); cursor: pointer; z-index: 4; transition: transform .14s ease, background .14s ease; white-space: nowrap; }
.fb-fab:active { transform: scale(.96); }
.fb-fab-badge { display: none; }
.device.cta-outline .fb-fab { background: #FFFFFF; border-color: #FFD600; box-shadow: none; }
.device.cta-solid .fb-fab { background: var(--color-primary); border-color: var(--color-primary); box-shadow: inset 0 1px 0 rgba(255,255,255,.65), 0 2px 6px rgba(255,179,0,.22); }
.device.cta-reward .fb-fab { height: 28px; padding: 0 10px; font-size: 11px; background: var(--color-primary); border-color: var(--color-primary); box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 4px rgba(255,179,0,.18); }
.device.cta-reward .fb-fab-badge { position: absolute; top: -7px; right: -4px; height: 14px; padding: 0 4px; background: #FF3D00; color: #fff; font-size: 8px; font-weight: 700; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; line-height: 1; box-shadow: 0 1px 3px rgba(255,61,0,.25); }
.device.selecting .fb-fab { display: none; }
/* —— 单选模式 —— */
.device.selecting .hist-header-search { display: none; }
.device.selecting .hist-header-title { font-size: 0; }
.device.selecting .hist-header-title::before { content: '选择一条记录上报'; font-size: 17px; font-weight: 600; color: var(--color-ink); }
.select-task-panel { display: none; position: absolute; left: 0; right: 0; top: 44px; padding: 8px 16px 6px; background: #F5F5F5; z-index: 2; }
.select-task-panel .fb-task-card { margin: 0; }
.device.selecting .select-task-panel { display: block; }
/* 【去框】单选态滚动区 bottom 76→0action bar 改为 fixed 浮在视口底) */
.device.selecting .hist-scroll { top: 198px; bottom: 0; padding-bottom: 92px; }
/* 单选模式顶部 3 步指引 */
.device.selecting .tab-bar { display: none; }
/* 单选时整张卡片左侧让出 radio 空间(14 padding + 22 radio + 10 gap = 46 */
.device.selecting .hist-item { padding-left: 46px; cursor: pointer; }
.hist-item-radio { position: absolute; top: 14px; left: 14px; width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--color-divider-strong); background: var(--color-surface-card); display: none; align-items: center; justify-content: center; transition: all .15s; z-index: 1; }
.device.selecting .hist-item-radio { display: flex; }
.hist-item.selected { background: #FFFCE5; box-shadow: 0 0 0 1.5px var(--color-primary), 0 1px 3px rgba(0,0,0,.04); }
.hist-item.selected .hist-item-radio { background: var(--color-primary); border-color: var(--color-primary); }
.hist-item.selected .hist-item-radio::after { content: ''; position: absolute; left: 50%; top: 47%; width: 9px; height: 5px; border-left: 2px solid var(--color-on-primary); border-bottom: 2px solid var(--color-on-primary); background: transparent; border-radius: 0; transform: translate(-50%, -50%) rotate(-45deg); }
/* 单选模式底部 action bar —— 【去框】absolute→fixed 贴视口底 */
.fb-actionbar { position: fixed; left: 0; right: 0; bottom: 0; height: 76px; display: none; flex-direction: row; align-items: center; gap: 10px; padding: 12px 16px 16px; background: rgba(255,255,255,.96); border-top: .5px solid var(--color-divider-strong); z-index: 7; backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px); }
.device.selecting .fb-actionbar { display: flex; }
.fb-next-btn { flex: 1; height: 44px; border-radius: 999px; border: 0; background: var(--color-primary); color: var(--color-on-primary); font-size: 15px; font-weight: 700; box-shadow: var(--shadow-cta-warm); cursor: pointer; transition: transform .12s; }
.fb-next-btn:active { transform: scale(.98); }
.fb-next-btn:disabled { background: var(--color-ink-disabled); color: #fff; box-shadow: none; cursor: not-allowed; }
/* —— 反馈 bottom-sheet —— */
/* 反馈低价独立页面(push 从右进入,全屏)—— 【去框】absolute→fixedtop 44→0 铺满 WebView */
.fb-mask { display: none; }
.fb-sheet { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: #F5F5F5; z-index: 11; transform: translateX(100%); transition: transform .3s cubic-bezier(.2,.8,.2,1); display: flex; flex-direction: column; }
.fb-sheet.show { transform: translateX(0); }
/* 页面顶部 header */
.fb-page-header { height: 44px; padding: 0 12px; background: #F5F5F5; display: flex; align-items: center; position: relative; flex-shrink: 0; }
.fb-page-back { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.fb-page-back:active { opacity: .5; }
.fb-page-title { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); text-align: center; font-size: 17px; font-weight: 600; color: var(--color-ink); pointer-events: none; }
.fb-page-reports { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; padding: 6px 4px; font-size: 14px; color: var(--color-ink-muted); font-weight: 400; background: none; border: none; cursor: pointer; font-family: inherit; }
.fb-page-reports:active { opacity: .5; }
.fb-page-reports .fb-page-reports-dot { width: 5px; height: 5px; border-radius: 50%; background: #FF3D00; flex-shrink: 0; align-self: flex-start; margin-top: 4px; }
/* 内容滚动区 */
.fb-page-body { flex: 1; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; padding: 0 16px 16px; min-height: 0; overscroll-behavior: contain; }
.fb-page-body::-webkit-scrollbar { display: none; }
/* 底部固定提交按钮容器 */
.fb-page-footer { flex-shrink: 0; padding: 12px 16px calc(20px + env(safe-area-inset-bottom, 0)); background: #FFFFFF; border-top: .5px solid var(--color-divider); }
.fb-sheet-title { font-size: 16px; font-weight: 600; color: var(--color-ink); text-align: center; line-height: 1.3; }
/* 顶部奖励横幅(取代原 sheet-title 位置,承担「身份 + 利益」双重作用) */
.fb-reward-banner-top { display: block; text-align: center; background: linear-gradient(135deg, #FFF1A8 0%, #FFE066 100%); border: 1px solid #FFD600; border-radius: 12px; padding: 12px 16px; margin: 0 0 18px; font-size: 14px; font-weight: 700; color: #5A3A00; line-height: 1.3; box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 2px rgba(255,179,0,.18); }
.fb-reward-banner-top .num { font-family: var(--font-num); font-size: 17px; font-weight: 800; color: var(--color-price-lowest); margin: 0 3px 0 2px; }
.fb-reward-banner-top img { width: 20px; height: 20px; margin: 0 2px; vertical-align: -6px; }
/* 顶部 4 步任务卡(仿识货) */
.fb-task-card { background: var(--color-surface-card); border: 1px solid var(--color-divider); border-radius: 14px; padding: 14px 6px; margin: 12px 0 16px; display: flex; align-items: flex-start; justify-content: space-between; gap: 2px; }
.fb-task-step { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; min-width: 0; }
.fb-task-icon { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: var(--color-ink); }
.fb-task-text { font-size: 11px; color: var(--color-ink); font-weight: 500; text-align: center; line-height: 1.3; padding: 0 2px; }
.fb-task-text .line { display: block; white-space: nowrap; }
.fb-task-text .hl { color: var(--color-price-lowest); font-weight: 800; font-family: var(--font-num); }
.fb-task-arrow { color: var(--color-ink-muted); font-size: 14px; margin-top: 7px; flex-shrink: 0; font-weight: 400; }
/* 第 4 步「奖励」整体高亮为橙红色 */
.fb-task-step.reward .fb-task-icon svg { stroke: var(--color-ink); }
.fb-task-step.reward .fb-task-text { color: var(--color-ink); font-weight: 700; }
.fb-task-step.active .fb-task-icon svg { stroke: var(--color-price-lowest); }
.fb-task-step.active .fb-task-text { color: var(--color-price-lowest); font-weight: 700; }
.fb-sheet-sub { font-size: 12px; color: var(--color-ink-muted); text-align: center; margin-top: 4px; margin-bottom: 16px; }
.fb-sheet-sub .gold { color: var(--color-price-lowest); font-weight: 700; }
/* 奖励横幅 —— 强调「审核通过 = 1000 金币」这个核心信息 */
.fb-reward-banner { display: flex; align-items: center; justify-content: center; gap: 4px; background: linear-gradient(135deg, #FFF9DB 0%, #FFF3C4 100%); border: 1px solid #FFE066; border-radius: 12px; padding: 10px 14px; margin: 12px 0 16px; font-size: 13px; font-weight: 600; color: #5A3A00; line-height: 1.3; }
.fb-coin-icon { width: 18px; height: 18px; vertical-align: -4px; margin: 0 2px 0 4px; }
.fb-reward-banner .num { font-family: var(--font-num); font-size: 15px; font-weight: 800; color: var(--color-price-lowest); margin-right: 2px; }
.fb-target-card { background: var(--color-surface-card); border: 1px solid var(--color-divider); border-radius: 14px; padding: 14px; margin-bottom: 18px; }
.fb-target-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.fb-target-store { font-size: 15px; font-weight: 600; color: var(--color-ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.3; }
.fb-target-date { font-size: 12px; color: var(--color-ink-subtle); flex-shrink: 0; }
.fb-target-dish { font-size: 13px; color: var(--color-ink-secondary); margin-top: 6px; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.fb-target-dish.is-fold { position: relative; display: flex; align-items: center; overflow: visible; text-overflow: clip; white-space: normal; padding-right: 18px; cursor: pointer; user-select: none; min-width: 0; }
.fb-target-dish.is-fold:active { opacity: .55; }
.fb-target-dish-names { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.4; }
.fb-target-dish-count { margin-left: 4px; color: var(--color-ink-subtle); flex-shrink: 0; line-height: 1.4; }
.fb-target-dish-arrow { position: absolute; right: 0; top: 50%; width: 12px; height: 12px; color: var(--color-ink-disabled); display: flex; align-items: center; justify-content: center; transform: translateY(-50%); transition: transform .16s ease, color .16s ease; transform-origin: center; }
.fb-target-dish-arrow svg { width: 12px; height: 12px; display: block; stroke: currentColor; overflow: visible; }
.fb-target-dish.is-fold.expanded { align-items: flex-start; }
.fb-target-dish.is-fold.expanded .fb-target-dish-names { white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.45; }
.fb-target-dish.is-fold.expanded .fb-target-dish-count { padding-top: 1px; }
.fb-target-dish.is-fold.expanded .fb-target-dish-arrow { top: 3px; color: var(--color-ink-subtle); transform: rotate(180deg); }
.fb-target-lowest { display: flex; align-items: center; height: 22px; margin-top: 12px; padding-top: 12px; border-top: 0.5px solid var(--color-divider); }
.fb-target-pl-logo { width: 16px; height: 16px; border-radius: 3px; object-fit: cover; margin-right: 8px; flex-shrink: 0; }
.fb-target-pl-name { font-size: 14px; color: var(--color-ink); font-weight: 600; flex: 1; min-width: 0; }
.fb-target-pl-tag { height: 16px; padding: 0 5px; border-radius: 3px; background: var(--color-price-lowest-bg); color: var(--color-price-lowest); font-size: 10px; font-weight: 700; display: inline-flex; align-items: center; margin-right: 6px; }
.fb-target-pl-price { font-family: var(--font-num); font-size: 16px; font-weight: 700; color: var(--color-price-lowest); line-height: 1; }
.fb-field { margin-bottom: 16px; }
.fb-field-label { font-size: 13px; font-weight: 600; color: var(--color-ink); margin-bottom: 8px; }
.fb-field-label .req { color: var(--color-price-lowest); margin-left: 2px; }
.fb-field-hint { font-size: 12px; color: var(--color-ink-subtle); line-height: 1.4; margin: -4px 0 10px; }
.fb-price-input-row { display: flex; align-items: center; gap: 6px; padding: 12px 14px; border: 1px solid var(--color-divider-strong); border-radius: 14px; background: var(--color-surface-card); }
.fb-price-input-row:focus-within { border-color: var(--color-primary); }
.fb-price-input-prefix { font-size: 17px; font-weight: 700; color: var(--color-price-lowest); font-family: var(--font-num); }
.fb-price-input { flex: 1; border: 0; outline: 0; font-size: 17px; font-family: var(--font-num); font-weight: 700; color: var(--color-price-lowest); background: transparent; width: 100%; }
.fb-price-input::placeholder { color: var(--color-ink-disabled); font-weight: 400; }
.fb-platform-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.fb-platform-chip { height: 36px; border-radius: 999px; border: 1px solid var(--color-divider-strong); background: var(--color-surface-card); display: inline-flex; align-items: center; justify-content: center; gap: 5px; font-size: 13px; font-weight: 600; color: var(--color-ink-secondary); cursor: pointer; transition: all .15s; }
.fb-platform-chip img { width: 14px; height: 14px; border-radius: 3px; flex-shrink: 0; }
.fb-platform-chip.active { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); }
.fb-upload-row { display: flex; gap: 8px; }
.fb-upload-slot { width: 80px; height: 80px; border-radius: 10px; background: #F2F2F4; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; color: var(--color-ink-muted); font-size: 12px; cursor: pointer; transition: all .15s; position: relative; }
.fb-upload-slot:active { transform: scale(.96); }
.fb-upload-plus { width: 22px; height: 22px; position: relative; }
.fb-upload-plus::before, .fb-upload-plus::after { content: ''; position: absolute; background: var(--color-ink-muted); border-radius: 1.5px; }
.fb-upload-plus::before { left: 50%; top: 0; width: 2px; height: 22px; transform: translateX(-50%); }
.fb-upload-plus::after { top: 50%; left: 0; width: 22px; height: 2px; transform: translateY(-50%); }
.fb-upload-slot.uploaded { background: var(--color-price-lowest-bg); color: var(--color-price-lowest); }
.fb-upload-slot.uploaded .fb-upload-plus::before, .fb-upload-slot.uploaded .fb-upload-plus::after { background: var(--color-price-lowest); }
/* 示例截图 tile —— 放在上传槽左侧,引导用户对照上传 */
.fb-sample-slot { width: 80px; height: 80px; border-radius: 10px; background: linear-gradient(180deg, #FFF8E1 0%, #FFFDF5 100%); border: 1px solid #FFE082; position: relative; cursor: pointer; overflow: hidden; display: flex; flex-direction: column; transition: transform .15s ease; flex-shrink: 0; }
.fb-sample-slot:active { transform: scale(.96); }
.fb-sample-badge { position: absolute; top: 0; left: 0; background: #1A1A1A; color: var(--color-primary); font-size: 10px; font-weight: 700; line-height: 1; padding: 3px 6px 3px 5px; border-radius: 10px 0 8px 0; letter-spacing: .04em; z-index: 2; }
.fb-sample-zoom { position: absolute; top: 4px; right: 4px; width: 18px; height: 18px; border-radius: 50%; background: rgba(26,26,26,.7); display: flex; align-items: center; justify-content: center; z-index: 2; }
.fb-sample-zoom svg { width: 10px; height: 10px; }
.fb-sample-thumb { flex: 1; padding: 4px 6px 6px; display: flex; flex-direction: column; }
.fb-sample-thumb-bar { height: 8px; background: var(--color-platform-meituan-waimai); border-radius: 2px; margin-top: 12px; margin-bottom: 4px; }
.fb-sample-thumb-line { height: 4px; background: #E5E5E5; border-radius: 2px; margin-bottom: 3px; }
.fb-sample-thumb-line.s { width: 60%; }
.fb-sample-thumb-line.m { width: 80%; }
.fb-sample-thumb-price { font-family: var(--font-num); font-size: 14px; font-weight: 700; color: var(--color-price-lowest); line-height: 1; margin-top: 4px; letter-spacing: -.5px; }
.fb-sample-thumb-chip { display: inline-flex; align-items: center; background: var(--color-price-lowest-bg); color: var(--color-price-lowest); font-size: 8px; font-weight: 700; padding: 1px 3px; border-radius: 3px; margin-top: 3px; align-self: flex-start; line-height: 1.2; }
/* 上传字段下方提示 */
.fb-upload-callout { display: flex; gap: 6px; align-items: center; font-size: 11px; color: var(--color-ink-muted); margin-top: 10px; }
.fb-upload-callout svg { width: 12px; height: 12px; flex-shrink: 0; color: var(--color-primary-dark); }
.fb-field-hint .em { color: var(--color-price-lowest); font-weight: 600; }
/* 示例放大查看器 modal —— 【去框】absolute inset:0 在 fixed 的 fb-sheet 内仍贴满,无需改 */
.fb-sample-modal { position: absolute; inset: 0; background: rgba(26,26,26,.88); display: none; flex-direction: column; z-index: 20; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.fb-sample-modal.show { display: flex; }
.fb-sample-modal-header { height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; color: #fff; flex-shrink: 0; }
.fb-sample-modal-title { font-size: 15px; font-weight: 600; }
.fb-sample-modal-close { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,.15); display: flex; align-items: center; justify-content: center; cursor: pointer; }
.fb-sample-modal-close svg { width: 14px; height: 14px; }
.fb-sample-modal-body { flex: 1; padding: 8px 24px 24px; display: flex; flex-direction: column; gap: 16px; align-items: center; justify-content: flex-start; overflow-y: auto; }
.fb-sample-fullimg { width: 240px; border-radius: 14px; background: #fff; overflow: hidden; position: relative; box-shadow: 0 8px 24px rgba(0,0,0,.4); flex-shrink: 0; }
.fb-sample-fullimg .fi-header { height: 36px; background: var(--color-platform-meituan-waimai); display: flex; align-items: center; padding: 0 12px; font-size: 12px; font-weight: 700; color: var(--color-ink); }
.fb-sample-fullimg .fi-header img { width: 16px; height: 16px; border-radius: 3px; margin-right: 6px; }
.fb-sample-fullimg .fi-block { padding: 12px 12px 8px; border-bottom: 1px solid var(--color-divider); }
.fb-sample-fullimg .fi-shop { font-size: 13px; font-weight: 600; color: var(--color-ink); margin-bottom: 8px; }
.fb-sample-fullimg .fi-row { display: flex; justify-content: space-between; font-size: 12px; color: var(--color-ink-secondary); margin-bottom: 4px; }
.fb-sample-fullimg .fi-row .name { flex: 1; padding-right: 12px; }
.fb-sample-fullimg .fi-row .price { font-family: var(--font-num); color: var(--color-ink); }
.fb-sample-fullimg .fi-disc { display: flex; justify-content: space-between; font-size: 11px; color: var(--color-price-lowest); margin-top: 6px; }
.fb-sample-fullimg .fi-disc .price { font-family: var(--font-num); }
.fb-sample-fullimg .fi-total { padding: 12px; display: flex; justify-content: space-between; align-items: baseline; background: var(--color-surface-page); }
.fb-sample-fullimg .fi-total .lbl { font-size: 12px; color: var(--color-ink-muted); }
.fb-sample-fullimg .fi-total .val { font-family: var(--font-num); font-size: 22px; font-weight: 700; color: var(--color-price-lowest); }
.fb-sample-annot { position: absolute; border: 2px solid var(--color-primary); border-radius: 6px; pointer-events: none; box-shadow: 0 0 0 2px rgba(255,214,0,.2); }
.fb-sample-annot .num { position: absolute; top: -10px; right: -10px; width: 20px; height: 20px; background: var(--color-primary); color: var(--color-ink); border-radius: 50%; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; line-height: 1; }
.fb-sample-annot.a1 { top: 36px; left: 8px; right: 8px; height: 22px; }
.fb-sample-annot.a2 { bottom: 60px; left: 8px; right: 8px; height: 18px; }
.fb-sample-annot.a3 { bottom: 8px; left: 8px; right: 8px; height: 38px; }
.fb-sample-rules { width: 100%; max-width: 280px; background: rgba(255,255,255,.08); border-radius: 12px; padding: 12px 14px; color: #fff; flex-shrink: 0; }
.fb-sample-rules-title { font-size: 12px; font-weight: 700; color: var(--color-primary); margin-bottom: 8px; letter-spacing: .04em; }
.fb-sample-rules-item { font-size: 12px; line-height: 1.5; color: rgba(255,255,255,.92); display: flex; gap: 6px; margin-bottom: 4px; }
.fb-sample-rules-item .n { color: var(--color-primary); font-weight: 700; flex-shrink: 0; }
.fb-sample-rules-item .em { color: var(--color-primary); font-weight: 700; }
.fb-submit-btn { width: 100%; height: 48px; border-radius: 14px; border: 0; background: linear-gradient(180deg, #FFE066 0%, #FFD600 45%, #FFB300 100%); color: var(--color-on-primary); font-size: 17px; font-weight: 700; box-shadow: var(--shadow-cta-warm); cursor: pointer; transition: transform .12s; }
.fb-submit-btn:active { transform: scale(.98); }
.fb-submit-btn:disabled { background: var(--color-ink-disabled); box-shadow: none; cursor: not-allowed; color: #fff; }
/* —— 模拟 iOS 数字键盘(focus 价格输入框时升起)—— 【去框】absolute→fixed 贴视口底 */
.fb-keyboard { position: fixed; left: 0; right: 0; bottom: 0; height: 226px; background: #D1D5DB; padding: 6px 4px 8px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); gap: 6px; z-index: 12; transform: translateY(100%); transition: transform .28s cubic-bezier(.2,.8,.2,1); }
.fb-keyboard.show { transform: translateY(0); }
.fb-key { background: #FFFFFF; border-radius: 5px; display: flex; align-items: center; justify-content: center; font-size: 25px; font-weight: 400; color: #1A1A1A; cursor: pointer; box-shadow: 0 1px 0 rgba(0,0,0,.28); user-select: none; -webkit-user-select: none; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
.fb-key:active { background: #BCC0C6; transition: background 0s; }
.fb-key.fn { background: #ABB1BB; font-size: 17px; }
.fb-key.fn svg { display: block; }
/* sheet 在键盘升起时收缩到键盘上方 */
/* 键盘升起时,整个 fb-sheet 向上偏移让出键盘空间 */
.device.kb-up .fb-sheet { bottom: 226px; }
</style>
<script src="../shared/bridge.js"></script>
</head>
<body>
<div class="device time-compact">
<!-- status barApp 内有原生状态栏,已 display:none 隐藏,保留 DOM 防脚本引用报错) -->
<div class="status-bar">
<span>9:41</span>
<span class="right"></span>
</div>
<!-- 顶部筛选 tab(正常态显示,单选 / 搜索态自动隐藏) -->
<div class="hist-filter">
<div class="hist-filter-tab active" data-filter="all" onclick="onFilterClick(this)">全部记录</div>
<div class="hist-filter-tab" data-filter="ordered" onclick="onFilterClick(this)">已下单</div>
</div>
<!-- header:居中标题 + 右侧搜索;搜索态时被 .hist-search-bar 覆盖;单选态时左上「取消」 -->
<div class="hist-header">
<div class="hist-header-cancel" onclick="cancelSelect()" role="button">取消</div>
<button class="hist-header-back" type="button" onclick="goBackToProfile()" aria-label="返回我的页面">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15 6L9 12L15 18" stroke="#1A1A1A" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="hist-header-reports" type="button" onclick="goReports()" aria-label="查看上报记录">上报记录<span class="hist-header-reports-dot" style="display:none"></span></button>
<div class="hist-header-title">比价记录</div>
<div class="hist-header-search" onclick="enterSearchMode()" role="button" aria-label="搜索">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"></circle>
<path d="m20 20-3.5-3.5"></path>
</svg>
</div>
<!-- 搜索态 bar,默认 display:none.device.searching 时显示 -->
<div class="hist-search-bar">
<div class="hist-search-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true">
<circle cx="11" cy="11" r="7"></circle>
<path d="m20 20-3.5-3.5"></path>
</svg>
<input type="text" class="hist-search-input" id="histSearchInput" placeholder="搜店名 / 菜名" oninput="onSearchInput(this.value)" autocomplete="off">
<span class="hist-search-clear" onclick="clearSearch()" aria-label="清空"></span>
</div>
<span class="hist-search-cancel" onclick="exitSearchMode()">取消</span>
</div>
</div>
<div class="select-task-panel" aria-hidden="true">
<div class="fb-task-card">
<div class="fb-task-step active">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/></svg>
</div>
<div class="fb-task-text"><span class="line">选择要</span><span class="line">上报的记录</span></div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5l2.5-3.5h13L21 7.5v11.5a1.5 1.5 0 01-1.5 1.5h-15A1.5 1.5 0 013 19V7.5z"/><circle cx="12" cy="13" r="4"/></svg>
</div>
<div class="fb-task-text">补充低价<br>相关证明</div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M4.5 19c.5-3.2 2.4-5 4.8-5 1.1 0 2.1.3 2.8.9"/><path d="M14.5 17l2 2 4-4"/></svg>
</div>
<div class="fb-task-text">人工<br>审核</div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step reward">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8.5 7.5l3.5 4 3.5-4"/><path d="M8 13h8"/><path d="M8 16h8"/><path d="M12 11.5v6.5"/></svg>
</div>
<div class="fb-task-text">得 1000<br>金币奖励</div>
</div>
</div>
</div>
<!-- 滚动区 —— 列表由 JS 动态渲染进 #histList,6 张写死卡片已删除 -->
<div class="hist-scroll">
<!-- 搜索态:最近搜索(仅 .device.searching 且无输入时显示) -->
<div class="hist-search-history">
<div class="hist-search-history-title">
<span>最近搜索</span>
<span class="hist-search-history-clear" onclick="clearSearchHistory()">清除</span>
</div>
<div class="hist-search-history-chips" id="histSearchChips"></div>
<div class="hist-search-history-empty" id="histSearchEmpty" style="display:none">暂无搜索记录</div>
</div>
<!-- 动态列表挂载点(renderList 往这里写 renderHistItem 生成的卡片) -->
<div id="histList"></div>
<!-- 搜索无结果空态 -->
<div class="hist-empty">没有找到匹配的比价记录<br>试试其他关键词</div>
</div>
<!-- tab bar(已 display:none 隐藏,保留 DOM 防脚本引用报错) -->
<div class="tab-bar">
<div class="tab-item" onclick="go('home')"><span class="tab-icon"><svg class="i-outline" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12l9-8 9 8"></path><path d="M5 10v9a1 1 0 001 1h3v-5h6v5h3a1 1 0 001-1v-9"></path></svg><svg class="i-filled" width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M3 12l9-8 9 8" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M5 10v9a1 1 0 001 1h3v-5h6v5h3a1 1 0 001-1v-9" fill="#FFD600" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="tab-label">首页</span></div>
<div class="tab-item" id="homeTabWelfare" onclick="go('welfare')"><span class="welfare-tab-dot" aria-hidden="true"></span><span class="tab-icon" id="homeTabWelfareIcon"><svg class="i-outline" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="8" width="18" height="4" rx="1"></rect><path d="M12 8v12"></path><path d="M5 12v7a1 1 0 001 1h12a1 1 0 001-1v-7"></path><path d="M7.5 8C6 8 5 6.5 6 5.5S9 4 9.5 5.5c.4 1.2 2.5 2.5 2.5 2.5s2.1-1.3 2.5-2.5C15 4 17 4.5 18 5.5s-1 2.5-2.5 2.5"></path></svg><svg class="i-filled" width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="3" y="8" width="18" height="4" rx="1" fill="#FFD600" stroke="#1a1a1a" stroke-width="2"></rect><path d="M5 12v7a1 1 0 001 1h12a1 1 0 001-1v-7" fill="#FFD600" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 8v12" stroke="#1a1a1a" stroke-width="2"></path><path d="M7.5 8C6 8 5 6.5 6 5.5S9 4 9.5 5.5c.4 1.2 2.5 2.5 2.5 2.5s2.1-1.3 2.5-2.5C15 4 17 4.5 18 5.5s-1 2.5-2.5 2.5" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="tab-label">福利</span></div>
<div class="tab-item active" onclick="go('history')"><span class="tab-icon"><svg class="i-outline" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2.5"></rect><path d="M8 9h8"></path><path d="M8 13h8"></path><path d="M8 17h5"></path></svg><svg class="i-filled" width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="4" y="4" width="16" height="16" rx="2.5" fill="#FFD600" stroke="#1a1a1a" stroke-width="2"></rect><path d="M8 9h8" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round"></path><path d="M8 13h8" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round"></path><path d="M8 17h5" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round"></path></svg></span><span class="tab-label">记录</span></div>
<div class="tab-item" onclick="go('profile')"><span class="tab-icon"><svg class="i-outline" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"></circle><path d="M4 20c0-4 4-6 8-6s8 2 8 6"></path></svg><svg class="i-filled" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="8" r="4" fill="#FFD600" stroke="#1a1a1a" stroke-width="2"></circle><path d="M4 20c0-4 4-6 8-6s8 2 8 6" fill="#FFD600" stroke="#1a1a1a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg></span><span class="tab-label">我的</span></div>
</div>
<!-- 上报更低价顶部 CTA -->
<button class="fb-fab" onclick="enterSelectMode()" aria-label="上报更低价赚金币">
上报更低价<span class="fb-fab-badge">赚金币</span>
</button>
<!-- 单选模式底部 action bar -->
<div class="fb-actionbar">
<button class="fb-next-btn" id="fbNextBtn" disabled onclick="openFeedbackSheet()">下一步</button>
</div>
<!-- 反馈低价 独立页面(push 从右进入) -->
<div class="fb-sheet" id="fbSheet" role="dialog">
<div class="fb-page-header">
<div class="fb-page-back" onclick="closeFeedbackSheet()" role="button" aria-label="返回">
<svg width="24" height="24" 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>
</div>
<div class="fb-page-title">上报更低价</div>
<button class="fb-page-reports" type="button" onclick="goReports()" aria-label="查看上报记录">上报记录<span class="fb-page-reports-dot" style="display:none"></span></button>
</div>
<div class="fb-page-body">
<div class="fb-task-card">
<div class="fb-task-step">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/></svg>
</div>
<div class="fb-task-text"><span class="line">选择要</span><span class="line">上报的记录</span></div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step active">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5l2.5-3.5h13L21 7.5v11.5a1.5 1.5 0 01-1.5 1.5h-15A1.5 1.5 0 013 19V7.5z"/><circle cx="12" cy="13" r="4"/></svg>
</div>
<div class="fb-task-text">补充低价<br>相关证明</div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M4.5 19c.5-3.2 2.4-5 4.8-5 1.1 0 2.1.3 2.8.9"/><path d="M14.5 17l2 2 4-4"/></svg>
</div>
<div class="fb-task-text">人工<br>审核</div>
</div>
<span class="fb-task-arrow"></span>
<div class="fb-task-step reward">
<div class="fb-task-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8.5 7.5l3.5 4 3.5-4"/><path d="M8 13h8"/><path d="M8 16h8"/><path d="M12 11.5v6.5"/></svg>
</div>
<div class="fb-task-text">得 1000<br>金币奖励</div>
</div>
</div>
<div class="fb-target-card">
<div class="fb-target-head">
<div class="fb-target-store" id="fbTargetStore"></div>
<div class="fb-target-date" id="fbTargetDate"></div>
</div>
<div class="fb-target-dish" id="fbTargetDish"></div>
<div class="fb-target-lowest">
<img class="fb-target-pl-logo" id="fbTargetPlLogo" src="" alt="">
<span class="fb-target-pl-name" id="fbTargetPlatform"></span>
<span class="fb-target-pl-tag">原最低价</span>
<span class="fb-target-pl-price" id="fbTargetPrice"></span>
</div>
</div>
<div class="fb-field">
<div class="fb-field-label">填写您看到的更低价<span class="req">*</span></div>
<div class="fb-price-input-row">
<span class="fb-price-input-prefix">¥</span>
<input type="text" inputmode="decimal" autocomplete="off" class="fb-price-input" id="fbPriceInput" placeholder="0.00" readonly oninput="refreshSubmitBtn()">
</div>
</div>
<div class="fb-field">
<div class="fb-field-label">您是在哪里找到更低价的<span class="req">*</span></div>
<div class="fb-platform-grid">
<div class="fb-platform-chip" data-pf="meituan-waimai"><img src="logos/meituan.png" alt="">美团</div>
<div class="fb-platform-chip" data-pf="jd-waimai"><img src="logos/jd.png" alt="">京东</div>
<div class="fb-platform-chip" data-pf="taobao-shanguang"><img src="logos/taobao.png" alt="">淘宝</div>
</div>
</div>
<div class="fb-field">
<div class="fb-field-label">上传截图证明<span class="req">*</span></div>
<div class="fb-field-hint">需展示<span class="em">使用红包后的实付价</span>,方便审核通过</div>
<div class="fb-upload-row">
<div class="fb-sample-slot" onclick="openSampleModal()" role="button" aria-label="查看截图示例">
<div class="fb-sample-badge">示例</div>
<div class="fb-sample-zoom" aria-hidden="true">
<svg viewBox="0 0 10 10" fill="none"><circle cx="4" cy="4" r="3" stroke="#fff" stroke-width="1.2"/><path d="M6.5 6.5L9 9" stroke="#fff" stroke-width="1.2" stroke-linecap="round"/></svg>
</div>
<div class="fb-sample-thumb">
<div class="fb-sample-thumb-bar"></div>
<div class="fb-sample-thumb-line m"></div>
<div class="fb-sample-thumb-line s"></div>
<div class="fb-sample-thumb-price">¥12.80</div>
<div class="fb-sample-thumb-chip">已抵 ¥5</div>
</div>
</div>
<div class="fb-upload-slot" id="fbUploadSlot" onclick="onPickPhoto()">
<span class="fb-upload-plus" aria-hidden="true"></span>
<span class="fb-upload-text">点击添加</span>
</div>
<!-- 隐藏文件选择 input:点上传槽 → 触发它 → 原生 onShowFileChooser 弹系统相册多选(≤4 张) -->
<input type="file" accept="image/*" multiple id="fbFileInput" style="display:none" onchange="onPhotoFilesPicked(this)">
</div>
<div class="fb-upload-callout">
<svg viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.5"/><path d="M6 3v3.5M6 8.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<span>对照左侧示例上传,审核更快通过</span>
</div>
</div>
</div><!-- /.fb-page-body -->
<div class="fb-page-footer">
<button class="fb-submit-btn" id="fbSubmitBtn" disabled onclick="submitFeedback()">提交反馈</button>
</div>
<!-- 截图示例 放大查看器 -->
<div class="fb-sample-modal" id="fbSampleModal" role="dialog" aria-modal="true" aria-label="截图示例">
<div class="fb-sample-modal-header">
<div class="fb-sample-modal-title">什么样的截图能通过审核</div>
<div class="fb-sample-modal-close" onclick="closeSampleModal()" role="button" aria-label="关闭">
<svg viewBox="0 0 14 14" fill="none"><path d="M2 2l10 10M12 2L2 12" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
</div>
</div>
<div class="fb-sample-modal-body">
<div class="fb-sample-fullimg">
<div class="fi-header">
<img src="logos/meituan.png" alt="">美团 · 订单详情
</div>
<div class="fi-block">
<div class="fi-shop">塔斯汀汉堡(朝阳大悦城店)</div>
<div class="fi-row"><span class="name">香辣鸡腿堡套餐 ×1</span><span class="price">¥17.80</span></div>
<div class="fi-row"><span class="name">配送费</span><span class="price">¥0.00</span></div>
<div class="fi-disc"><span>新人红包</span><span class="price">-¥5.00</span></div>
</div>
<div class="fi-total"><span class="lbl">实付</span><span class="val">¥12.80</span></div>
<div class="fb-sample-annot a1"><div class="num">1</div></div>
<div class="fb-sample-annot a2"><div class="num">2</div></div>
<div class="fb-sample-annot a3"><div class="num">3</div></div>
</div>
<div class="fb-sample-rules">
<div class="fb-sample-rules-title">截图需同时包含</div>
<div class="fb-sample-rules-item"><span class="n"></span><span>商品名 / 店铺名清晰可见</span></div>
<div class="fb-sample-rules-item"><span class="n"></span><span>领取的红包 / 优惠券金额</span></div>
<div class="fb-sample-rules-item"><span class="n"></span><span><span class="em">使用红包后的实付价</span>(不是商品标价)</span></div>
</div>
</div>
</div>
</div>
<!-- 模拟 iOS 数字键盘(仅价格输入聚焦时显示) -->
<div class="fb-keyboard" id="fbKeyboard">
<div class="fb-key" data-k="1">1</div>
<div class="fb-key" data-k="2">2</div>
<div class="fb-key" data-k="3">3</div>
<div class="fb-key" data-k="4">4</div>
<div class="fb-key" data-k="5">5</div>
<div class="fb-key" data-k="6">6</div>
<div class="fb-key" data-k="7">7</div>
<div class="fb-key" data-k="8">8</div>
<div class="fb-key" data-k="9">9</div>
<div class="fb-key fn" data-k=".">.</div>
<div class="fb-key" data-k="0">0</div>
<div class="fb-key fn" data-k="DEL" aria-label="删除">
<svg width="26" height="20" viewBox="0 0 32 24" fill="none"><path d="M11 3 L29 3 A2 2 0 0 1 31 5 L31 19 A2 2 0 0 1 29 21 L11 21 L1 12 Z" stroke="#1A1A1A" stroke-width="1.5" fill="none"/><path d="M16 9 L24 15 M24 9 L16 15" stroke="#1A1A1A" stroke-width="1.5" stroke-linecap="round"/></svg>
</div>
</div>
<!-- 菜品清单 bottom sheet(点 dish-fold › 触发;本页改为卡内展开,此 sheet 仅备用) -->
<div class="menu-mask" id="menuMask" onclick="closeMenuSheet()"></div>
<div class="menu-sheet" id="menuSheet" role="dialog" aria-modal="true">
<div class="menu-grip"></div>
<div class="menu-title-row">
<div class="menu-title" id="menuTitle"></div>
<div class="menu-count" id="menuCount"></div>
</div>
<div class="menu-date" id="menuDate"></div>
<div class="menu-list" id="menuList"></div>
<div class="menu-close" onclick="closeMenuSheet()">关闭</div>
</div>
<!-- 未登录态:骨架模糊 + 居中登录卡,不展示任何真实数据 -->
<div class="hist-logout-skeleton" aria-hidden="true">
<div class="hist-sk-item"><div class="hist-sk-line w60"></div><div class="hist-sk-line w30 sm"></div><div class="hist-sk-cells"><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div></div><div class="hist-sk-line w50 sm"></div></div>
<div class="hist-sk-item"><div class="hist-sk-line w50"></div><div class="hist-sk-line w30 sm"></div><div class="hist-sk-cells"><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div></div><div class="hist-sk-line w60 sm"></div></div>
<div class="hist-sk-item"><div class="hist-sk-line w60"></div><div class="hist-sk-line w30 sm"></div><div class="hist-sk-cells"><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div><div class="hist-sk-cell"></div></div></div>
</div>
<div class="hist-logout-scrim" onclick="onEmptyLogin()"></div>
<div class="hist-empty-logout">
<div class="hist-login-card">
<div class="lk"><svg viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div>
<div class="empty-title">登录后查看你的比价记录</div>
<button class="empty-cta" onclick="onEmptyLogin()">去登录</button>
</div>
</div>
<!-- toast -->
<div class="toast" id="toast"></div>
</div>
<script>
/* ============================================================
* 数据层(照搬昨天手写版,已真机验证)
* FIXED_PLATFORMS / PLATFORM_ALIASES / resolvePlatformId /
* MOCK(浏览器预览)/ normalizeRecords / loadRecords
* ============================================================ */
// ===== 三个固定平台(顺序即网格顺序)。id 用客户端【规范 ID】,与原生 FIXED_PLATFORMS 一致 =====
// logo 用产物同级 logos/ 下的 png(原型路径 ../../assets/shared/logos/ → 拷到本地)
const FIXED_PLATFORMS = [
{ id: 'meituan-waimai', name: '美团', color: '#FFB300', logo: 'logos/meituan.png' },
{ id: 'taobao-shanguang', name: '淘宝', color: '#FF5000', logo: 'logos/taobao.png' },
{ id: 'jd-waimai', name: '京东', color: '#E1251B', logo: 'logos/jd.png' },
];
// ===== 平台别名归一化(照搬原生 PlatformIdMap.kt=====
// 后端 platform_id 是短名(meituan/taobao_flash/jd_waimai)、platform_name 是中文(美团/淘宝闪购/京东外卖),
// 都要先归一化成上面的规范 id,才能对上三个格子。找不到返回 null。
const 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;
}
// ===== 假数据(仅电脑浏览器预览用;装进 App 后走真接口)=====
// platformId 用后端真实会返回的【短名】,正好顺带验证上面的归一化生效。
// 覆盖:已下单/未下单、菜品折叠(≥4)/不折叠(≤3)、最低价在不同平台、某平台没比到价
const MOCK = [
{
id: 101, bestPriceCents: 3500, bestPlatformId: 'taobao-shanguang',
storeName: '麦当劳&麦咖啡(北京杨林DT店)', ordered: true,
items: [{name:'巨无霸'},{name:'薯条(大)'},{name:'可乐'},{name:'麦辣鸡翅'},{name:'圣代'}],
createdAt: '2026-06-12 14:30',
comparisonResults: [
{ platformId:'meituan', price:38.5, rank:2 },
{ platformId:'taobao_flash', price:35.0, rank:1 },
{ platformId:'jd_waimai', price:null },
],
},
{
id: 102, bestPriceCents: 1910, bestPlatformId: 'taobao-shanguang',
storeName: '麦稻中式健康菜(首都机场店)', ordered: false,
items: [{name:'秘制口水鸡'},{name:'干锅土豆片'},{name:'白米饭'}],
createdAt: '2026-06-11 19:05',
comparisonResults: [
{ platformId:'meituan', price:22.5, rank:2 },
{ platformId:'taobao_flash', price:19.1, rank:1 },
{ platformId:'jd_waimai', price:null },
],
},
{
id: 103, bestPriceCents: 1590, bestPlatformId: 'taobao-shanguang',
storeName: '瑞幸咖啡(望京SOHO店)', ordered: true,
items: [{name:'生椰拿铁'}],
createdAt: '2026-06-10 09:12',
comparisonResults: [
{ platformId:'meituan', price:16.9, rank:2 },
{ platformId:'taobao_flash', price:15.9, rank:1 },
{ platformId:'jd_waimai', price:18.0, rank:3 },
],
},
];
// ===== 数据加载:App 里走真接口,浏览器预览走假数据 =====
// 后端 JSON 是 snake_case(store_name / comparison_results / platform_id …),统一转成本页渲染用的字段名。
function normalizeRecords(items) {
return (items || []).map(function (r) {
return {
// id / bestPriceCents / bestPlatformId:上报流程要用——提交带记录 id、校验「比原最低价更低」要原价(分)
id: r.id,
bestPriceCents: r.best_price_cents,
bestPlatformId: r.best_platform_id,
storeName: r.store_name,
ordered: !!r.ordered,
items: r.items || [],
createdAt: String(r.created_at || '').replace('T', ' ').slice(0, 16),
comparisonResults: (r.comparison_results || []).map(function (c) {
return { platformId: c.platform_id, platformName: c.platform_name, price: c.price, rank: c.rank };
}),
};
});
}
// 同源部署:base 恒为空串,fetch 走相对路径 /api/v1/...(不依赖原生给域名)。
async function loadRecords() {
const hasBridge = window.SGBridge && typeof SGBridge.getToken === 'function';
if (hasBridge) {
const token = SGBridge.getToken();
if (!token) return null; // 未登录 → "登录后查看"
const base = '';
try {
const res = await fetch(base + '/api/v1/compare/records?limit=50',
{ headers: { 'Authorization': 'Bearer ' + token } });
if (res.status === 401) { SGBridge.requestLogin && SGBridge.requestLogin(); return null; }
const data = await res.json();
return normalizeRecords(data.items || []);
} catch (e) { /* 网络失败,落回预览数据 */ }
}
return MOCK; // 浏览器预览(无 Bridge
}
/* ============================================================
* 列表渲染(用原型的 class,把 6 张写死卡片改成动态生成)
* ============================================================ */
// 全局记录与状态。RECORDS===null 表示未登录态(由 loadRecords 返回 null 触发)
let RECORDS = [];
const expandedSet = new Set(); // 记录哪些卡片菜品已展开(按 record 在 RECORDS 中的下标)
function esc(s){ return String(s == null ? '' : s).replace(/[&<>"]/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); }
// 搜索高亮:用原型的 .search-hl 类(橙红加粗),命中关键词包一层
function highlight(text, q){
var safe = esc(text);
if (!q) return safe;
var qe = esc(q).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return safe.replace(new RegExp('(' + qe + ')', 'gi'), '<span class="search-hl">$1</span>');
}
// 三平台格:固定顺序 美团/淘宝/京东,按数据三态生成
// ① 有价正常;② rank==1 → .lowest + .hist-pl-corner「最低」;③ 无记录 → .no-store +「店家未入驻」
function renderPlatformCells(rec, q){
return FIXED_PLATFORMS.map(function (fp) {
var r = (rec.comparisonResults || []).find(function (x) {
return resolvePlatformId(x.platformId || x.platformName) === fp.id;
});
var hasPrice = r && r.price != null;
var head = '<div class="hist-pl-head">' +
'<img class="hist-pl-logo" src="' + fp.logo + '" alt=""><span class="hist-pl-name">' + fp.name + '</span></div>';
if (!hasPrice) {
// 态③:该平台无记录 → 店家未入驻
return '<div class="hist-pl-cell no-store">' + head +
'<div class="hist-pl-status">店家未入驻</div></div>';
}
var lowest = r.rank === 1;
var priceTxt = '¥' + Number(r.price).toFixed(2);
// 态②最低:.lowest + 角标;态①普通:仅价格
return '<div class="hist-pl-cell' + (lowest ? ' lowest' : '') + '">' + head +
(lowest ? '<span class="hist-pl-corner">最低</span>' : '') +
'<div class="hist-pl-price">' + priceTxt + '</div></div>';
}).join('');
}
// 菜品块:≤3 件用 .hist-dish-full(直接列名);≥4 件用 .hist-dish-fold(单行省略 + 等N件 + 箭头,点击卡内展开)
function renderDishBlock(rec, idx, q){
var items = rec.items || [];
var names = items.map(function(i){ return i.name; }).filter(Boolean);
if (names.length <= 3) {
return '<div class="hist-dish-full">' + highlight(names.join('、'), q) + '</div>';
}
var isOpen = expandedSet.has(idx);
return '<div class="hist-dish-fold" onclick="toggleDishExpand(event)" role="button" aria-expanded="' + (isOpen ? 'true' : 'false') + '">' +
'<span class="hist-dish-fold-names">' + highlight(names.join('、'), q) + '</span>' +
'<span class="hist-dish-fold-count">等 ' + names.length + ' 件</span>' +
'<span class="hist-dish-fold-arrow" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none"><path d="M3.2 4.7 6 7.3 8.8 4.7" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span>' +
'</div>';
}
// 单张卡片:结构/class 对齐原型,且把 dish 放在 platforms 之前、meta 放进 actions= 原型 time-compact 处理后的最终布局)
// data-idx 供搜索 / 折叠 / 单选回查 record
function renderHistItem(rec, idx, q){
var head = '<div class="hist-item-head">' +
'<div class="hist-store">' + highlight(rec.storeName || '比价', q) + '</div>' +
(rec.ordered ? '<span class="hist-ordered-chip">已下单</span>' : '') +
'</div>';
var meta = '<div class="hist-item-meta"><div class="hist-date">' + esc(rec.createdAt || '') + '</div></div>';
var dish = renderDishBlock(rec, idx, q);
var platforms = '<div class="hist-platforms">' + renderPlatformCells(rec, q) + '</div>';
// actions 区:time-compact 下 meta 在最左、再比一次按钮在最右(justify-content:space-between
var actions = '<div class="hist-card-actions">' + meta +
'<button class="hist-recompare-btn" type="button" onclick="onRecompare(event)">再比一次</button></div>';
var item = document.createElement('div');
item.className = 'hist-item';
item.setAttribute('data-idx', String(idx));
item.innerHTML = head + dish + platforms + actions;
return item;
}
// 渲染整个列表到 #histList。currentFilter='all'|'ordered'q=搜索关键词(搜索态由 onSearchInput 传)
var currentFilter = 'all';
function renderList(q){
var listEl = document.getElementById('histList');
if (!listEl) return;
listEl.innerHTML = '';
// 未登录态:由 setLoggedIn 控制骨架/登录卡显隐,这里不渲染真实数据
if (RECORDS === null) return;
// 单选模式下重渲染要保留 radio + 点击绑定,故渲染后若在 selecting 态需补挂
RECORDS.forEach(function (rec, idx) {
var node = renderHistItem(rec, idx, q || '');
// tab 过滤:已下单 tab 只显示 ordered
if (currentFilter === 'ordered' && !rec.ordered) node.classList.add('filter-hide-ordered');
listEl.appendChild(node);
});
// 应用筛选(用原型一致的 display 方式)
applyHistFilter(currentFilter);
// 渲染后若一张可见卡片都没有(无记录 / 该分类无结果 / 搜索无结果)→ 出空态文案,别留纯空白
var visibleCount = 0;
listEl.querySelectorAll('.hist-item').forEach(function (it) {
if (it.style.display !== 'none' && !it.classList.contains('filter-hide-ordered')) visibleCount++;
});
if (visibleCount === 0) {
var emptyMsg = (RECORDS.length === 0) ? '还没有比价记录'
: (q ? '没有找到匹配的记录' : '该分类下暂无记录');
listEl.insertAdjacentHTML('beforeend',
'<div class="hist-empty-tip" style="text-align:center;color:#999;font-size:14px;padding:64px 16px;line-height:1.6;">' + emptyMsg + '</div>');
}
// 若处于单选态,补挂 radio 与点击
if (document.querySelector('.device').classList.contains('selecting')) {
decorateItemsForSelect();
}
}
/* ============================================================
* 以下为原型脚本(导航占位 / 筛选 / 搜索 / 折叠 / 单选 / 反馈 sheet / 键盘),
* 改动点:①导航 go()/onRecompare() 改为占位;②筛选 applyHistFilter 适配动态卡片;
* ③搜索 onSearchInput 驱动动态列表并走 renderList 高亮;④折叠 toggleDishExpand 走 expandedSet 重渲染。
* ============================================================ */
// reportCta 变体(保留原型逻辑,默认 reward)
(function() {
var variant = 'reward';
try {
var q = new URLSearchParams(location.search).get('reportCta');
if (q === 'outline' || q === 'solid' || q === 'reward') variant = q;
} catch(e) {}
var device = document.querySelector('.device');
if (device) device.classList.add('cta-' + variant);
})();
// ?from=profile:隐藏底栏 + 显示左上返回(保留原型逻辑;底栏本就隐藏,这里仅控制返回箭头)
var historyFromProfile = false;
(function() {
try { historyFromProfile = new URLSearchParams(location.search).get('from') === 'profile'; } catch(e) {}
var device = document.querySelector('.device');
if (historyFromProfile && device) device.classList.add('profile-entry');
})();
// 返回「我的」:?from=profile 进来时顶栏返回箭头调原生 closePage 回上一页(WebViewScreen onBack→popBackStack);浏览器预览仅 log
function goBackToProfile() {
if (window.SGBridge && typeof SGBridge.closePage === 'function') { SGBridge.closePage(); }
else { console.log('[nav stub] goBackToProfile(浏览器预览)'); }
}
// 导航占位:原型内部跳转(home/profile/reports/welfare/comparing…)在 App 里走不通,统一打 log
function navigateStub(id) {
console.log('[nav stub] go("' + id + '")App 内由原生处理,H5 不跳转)');
}
window.go = navigateStub;
window.goBack = function() { console.log('[nav stub] goBack'); };
// 「上报记录」入口:App 内经原生桥跳上报记录页(REPORT_RECORDS);浏览器预览(无原生 navigate)走 mock。
function goReports() {
if (window.SGBridge && SGBridge.navigate) { SGBridge.navigate('reportRecords'); }
else { go('reports'); }
}
window.goReports = goReports;
// ========== 筛选 tab ==========
function onFilterClick(tab) {
if (document.querySelector('.device').classList.contains('logged-out')) { onEmptyLogin(); return; }
var allTabs = document.querySelectorAll('.hist-filter-tab');
var prev = document.querySelector('.hist-filter-tab.active');
if (prev === tab) return;
var prevIdx = Array.prototype.indexOf.call(allTabs, prev);
var newIdx = Array.prototype.indexOf.call(allTabs, tab);
var direction = newIdx > prevIdx ? 1 : -1; // 1 = 切到右 tab (内容从右进), -1 = 切到左 tab
allTabs.forEach(function(t) { t.classList.remove('active'); });
tab.classList.add('active');
currentFilter = tab.dataset.filter;
slideHistFilter(tab.dataset.filter, direction);
}
// 应用 filter:动态卡片上按 ordered chip 判定显隐(与原型同款 display 切换)
function applyHistFilter(filter) {
document.querySelectorAll('.hist-item').forEach(function(item) {
var hasOrdered = !!item.querySelector('.hist-ordered-chip');
if (filter === 'ordered') item.style.display = hasOrdered ? '' : 'none';
else item.style.display = ''; // 'all'
});
}
// 切换 filter 时的横向 slide + fade 动画(替代瞬间 display 切换)
function slideHistFilter(filter, direction) {
var scroll = document.querySelector('.hist-scroll');
var dist = 36;
// 旧内容朝反方向滑出 + 淡出
scroll.style.transition = 'transform .18s ease-out, opacity .18s ease-out';
scroll.style.transform = 'translateX(' + (-direction * dist) + 'px)';
scroll.style.opacity = '0';
setTimeout(function() {
applyHistFilter(filter);
scroll.scrollTop = 0;
// 把容器瞬移到反方向,准备从那里滑回 0
scroll.style.transition = 'none';
scroll.style.transform = 'translateX(' + (direction * dist) + 'px)';
void scroll.offsetHeight; // 强制 reflow
// 新内容滑入 + 淡入
scroll.style.transition = 'transform .24s cubic-bezier(.2,.8,.2,1), opacity .22s ease-in';
scroll.style.transform = 'translateX(0)';
scroll.style.opacity = '1';
}, 180);
}
// 左右滑动切换 tab —— 触摸 + 鼠标拖动都支持
(function() {
var scroll = document.querySelector('.hist-scroll');
if (!scroll) return;
var startX = null, startY = null;
function onStart(x, y) { startX = x; startY = y; }
function onEnd(x, y) {
if (startX === null) return;
var dx = x - startX, dy = y - startY;
startX = null; startY = null;
if (Math.abs(dx) < 60 || Math.abs(dy) > 40) return; // 阈值 + 垂直滑动忽略
var tabs = Array.prototype.slice.call(document.querySelectorAll('.hist-filter-tab'));
var active = document.querySelector('.hist-filter-tab.active');
var idx = tabs.indexOf(active);
if (dx < 0 && idx < tabs.length - 1) onFilterClick(tabs[idx + 1]); // 左滑 → 下一个 tab
else if (dx > 0 && idx > 0) onFilterClick(tabs[idx - 1]); // 右滑 → 上一个 tab
}
scroll.addEventListener('touchstart', function(e) { if (e.touches.length === 1) onStart(e.touches[0].clientX, e.touches[0].clientY); }, { passive: true });
scroll.addEventListener('touchend', function(e) { onEnd(e.changedTouches[0].clientX, e.changedTouches[0].clientY); });
// 鼠标拖动模拟:mousedown 在 scroll 起,mouseup 绑到 window 上(防止鼠标拖出 device 外丢失事件)
scroll.addEventListener('mousedown', function(e) {
onStart(e.clientX, e.clientY);
var up = function(ev) {
onEnd(ev.clientX, ev.clientY);
window.removeEventListener('mouseup', up);
};
window.addEventListener('mouseup', up);
});
})();
// ========== 搜索 ==========
var SEARCH_HISTORY_KEY = 'proto_history_search_recent';
function enterSearchMode() {
var device = document.querySelector('.device');
if (device.classList.contains('logged-out')) { onEmptyLogin(); return; }
device.classList.add('searching');
device.classList.remove('has-input', 'no-results');
renderSearchHistory();
var input = document.getElementById('histSearchInput');
input.value = '';
setTimeout(function() { input.focus(); }, 80);
}
function exitSearchMode() {
var device = document.querySelector('.device');
device.classList.remove('searching', 'has-input', 'no-results');
document.getElementById('histSearchInput').value = '';
// 退出搜索:清掉高亮,按当前 filter 重渲染列表
renderList('');
document.getElementById('histSearchInput').blur();
}
function clearSearch() {
var input = document.getElementById('histSearchInput');
input.value = '';
onSearchInput('');
input.focus();
}
// 搜索输入:驱动动态列表(搜店名 + 菜名),命中高亮、无结果切空态
function onSearchInput(value) {
var device = document.querySelector('.device');
var q = (value || '').trim();
var qLower = q.toLowerCase();
if (qLower.length === 0) {
device.classList.remove('has-input', 'no-results');
renderList(''); // 清空高亮、恢复全列表
return;
}
device.classList.add('has-input');
// 重渲染(带高亮),再按命中加 search-hide
renderList(q);
var hitCount = 0;
document.querySelectorAll('.hist-item').forEach(function(item) {
var store = (item.querySelector('.hist-store') || {}).textContent || '';
var dishFull = (item.querySelector('.hist-dish-full') || {}).textContent || '';
var dishFoldNames = (item.querySelector('.hist-dish-fold-names') || {}).textContent || '';
var haystack = (store + ' ' + dishFull + ' ' + dishFoldNames).toLowerCase();
if (haystack.indexOf(qLower) >= 0) {
item.classList.remove('search-hide');
hitCount++;
} else {
item.classList.add('search-hide');
}
});
device.classList.toggle('no-results', hitCount === 0);
}
function commitSearchHistory(term) {
var t = (term || '').trim();
if (!t) return;
var list = readSearchHistory();
// 去重 + 置顶
list = [t].concat(list.filter(function(x) { return x !== t; })).slice(0, 8);
try { localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(list)); } catch(e) {}
}
function readSearchHistory() {
try {
var raw = localStorage.getItem(SEARCH_HISTORY_KEY);
return raw ? JSON.parse(raw) : [];
} catch(e) { return []; }
}
function renderSearchHistory() {
var list = readSearchHistory();
var chipsEl = document.getElementById('histSearchChips');
var emptyEl = document.getElementById('histSearchEmpty');
if (list.length === 0) {
chipsEl.innerHTML = '';
emptyEl.style.display = 'block';
return;
}
emptyEl.style.display = 'none';
chipsEl.innerHTML = list.map(function(term) {
var safe = term.replace(/</g, '&lt;');
return '<div class="hist-search-chip" onclick="applySearchTerm(\'' + safe.replace(/'/g, "&#39;") + '\')">' + safe + '</div>';
}).join('');
}
function applySearchTerm(term) {
var input = document.getElementById('histSearchInput');
input.value = term;
onSearchInput(term);
commitSearchHistory(term);
}
function clearSearchHistory() {
try { localStorage.removeItem(SEARCH_HISTORY_KEY); } catch(e) {}
renderSearchHistory();
}
// 输入框失焦或回车时,把当前关键词写入历史
document.addEventListener('DOMContentLoaded', function() {
var input = document.getElementById('histSearchInput');
if (!input) return;
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { commitSearchHistory(input.value); renderSearchHistory(); input.blur(); }
});
input.addEventListener('blur', function() {
if (input.value.trim()) commitSearchHistory(input.value);
});
});
// 点击未安装 cell → 引导下载该平台 APP(本页三态无 not-installed,保留函数兜底)
// 单选模式下不响应,让事件冒泡到 hist-item 选中
function onDownloadPlatform(platformId) {
var device = document.querySelector('.device');
if (device && device.classList.contains('selecting')) return;
var name = { 'meituan-waimai': '美团', 'taobao-shanguang': '淘宝', 'jd-waimai': '京东' }[platformId] || platformId;
showToast('去下载' + name);
}
// 再比一次:App 里走原生重新比价占位
function onRecompare(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
var device = document.querySelector('.device');
if (device && device.classList.contains('selecting')) return;
console.log('[stub] onRecompareApp 内由原生重新发起比价)');
showToast('正在再比一次');
}
// ========== 登录态 ==========
// 本页登录态以「有无 token」为准(loadRecords 返回 null = 未登录)。
// 同时保留 logout-toggle demo 钩子(仅浏览器预览用,App 内无此按钮)。
function setLoggedIn(loggedIn) {
var device = document.querySelector('.device');
if (!device) return;
if (loggedIn) device.classList.remove('logged-out');
else {
device.classList.add('logged-out');
device.classList.remove('selecting', 'searching', 'has-input', 'no-results');
}
}
function onEmptyLogin() {
// App 里点「去登录」交给原生(或 onUnauthorized 已触发跳登录)
console.log('[stub] onEmptyLoginApp 内由原生引导登录)');
if (window.SGBridge && typeof SGBridge.requestLogin === 'function') {
try { SGBridge.requestLogin(); } catch(e) {}
}
}
var toastTimer = null;
function showToast(msg) {
var t = document.getElementById('toast');
if (!t) return;
t.textContent = msg;
t.classList.add('show');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(function() { t.classList.remove('show'); }, 1500);
}
// ========== 菜品清单 bottom sheet(本页改卡内展开,sheet 仅保留 DOMopenMenuSheet 兜底)==========
function openMenuSheet() { /* 本页用卡内展开,不再用底部 sheet */ }
function closeMenuSheet() {
document.getElementById('menuMask').classList.remove('show');
document.getElementById('menuSheet').classList.remove('show');
}
function setDishExpanded(item, expanded) {
item.classList.toggle('dish-expanded', expanded);
var trigger = item.querySelector('.hist-dish-fold');
if (trigger) trigger.setAttribute('aria-expanded', expanded ? 'true' : 'false');
}
// 菜品折叠:点 dish-fold 在卡内展开 / 收起。走 expandedSet 记状态(重渲染也保留),手风琴式互斥。
function toggleDishExpand(event) {
if (event) event.stopPropagation();
var trigger = event && event.currentTarget;
var item = trigger && trigger.closest ? trigger.closest('.hist-item') : null;
if (!item) return;
var device = document.querySelector('.device');
if (device && device.classList.contains('selecting')) return;
var idx = parseInt(item.getAttribute('data-idx'), 10);
var shouldExpand = !item.classList.contains('dish-expanded');
// 手风琴:先收起其它已展开的
expandedSet.clear();
document.querySelectorAll('.hist-item.dish-expanded').forEach(function(openItem) {
if (openItem !== item) setDishExpanded(openItem, false);
});
setDishExpanded(item, shouldExpand);
if (shouldExpand && !isNaN(idx)) expandedSet.add(idx);
}
function setFeedbackTargetDish(dishFull, dishFold) {
var dishEl = document.getElementById('fbTargetDish');
if (!dishEl) return;
dishEl.className = 'fb-target-dish';
dishEl.innerHTML = '';
dishEl.onclick = null;
dishEl.removeAttribute('role');
dishEl.removeAttribute('aria-expanded');
if (dishFull) {
dishEl.textContent = dishFull.textContent.trim();
return;
}
if (!dishFold) return;
var namesText = ((dishFold.querySelector('.hist-dish-fold-names') || {}).textContent || '').trim();
var countText = ((dishFold.querySelector('.hist-dish-fold-count') || {}).textContent || '').trim();
dishEl.classList.add('is-fold');
dishEl.setAttribute('role', 'button');
dishEl.setAttribute('aria-expanded', 'false');
dishEl.onclick = toggleFeedbackTargetDish;
var names = document.createElement('span');
names.className = 'fb-target-dish-names';
names.textContent = namesText;
dishEl.appendChild(names);
if (countText) {
var count = document.createElement('span');
count.className = 'fb-target-dish-count';
count.textContent = countText;
dishEl.appendChild(count);
}
var arrow = document.createElement('span');
arrow.className = 'fb-target-dish-arrow';
arrow.setAttribute('aria-hidden', 'true');
arrow.innerHTML = '<svg viewBox="0 0 12 12" fill="none"><path d="M3.2 4.7 6 7.3 8.8 4.7" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
dishEl.appendChild(arrow);
}
function toggleFeedbackTargetDish(event) {
if (event) event.stopPropagation();
var dishEl = document.getElementById('fbTargetDish');
if (!dishEl || !dishEl.classList.contains('is-fold')) return;
var expanded = !dishEl.classList.contains('expanded');
dishEl.classList.toggle('expanded', expanded);
dishEl.setAttribute('aria-expanded', expanded ? 'true' : 'false');
}
// ========== 反馈低价 · 单选模式 ==========
var selectMode = false;
var selectedItem = null; // 选中卡片的 DOM(驱动 radio 高亮 / 下一步可用)
var selectedRecord = null; // 选中卡片对应的 record 数据(从 RECORDS[data-idx] 回查;提交/校验用它,不再从 DOM 抠)
var selectedPlatform = null;
// 是否由「我的 · 上报更低价」深链进入(?report=1):决定取消时返回「我的」而非停留在记录页
var cameFromProfile = new URLSearchParams(location.search).get('report') === '1';
// 给当前所有 .hist-item 挂上 radio + 选中点击(渲染后调用;重渲染也要补挂)
function decorateItemsForSelect() {
document.querySelectorAll('.hist-item').forEach(function(item) {
if (!item.querySelector('.hist-item-radio')) {
var radio = document.createElement('div');
radio.className = 'hist-item-radio';
item.appendChild(radio);
}
item.classList.remove('selected');
item.onclick = function() { onItemSelect(item); };
});
}
function enterSelectMode() {
if (document.querySelector('.device').classList.contains('logged-out')) { onEmptyLogin(); return; }
selectMode = true;
selectedItem = null;
document.querySelector('.device').classList.add('selecting');
decorateItemsForSelect();
refreshNextBtn();
}
// 取消上报:深链(从「我的」进入)则返回「我的」,否则仅退出选择态停留在记录页
function cancelSelect() {
if (cameFromProfile) { goBackToProfile(); return; }
exitSelectMode();
}
function exitSelectMode() {
selectMode = false;
selectedItem = null;
selectedRecord = null;
document.querySelector('.device').classList.remove('selecting');
document.querySelectorAll('.hist-item').forEach(function(item) {
item.classList.remove('selected');
item.onclick = null;
});
}
function onItemSelect(item) {
document.querySelectorAll('.hist-item').forEach(function(i) { i.classList.remove('selected'); });
item.classList.add('selected');
selectedItem = item;
refreshNextBtn();
}
function refreshNextBtn() {
var btn = document.getElementById('fbNextBtn');
if (btn) btn.disabled = !selectedItem;
}
// ========== 反馈 bottom-sheet ==========
var photoUploaded = false;
var selectedFiles = []; // 已选截图 File 列表(≤4),提交时压缩后逐张 append
function openFeedbackSheet() {
if (!selectedItem) return;
// 从卡片的 data-idx 回查 record(不再从 DOM textContent 抠,拿不到 id/bestPriceCents
var idx = parseInt(selectedItem.getAttribute('data-idx'), 10);
selectedRecord = (RECORDS && idx >= 0) ? RECORDS[idx] : null;
if (!selectedRecord) return;
// 展示沿用原逻辑:仍从选中卡片的 DOM 取店名/日期/菜品/最低价行(纯展示,无须 id)
var store = (selectedItem.querySelector('.hist-store') || {}).textContent || '';
var date = (selectedItem.querySelector('.hist-date') || {}).textContent || '';
// 菜品:dish-full 普通展示;dish-fold 保留「等 N 件」并支持在当前卡片内展开
var dishFull = selectedItem.querySelector('.hist-dish-full');
var dishFold = selectedItem.querySelector('.hist-dish-fold');
var lowestRow = selectedItem.querySelector('.hist-pl-cell.lowest');
var price = lowestRow ? (lowestRow.querySelector('.hist-pl-price') || {}).textContent || '' : '';
var platform = lowestRow ? (lowestRow.querySelector('.hist-pl-name') || {}).textContent || '' : '';
var plLogoEl = lowestRow ? lowestRow.querySelector('.hist-pl-logo') : null;
var plLogoSrc = plLogoEl ? plLogoEl.src : '';
document.getElementById('fbTargetStore').textContent = store;
document.getElementById('fbTargetDate').textContent = date;
setFeedbackTargetDish(dishFull, dishFold);
document.getElementById('fbTargetPrice').textContent = price;
document.getElementById('fbTargetPlatform').textContent = platform;
var plLogoImg = document.getElementById('fbTargetPlLogo');
if (plLogoSrc) { plLogoImg.src = plLogoSrc; plLogoImg.style.display = ''; }
else { plLogoImg.style.display = 'none'; }
// 重置表单
var priceInput = document.getElementById('fbPriceInput');
priceInput.value = '';
photoUploaded = false;
selectedPlatform = null;
resetSelectedFiles();
document.querySelectorAll('.fb-platform-chip').forEach(function(c) { c.classList.remove('active'); });
refreshSubmitBtn();
// 显示
document.getElementById('fbSheet').classList.add('show');
// 进入即拉起模拟键盘 + 聚焦价格输入框
showKeyboard();
}
function closeFeedbackSheet() {
document.getElementById('fbSheet').classList.remove('show');
hideKeyboard();
}
// ===== 截图示例 modal =====
function openSampleModal() {
hideKeyboard();
document.getElementById('fbSampleModal').classList.add('show');
}
function closeSampleModal() {
document.getElementById('fbSampleModal').classList.remove('show');
}
// ===== 模拟键盘 =====
function showKeyboard() {
var device = document.querySelector('.device');
device.classList.add('kb-up');
document.getElementById('fbKeyboard').classList.add('show');
var priceInput = document.getElementById('fbPriceInput');
// 真机上 focus 会调起系统键盘,桌面 demo 上只为了显示焦点描边
try { priceInput.focus({ preventScroll: true }); } catch(e) { priceInput.focus(); }
}
function hideKeyboard() {
var device = document.querySelector('.device');
device.classList.remove('kb-up');
document.getElementById('fbKeyboard').classList.remove('show');
if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
}
// 模拟键盘按键 → 写入价格输入框
document.querySelectorAll('.fb-key').forEach(function(key) {
key.addEventListener('click', function(e) {
e.preventDefault();
var input = document.getElementById('fbPriceInput');
var k = key.dataset.k;
var v = input.value || '';
if (k === 'DEL') {
input.value = v.slice(0, -1);
} else if (k === '.') {
if (v.indexOf('.') === -1) input.value = v + '.';
} else {
// 数字键
var next = (v === '0') ? k : v + k; // 替代前导 0
var dot = next.indexOf('.');
if (!(dot >= 0 && next.length - dot - 1 > 2)) input.value = next; // 小数点后最多 2 位
}
refreshSubmitBtn();
});
});
document.querySelectorAll('.fb-platform-chip').forEach(function(chip) {
chip.addEventListener('click', function() {
document.querySelectorAll('.fb-platform-chip').forEach(function(c) { c.classList.remove('active'); });
chip.classList.add('active');
selectedPlatform = chip.dataset.pf;
refreshSubmitBtn();
});
});
function refreshSubmitBtn() {
var price = parseFloat(document.getElementById('fbPriceInput').value);
// 价格 + 平台 + 截图 三必填
var ok = price > 0 && selectedPlatform && photoUploaded;
document.getElementById('fbSubmitBtn').disabled = !ok;
}
// 点上传槽 → 触发隐藏 file input(真机由原生 onShowFileChooser 接管,弹系统相册多选)
function onPickPhoto() {
var input = document.getElementById('fbFileInput');
if (input) input.click();
}
// 上传槽缩略图里已有的 objectURL,重置/重选时要 revoke 防内存泄漏
var _thumbUrls = [];
function _revokeThumbUrls() {
_thumbUrls.forEach(function (u) { try { URL.revokeObjectURL(u); } catch (e) {} });
_thumbUrls = [];
}
// 清空已选图(打开 sheet / 提交成功后调用)
function resetSelectedFiles() {
selectedFiles = [];
photoUploaded = false;
_revokeThumbUrls();
var input = document.getElementById('fbFileInput');
if (input) input.value = ''; // 清掉 input 的 files,下次选同图也能触发 change
renderUploadSlot();
}
// file input 选完回调:取 files(≤4,超出截断并提示)→ 存全局 → 渲染缩略图
function onPhotoFilesPicked(input) {
var files = Array.prototype.slice.call(input.files || []);
if (files.length === 0) return; // 用户取消选择,保持原状
var truncated = false;
if (files.length > 4) { files = files.slice(0, 4); truncated = true; }
selectedFiles = files;
photoUploaded = selectedFiles.length > 0;
renderUploadSlot();
refreshSubmitBtn();
if (truncated) showToast('最多上传 4 张');
}
// 渲染上传槽:有图 → 显示第一张缩略图 + 已选张数;无图 → 复原「点击添加」
function renderUploadSlot() {
var slot = document.getElementById('fbUploadSlot');
if (!slot) return;
_revokeThumbUrls();
if (selectedFiles.length > 0) {
var url = URL.createObjectURL(selectedFiles[0]);
_thumbUrls.push(url);
slot.classList.add('uploaded');
// 缩略图铺满槽 + 右下角张数角标(行内样式即可,沿用 80x80 槽尺寸)
slot.innerHTML =
'<img src="' + url + '" alt="" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:10px;">' +
'<span style="position:absolute;right:3px;bottom:3px;padding:1px 6px;border-radius:8px;background:rgba(0,0,0,.6);color:#fff;font-size:11px;font-weight:600;line-height:1.5;">' + selectedFiles.length + '/4</span>';
} else {
slot.classList.remove('uploaded');
slot.innerHTML = '<span class="fb-upload-plus" aria-hidden="true"></span><span class="fb-upload-text">点击添加</span>';
}
}
// —— 截图压缩:把手机原图(可能几 MB)经 canvas 缩到最长边 ≤1280、JPEG ~0.8 转 Blob,避免上传超时 ——
var _COMPRESS_MAX_EDGE = 1280;
var _COMPRESS_QUALITY = 0.8;
function compressImage(file) {
return new Promise(function (resolve) {
// 非图片或不支持时,原样回退(字段名仍叫 images)
if (!file || !file.type || file.type.indexOf('image/') !== 0) { resolve(file); return; }
var url = URL.createObjectURL(file);
var img = new Image();
img.onload = function () {
try {
var w = img.naturalWidth || img.width;
var h = img.naturalHeight || img.height;
var scale = Math.min(1, _COMPRESS_MAX_EDGE / Math.max(w, h)); // 只缩不放大
var cw = Math.max(1, Math.round(w * scale));
var ch = Math.max(1, Math.round(h * scale));
var canvas = document.createElement('canvas');
canvas.width = cw; canvas.height = ch;
canvas.getContext('2d').drawImage(img, 0, 0, cw, ch);
canvas.toBlob(function (blob) {
URL.revokeObjectURL(url);
resolve(blob || file); // toBlob 失败兜底用原 file
}, 'image/jpeg', _COMPRESS_QUALITY);
} catch (e) { URL.revokeObjectURL(url); resolve(file); }
};
img.onerror = function () { URL.revokeObjectURL(url); resolve(file); }; // 解码失败兜底原 file
img.src = url;
});
}
// 提交反馈:真接 POST /api/v1/reportmultipart
var _submitting = false;
async function submitFeedback() {
var btn = document.getElementById('fbSubmitBtn');
if (btn.disabled || _submitting) return;
if (!selectedRecord) { showToast('请先选择一条记录'); return; }
var priceYuan = parseFloat(document.getElementById('fbPriceInput').value);
if (!(priceYuan > 0)) { showToast('请填写更低价'); return; }
// 校验:上报价(元)必须 < 原最低价(bestPriceCents/100),否则拦下
var bestYuan = (selectedRecord.bestPriceCents != null) ? selectedRecord.bestPriceCents / 100 : null;
if (bestYuan != null && !(priceYuan < bestYuan)) {
showToast('上报价需低于原最低价 ¥' + bestYuan.toFixed(2));
return;
}
// 平台映射:chip 的 data-pf 已是规范 id,仍走 resolvePlatformId 归一化兜底
var reportedPlatformId = resolvePlatformId(selectedPlatform) || selectedPlatform;
if (!reportedPlatformId) { showToast('请选择平台'); return; }
if (!selectedFiles || selectedFiles.length === 0) { showToast('请上传截图证明'); return; }
// 浏览器预览(无 Bridge):保留占位行为,不真发
var hasBridge = window.SGBridge && typeof SGBridge.getToken === 'function';
if (!hasBridge) {
console.log('[stub] submitFeedback(浏览器预览,无 Bridge,不真发)', {
comparison_record_id: selectedRecord.id,
reported_platform_id: reportedPlatformId,
reported_price: priceYuan,
images: selectedFiles.length
});
closeFeedbackSheet();
exitSelectMode();
resetSelectedFiles();
setTimeout(function () { showToast('感谢您的反馈'); }, 220);
return;
}
// 真机提交:防重复 + 按钮置灰文案「提交中…」
_submitting = true;
var oldText = btn.textContent;
btn.disabled = true;
btn.textContent = '提交中…';
try {
var token = SGBridge.getToken();
var base = '';
// 逐张压缩
var blobs = [];
for (var i = 0; i < selectedFiles.length; i++) {
blobs.push(await compressImage(selectedFiles[i]));
}
// 组 FormData:字段名按后端约定(reported_price 是「元」,images 逐张 append、字段名都叫 images
var fd = new FormData();
fd.append('comparison_record_id', String(selectedRecord.id));
fd.append('reported_platform_id', reportedPlatformId);
fd.append('reported_price', String(priceYuan));
blobs.forEach(function (b, i) {
var fname = 'report_' + (i + 1) + '.jpg';
fd.append('images', b, fname);
});
// fetch:只放 Authorization,不手动设 Content-Type(让浏览器自动带 multipart boundary
var res = await fetch(base + '/api/v1/report', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token },
body: fd
});
if (res.ok) {
closeFeedbackSheet();
exitSelectMode();
resetSelectedFiles();
setTimeout(function () { showToast('感谢您的反馈'); }, 220);
} else {
var detail = '';
try { var j = await res.json(); detail = (j && j.detail) ? j.detail : ''; } catch (e) {}
showToast(detail || ('提交失败(' + res.status + '),请稍后再试'));
}
} catch (e) {
showToast('网络异常,提交失败');
} finally {
_submitting = false;
btn.textContent = oldText;
refreshSubmitBtn(); // 由三必填条件重新决定按钮可用
}
}
// ========== 启动:拉数据 → 渲染 ==========
(async function init(){
RECORDS = await loadRecords();
// RECORDS===null 表示未登录(无 token)→ 进未登录态(骨架 + 登录卡)
setLoggedIn(RECORDS !== null);
// 从「我的 · 上报与比价」深链进入:登录态下自动进单选上报
var autoReport = false;
try { autoReport = new URLSearchParams(location.search).get('report') === '1'; } catch(e) {}
renderList('');
if (autoReport && RECORDS !== null) enterSelectMode();
})();
</script>
<script>
(function() {
function getTodayKey() {
var d = new Date();
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
}
function updateWelfareCheckinTabDot() {
var tab = document.getElementById('homeTabWelfare');
if (!tab) return;
var done = false;
try { done = localStorage.getItem('proto_checkinDoneDate') === getTodayKey(); } catch(e) {}
tab.classList.toggle('has-welfare-dot', !done);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateWelfareCheckinTabDot);
else updateWelfareCheckinTabDot();
})();
</script>
</body>
</html>