| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- 'use strict';
- // 数据中台页:本地湖全景 + 云端同步(推送/拉取)
- // 读取 GET /api/reports/lake;推送逐主体执行(POST /api/reports/aitable/push)。
- (function () {
- const $ = (id) => document.getElementById(id);
- let inited = false;
- let loading = false;
- function fmtDate(ymd) {
- const s = String(ymd || '');
- return /^\d{8}$/.test(s) ? `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}` : (s || '–');
- }
- function fmtTime(iso) {
- if (!iso) return '–';
- const d = new Date(iso);
- return Number.isNaN(d.getTime()) ? String(iso) : `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
- }
- function esc(value) {
- const div = document.createElement('div');
- div.textContent = String(value ?? '');
- return div.innerHTML;
- }
- function setBadge(id, text, cls) {
- const badge = $(id);
- if (!badge) return;
- badge.textContent = text;
- badge.className = 'badge ' + (cls || 'badge-info');
- }
- function renderKpis(data) {
- $('lakeEntityCount').textContent = data.entities.length;
- $('lakeRowCount').textContent = data.totalRows;
- $('lakeCoverage').textContent = data.coverage ? `${fmtDate(data.coverage.from)} ~ ${fmtDate(data.coverage.to)}` : '–';
- const lastSyncs = data.entities.map((e) => e.lastSync && e.lastSync.at).filter(Boolean).sort();
- $('lakeLastSync').textContent = lastSyncs.length ? fmtTime(lastSyncs[lastSyncs.length - 1]) : '–';
- $('lakeRowsBadge').textContent = `${data.entities.length} 主体`;
- }
- function renderEntities(entities) {
- const tbody = $('lakeEntityRows');
- if (!entities.length) {
- tbody.innerHTML = '<tr><td colspan="10" class="empty-cell">本地湖为空 —— 取数机先「同步数据」,其他电脑点右上「从云端拉取」</td></tr>';
- return;
- }
- tbody.innerHTML = entities.map((e) => {
- const cov = e.coverage ? `${fmtDate(e.coverage.from)} ~ ${fmtDate(e.coverage.to)}` : '–';
- const sync = e.lastSync ? `${fmtTime(e.lastSync.at)}(${fmtDate(e.lastSync.from)}~${fmtDate(e.lastSync.to)})${e.lastSync.ok ? '' : ' ⚠'}` : '–';
- const push = e.lastPush ? `${fmtTime(e.lastPush.at)} / ${e.lastPush.pushedRows || 0} 行` : '未推送';
- return `<tr>
- <td>${esc(e.name || e.key)}</td>
- <td>${e.shopCount || 0} / ${e.chainCount || 0}</td>
- <td>${e.rows}</td>
- <td>${e.byScope.company || 0}</td>
- <td>${e.byScope.chain || 0}</td>
- <td>${e.byScope.shop || 0}</td>
- <td>${e.byScope.summary || 0}</td>
- <td>${cov}</td>
- <td>${esc(sync)}</td>
- <td>${esc(push)}</td>
- </tr>`;
- }).join('');
- }
- async function loadLake() {
- if (loading) return;
- loading = true;
- try {
- const res = await fetch('/api/reports/lake');
- const data = await res.json();
- if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
- renderKpis(data);
- renderEntities(data.entities);
- if (!data.aitable.enabled) setBadge('lakeSyncBadge', '云端同步未启用(系统设置)', 'badge-info');
- else if (!data.aitable.operatorConfigured) setBadge('lakeSyncBadge', '云端配置不完整', 'badge-fail');
- else setBadge('lakeSyncBadge', '云端就绪', 'badge-ok');
- } catch (error) {
- setBadge('lakeSyncBadge', `加载失败:${error.message}`, 'badge-fail');
- } finally {
- loading = false;
- }
- }
- // 推送:逐主体执行,聚合结果(单主体失败不中断其余,最后统一汇报)
- async function pushAll() {
- const btn = $('btnLakePush');
- btn.disabled = true;
- setBadge('lakeSyncBadge', '推送中…');
- try {
- const res = await fetch('/api/reports/lake');
- const data = await res.json();
- const targets = (data.entities || []).map((e) => e.key);
- if (!targets.length) throw new Error('本地湖为空,没有可推送的主体');
- let created = 0;
- let updated = 0;
- let stale = 0;
- const errors = [];
- for (const key of targets) {
- try {
- const r = await window.shanAitableAction('push', 'lakeSyncBadge', { entity: key });
- created += r.created || 0;
- updated += r.updated || 0;
- stale += r.skippedStale || 0;
- } catch (e) {
- errors.push(`${key}:${e.message}`);
- }
- }
- if (errors.length) {
- setBadge('lakeSyncBadge', `部分失败(成功 新增${created}/更新${updated}):${errors[0]}`, 'badge-fail');
- } else {
- setBadge('lakeSyncBadge', `已推送:新增 ${created} / 更新 ${updated}${stale ? ` / 跳过云端较新 ${stale}` : ''}`, 'badge-ok');
- }
- await loadLake();
- } catch (error) {
- setBadge('lakeSyncBadge', error.message, 'badge-fail');
- } finally {
- btn.disabled = false;
- }
- }
- async function pullAll() {
- const btn = $('btnLakePull');
- btn.disabled = true;
- try {
- await window.shanAitableAction('pull', 'lakeSyncBadge', {});
- await loadLake();
- } catch { /* 徽标已由 aitableAction 写入 */ } finally {
- btn.disabled = false;
- }
- }
- window.shanLakeInit = function () {
- if (!inited) {
- inited = true;
- $('btnLakeRefresh').addEventListener('click', loadLake);
- $('btnLakePush').addEventListener('click', pushAll);
- $('btnLakePull').addEventListener('click', pullAll);
- }
- loadLake();
- };
- })();
|