feat(h5): 首页「上次比价」横幅接真实 /api/v1/compare/records(4 分钟新鲜窗口)

删写死 mock(DEFAULT_LAST_COMPARE_RECORD + localStorage 读),改带 Bearer token 拉真实比价记录;取最近一条 status=success 且 created_at 在 4 分钟内才显示(对齐原生 HomeViewModel.loadLastCompare),否则/未登录/401 隐藏横幅;进页 + 回前台 visibilitychange 各拉一次;点击仍走 SGBridge.navigate('compareResult')。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
guke
2026-06-30 10:39:18 +08:00
parent 6682f9d0d4
commit 452d7fef8f
2 changed files with 61 additions and 19 deletions
+45 -19
View File
@@ -4931,7 +4931,7 @@ TODO: 补充 @owner / @from / @to / @notes(参照 FLOW.md
</div>
</div>
<div class="last-compare-banner" id="lastCompareBanner" onclick="go('compresult')" aria-live="polite">
<div class="last-compare-banner" id="lastCompareBanner" onclick="go('compresult')" aria-live="polite" style="display:none">
<div class="lcb-head">
<span class="lcb-kicker" id="lastCompareAge">上次比价</span>
<span class="lcb-status"></span>
@@ -8997,7 +8997,9 @@ function goAuthLocation() {
openLocSysPopup();
}
// 回前台(定位授权窗 / 设置返回)重核授权态并刷新 feed 区(纯 H5,不依赖原生事件)。
document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'visible') { try { applyLocationState(); } catch(e){} } });
document.addEventListener('visibilitychange', function(){ if (document.visibilityState === 'visible') { try { applyLocationState(); } catch(e){} try { renderLastCompareBanner(); } catch(e){} } });
// 「上次比价」横幅:进页拉一次 + 回前台(上面 visibilitychange)再拉(4 分钟窗口随时间过期;在外卖 App 比完价回来即出)
document.addEventListener('DOMContentLoaded', function(){ try { renderLastCompareBanner(); } catch(e){} });
function isHomeNearbyEmpty(feed) {
try {
var params = new URLSearchParams(location.search);
@@ -9089,13 +9091,7 @@ function renderTriClaim() {
if (entry) entry.classList.toggle('coupon-claimed', homeCouponClaimed);
}
const LAST_COMPARE_RECORD_KEY = 'proto_last_compare_record';
const DEFAULT_LAST_COMPARE_RECORD = {
shop: '窑鸡王(康营店)',
items: ['招牌窑鸡整只-原味', '窑鸡饭套餐'],
bestPlatform: '淘宝',
bestPrice: 57.9
};
// 「上次比价」数据改由后端 /api/v1/compare/records 实拉(见 renderLastCompareBanner);原写死 mock 已移除。
const LAST_COMPARE_VARIANTS = ['reference', 'tagright', 'topbar', 'pricebadge', 'mutedright'];
const LAST_COMPARE_PLATFORM_LOGOS = {
'淘宝': 'assets/shared/logos/taobao.png',
@@ -9112,20 +9108,50 @@ function getLastCompareVariant() {
return LAST_COMPARE_VARIANTS.includes(value) ? value : 'reference';
}
function readLastCompareRecord() {
try {
const raw = localStorage.getItem(LAST_COMPARE_RECORD_KEY);
return raw ? JSON.parse(raw) : null;
} catch(e) {
return null;
}
}
function renderLastCompareBanner() {
const banner = document.getElementById('lastCompareBanner');
if (!banner) return;
const token = (window.SGBridge && SGBridge.getToken && SGBridge.getToken()) || '';
if (!token) { banner.style.display = 'none'; return; } // 未登录 → 隐藏
fetch('/api/v1/compare/records?limit=5', { headers: { 'Authorization': 'Bearer ' + token } })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (d) {
const rec = pickFreshLastCompare(d && d.items);
if (!rec) { banner.style.display = 'none'; return; } // 无成功 / 超 4 分钟窗口 / 401 → 隐藏
banner.style.display = '';
fillLastCompareBanner(lastCompareToSummary(rec));
})
.catch(function () { banner.style.display = 'none'; });
}
const record = readLastCompareRecord() || DEFAULT_LAST_COMPARE_RECORD;
// 取最近一条成功比价并卡 4 分钟新鲜窗口(records 按 created_at 倒序,首条成功即最新成功)。
function pickFreshLastCompare(items) {
if (!Array.isArray(items)) return null;
for (let i = 0; i < items.length; i++) {
const it = items[i];
if (it && it.status === 'success') {
const t = Date.parse(it.created_at);
if (isNaN(t)) return null;
const age = Date.now() - t; // 容 1 分钟时钟偏差(同原生 toFreshSummary)
return (age >= -60000 && age <= 4 * 60 * 1000) ? it : null;
}
}
return null;
}
// 后端 ComparisonRecordOut → 横幅摘要(对齐原生 LastCompareSummary)。
function lastCompareToSummary(rec) {
return {
shop: rec.store_name || '',
items: (rec.items || []).map(function (it) { return it && it.name; }).filter(Boolean),
bestPlatform: rec.best_platform_name || '',
bestPrice: rec.best_price_cents != null ? rec.best_price_cents / 100 : 0,
};
}
function fillLastCompareBanner(record) {
const banner = document.getElementById('lastCompareBanner');
if (!banner) return;
const shopEl = document.getElementById('lastCompareShop');
const dishEl = document.getElementById('lastCompareDish');
const ageEl = document.getElementById('lastCompareAge');
+16
View File
@@ -70,3 +70,19 @@ def test_h5_home_feed_wired(client) -> None:
assert "茶百道" not in body # 写死的 mock 卡(店名)已移除
assert "chabaidao.png" not in body # 写死的 mock 头图已移除
assert "比团购省 5.5 元" not in body # 随原生:mock 卡的「比团购省」标签已删(渲染只用 price_label/rank_label)
def test_h5_home_last_compare_wired(client) -> None:
# 「上次比价」横幅改接真实 /api/v1/compare/records + 4 分钟新鲜窗口;原写死 mock 默认值已移除
resp = client.get("/h5/home/index.html")
assert resp.status_code == 200
body = resp.text
assert "/api/v1/compare/records" in body # 接真实比价记录端点
assert "pickFreshLastCompare" in body # 4 分钟新鲜窗口逻辑
assert "DEFAULT_LAST_COMPARE_RECORD" not in body # 写死 mock 默认已移除
def test_compare_records_requires_auth(client) -> None:
# 横幅靠 Bearer token 才拿得到数据;无鉴权必须 401(无桥/未登录时横幅隐藏)
resp = client.get("/api/v1/compare/records?limit=5")
assert resp.status_code == 401