data.js 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. /* data.js —— Parse REST 读取层。
  2. * 只带 X-Parse-Application-Id(公开读),不带任何凭据。
  3. * 不用 count 参数(服务端已知问题),一律取 results 长度。 */
  4. (function () {
  5. "use strict";
  6. const cfg = window.KF_CONFIG;
  7. const state = {
  8. loadedAt: null,
  9. ok: false,
  10. error: null,
  11. truncated: [], // 被截断的表名
  12. counts: {}
  13. };
  14. /* Parse 的日期在各字段里是 {"__type":"Date","iso":"…"}(或 {"__type":"Date","__d"…})。
  15. * 下游到处 new Date(x) 排序、算时长,逐个处理容易漏——在入口一次性换成 ISO 串。
  16. * 只处理带 __type 的对象本身;数组/普通对象(scores、decision、report)原样递归。 */
  17. function normalizeDates(v) {
  18. if (Array.isArray(v)) return v.map(normalizeDates);
  19. if (v && typeof v === "object") {
  20. if (v.__type === "Date") {
  21. window.__DBGD = (window.__DBGD||[]).concat([JSON.stringify(v)]);
  22. const t = v.iso ? new Date(v.iso).getTime() : v.__d != null ? Number(v.__d) : NaN;
  23. return Number.isFinite(t) ? new Date(t).toISOString() : null; /* 统一成 UTC ISO,避免各浏览器解析口径不一 */
  24. }
  25. const out = {};
  26. for (const k in v) out[k] = normalizeDates(v[k]);
  27. return out;
  28. }
  29. return v;
  30. }
  31. function headers() {
  32. return { "X-Parse-Application-Id": cfg.appId, Accept: "application/json" };
  33. }
  34. async function getJSON(path) {
  35. const res = await fetch(cfg.parseBase + path, { headers: headers(), cache: "no-store" });
  36. if (!res.ok) throw new Error(`HTTP ${res.status} · ${path}`);
  37. return res.json();
  38. }
  39. /* 分页拉取:单页 pageLimit 条,用 skip 往后翻,直到不足一页。
  40. * 线下 40 人两天写下来完全可能超过 1000 条;超过时要能全部取到,而不是悄悄截断。
  41. * 翻了 50 页仍满页才标记"被截断",交给 UI 明示。 */
  42. async function fetchClass(cls) {
  43. const page = Math.max(1, cfg.pageLimit || 1000);
  44. const rows = [];
  45. let skip = 0;
  46. for (let guard = 0; guard < 50; guard++) {
  47. const q = `?limit=${page}&skip=${skip}&order=createdAt`;
  48. const json = await getJSON(`/classes/${cls}${q}`);
  49. const batch = Array.isArray(json.results) ? normalizeDates(json.results) : [];
  50. rows.push(...batch);
  51. if (batch.length < page) return rows;
  52. skip += page;
  53. }
  54. state.truncated.push(cls); /* 翻了 50 页还没完:如实标记被截断 */
  55. return rows;
  56. }
  57. /* 拉全部六张表。任何一张失败都整体失败并给出原因,UI 显示错误态而不是空态 */
  58. async function load() {
  59. state.truncated = [];
  60. const c = cfg.classes;
  61. const names = ["project", "artifact", "insight", "source", "session", "opportunity"];
  62. try {
  63. const rows = await Promise.all(names.map((n) => fetchClass(c[n])));
  64. const out = {};
  65. names.forEach((n, i) => { out[n] = rows[i]; });
  66. Object.assign(out, { loadedAt: new Date().toISOString(), truncated: state.truncated.slice() });
  67. state.ok = true;
  68. state.error = null;
  69. state.loadedAt = out.loadedAt;
  70. state.counts = { project: out.project.length, artifact: out.artifact.length,
  71. insight: out.insight.length, source: out.source.length, session: out.session.length,
  72. opportunity: out.opportunity.length };
  73. return out;
  74. } catch (e) {
  75. state.ok = false;
  76. state.error = e.message || String(e);
  77. throw e;
  78. }
  79. }
  80. window.KF_DATA = { load, state, normalizeDates };
  81. })();