Track successful sync runs

This commit is contained in:
liuji
2026-10-10 17:39:58 +08:00
parent baae470d78
commit 50af6ffb52
3 changed files with 84 additions and 21 deletions
+35 -8
View File
@@ -132,7 +132,6 @@
</section>
<section class="metrics" id="metrics" aria-label="数据概览" hidden>
<div class="metric"><span class="metric-label">数据更新时间</span><strong class="metric-value" id="updated-at">—</strong></div>
<div class="metric"><span class="metric-label">记录数</span><strong class="metric-value" id="record-count">—</strong></div>
<div class="metric"><span class="metric-label">LinkName 数</span><strong class="metric-value" id="link-count">—</strong></div>
<div class="metric"><span class="metric-label">Total Commission</span><strong class="metric-value" id="total-commission">—</strong></div>
@@ -167,6 +166,7 @@
'use strict';
var DATA_URL = 'data/merged.html';
var SYNC_LOG_URL = 'data/sync-log.json';
var RATES_URL = 'https://open.er-api.com/v6/latest/USD';
var REQUIRED_FIELDS = ['Date', 'Commission', 'Order', 'GMV', 'Currency', 'LinkName'];
var charts = [];
@@ -190,7 +190,6 @@
rangeButtons: Array.from(document.querySelectorAll('[data-range]')),
rateStatus: document.getElementById('rate-status'),
metrics: document.getElementById('metrics'),
updatedAt: document.getElementById('updated-at'),
recordCount: document.getElementById('record-count'),
linkCount: document.getElementById('link-count'),
totalCommission: document.getElementById('total-commission'),
@@ -205,7 +204,16 @@
linkCommission: document.getElementById('link-commission-chart'),
linkGmv: document.getElementById('link-gmv-chart')
};
var state = { rawRecords: [], sourceCurrencies: [], selectedCurrency: '', latestDate: null, dateOnlyRows: 0, invalidCurrencyRows: 0 };
var state = {
rawRecords: [],
sourceCurrencies: [],
selectedCurrency: '',
latestDate: null,
dateOnlyRows: 0,
invalidCurrencyRows: 0,
syncLog: null,
syncLogUnavailable: false
};
function setStatus(message, status) {
elements.status.textContent = message;
@@ -277,6 +285,8 @@
function updateNotice() {
var messages = [];
if (state.syncLog) messages.push('最后成功运行:' + state.syncLog.displayTime);
if (state.syncLogUnavailable) messages.push('运行日志暂不可用。');
if (state.dateOnlyRows) messages.push('已过滤 ' + formatNumber(state.dateOnlyRows) + ' 条只有 Date、Commission 和 GMV 均为空的日期行。');
if (state.invalidCurrencyRows) messages.push('已排除 ' + formatNumber(state.invalidCurrencyRows) + ' 条币种为空、Unknown 或格式无效且包含金额的记录,请检查原始数据。');
if (state.sourceCurrencies.length > 1) messages.push('检测到多个已知源币种(' + state.sourceCurrencies.join('、') + '),已统一换算为 USD,不能切换到 IDR。');
@@ -284,6 +294,15 @@
elements.warning.hidden = messages.length === 0;
}
function parseSyncLog(payload) {
if (!payload || typeof payload.last_success_at !== 'string' || !payload.last_success_at.trim() || Number.isNaN(Date.parse(payload.last_success_at))) {
throw new Error('运行日志缺少有效的 last_success_at。');
}
var shanghaiTime = typeof payload.last_success_at_shanghai === 'string' ? payload.last_success_at_shanghai.trim() : '';
var displayTime = shanghaiTime && !Number.isNaN(Date.parse(shanghaiTime)) ? shanghaiTime : payload.last_success_at;
return { displayTime: displayTime };
}
function setRateStatus(message) { elements.rateStatus.textContent = message; }
function updateCurrencyOptions() {
@@ -415,8 +434,7 @@
return total + (record.gmv === null ? 0 : record.gmv);
}, 0);
elements.title.textContent = 'TTSHOP 销售看板 · ' + state.selectedCurrency;
elements.subtitle.textContent = 'Commission 与 GMV 趋势(当前加载时间:' + dateFormatter.format(new Date()) + ')';
elements.updatedAt.textContent = dateFormatter.format(new Date());
elements.subtitle.textContent = 'Commission 与 GMV 趋势(数据来自最近一次成功同步)';
elements.recordCount.textContent = formatNumber(records.length);
elements.linkCount.textContent = formatNumber(links.size);
elements.totalCommission.textContent = state.selectedCurrency + ' ' + formatNumber(totalCommission);
@@ -551,11 +569,20 @@
}
function loadData() {
fetch(DATA_URL, { cache: 'no-store' }).then(function (response) {
var dataPromise = fetch(DATA_URL, { cache: 'no-store' }).then(function (response) {
if (!response.ok) throw new Error('无法读取 data/merged.html(HTTP ' + response.status + ')。');
return response.text();
}).then(function (htmlText) {
var result = readTable(htmlText);
});
var syncLogPromise = fetch(SYNC_LOG_URL, { cache: 'no-store' }).then(function (response) {
if (!response.ok) throw new Error('无法读取 data/sync-log.json(HTTP ' + response.status + ')。');
return response.json();
}).then(parseSyncLog).catch(function () {
state.syncLogUnavailable = true;
return null;
});
Promise.all([dataPromise, syncLogPromise]).then(function (results) {
var result = readTable(results[0]);
state.syncLog = results[1];
state.rawRecords = result.records;
state.dateOnlyRows = result.dateOnlyRows;
state.invalidCurrencyRows = result.invalidCurrencyRows;