/* data.js —— Parse REST 读取层。 * 只带 X-Parse-Application-Id(公开读),不带任何凭据。 * 不用 count 参数(服务端已知问题),一律取 results 长度。 */ (function () { "use strict"; const cfg = window.KF_CONFIG; const state = { loadedAt: null, ok: false, error: null, truncated: [], // 被截断的表名 counts: {} }; /* Parse 的日期在各字段里是 {"__type":"Date","iso":"…"}(或 {"__type":"Date","__d"…})。 * 下游到处 new Date(x) 排序、算时长,逐个处理容易漏——在入口一次性换成 ISO 串。 * 只处理带 __type 的对象本身;数组/普通对象(scores、decision、report)原样递归。 */ function normalizeDates(v) { if (Array.isArray(v)) return v.map(normalizeDates); if (v && typeof v === "object") { if (v.__type === "Date") { window.__DBGD = (window.__DBGD||[]).concat([JSON.stringify(v)]); const t = v.iso ? new Date(v.iso).getTime() : v.__d != null ? Number(v.__d) : NaN; return Number.isFinite(t) ? new Date(t).toISOString() : null; /* 统一成 UTC ISO,避免各浏览器解析口径不一 */ } const out = {}; for (const k in v) out[k] = normalizeDates(v[k]); return out; } return v; } function headers() { return { "X-Parse-Application-Id": cfg.appId, Accept: "application/json" }; } async function getJSON(path) { const res = await fetch(cfg.parseBase + path, { headers: headers(), cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status} · ${path}`); return res.json(); } /* 分页拉取:单页 pageLimit 条,用 skip 往后翻,直到不足一页。 * 线下 40 人两天写下来完全可能超过 1000 条;超过时要能全部取到,而不是悄悄截断。 * 翻了 50 页仍满页才标记"被截断",交给 UI 明示。 */ async function fetchClass(cls) { const page = Math.max(1, cfg.pageLimit || 1000); const rows = []; let skip = 0; for (let guard = 0; guard < 50; guard++) { const q = `?limit=${page}&skip=${skip}&order=createdAt`; const json = await getJSON(`/classes/${cls}${q}`); const batch = Array.isArray(json.results) ? normalizeDates(json.results) : []; rows.push(...batch); if (batch.length < page) return rows; skip += page; } state.truncated.push(cls); /* 翻了 50 页还没完:如实标记被截断 */ return rows; } /* 拉全部六张表。任何一张失败都整体失败并给出原因,UI 显示错误态而不是空态 */ async function load() { state.truncated = []; const c = cfg.classes; const names = ["project", "artifact", "insight", "source", "session", "opportunity"]; try { const rows = await Promise.all(names.map((n) => fetchClass(c[n]))); const out = {}; names.forEach((n, i) => { out[n] = rows[i]; }); Object.assign(out, { loadedAt: new Date().toISOString(), truncated: state.truncated.slice() }); state.ok = true; state.error = null; state.loadedAt = out.loadedAt; state.counts = { project: out.project.length, artifact: out.artifact.length, insight: out.insight.length, source: out.source.length, session: out.session.length, opportunity: out.opportunity.length }; return out; } catch (e) { state.ok = false; state.error = e.message || String(e); throw e; } } window.KF_DATA = { load, state, normalizeDates }; })();