Blame view

天文台pc/tianwentai-ui/src/app/kioskStorage.ts 12.1 KB
bc518174   王天杨   提交两个项目文件
1
2
3
4
5
6
7
8
  import type { GuideLocale } from "./guideContent";
  
  export const KIOSK_EVENT = "kiosk-settings-updated";
  
  export function emitKioskUpdate() {
    window.dispatchEvent(new CustomEvent(KIOSK_EVENT));
  }
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  /** 旧版本曾写入 localStorage 的 key,启动时清理,避免误以为仍在本地持久化 */
  const LEGACY_LS_KEYS = [
    "kiosk_welcome_v1",
    "kiosk_bg_home_urls_v1",
    "kiosk_bg_carousel_urls_v1",
    "kiosk_guide_overrides_v1",
    "kiosk_knowledge_db_v1",
    "kiosk_video_source_v1",
  ] as const;
  
  function clearLegacyKioskLocalStorage(): void {
    try {
      for (const k of LEGACY_LS_KEYS) {
        localStorage.removeItem(k);
      }
    } catch {
      // ignore
    }
  }
  
  clearLegacyKioskLocalStorage();
  
  /** 展台数据仅驻内存,由 GET /api/kiosk/bundle 填充;保存一律走后端 API */
  type MemoryCache = {
    homeBackgrounds: string[] | null;
    carouselBackgrounds: string[] | null;
    welcome: WelcomeMessages | null;
    guide: GuideOverrides | null;
    knowledge: KnowledgeEntry[] | null;
    videoSource: VideoSourceConfig | null;
  };
  
  const mem: MemoryCache = {
    homeBackgrounds: null,
    carouselBackgrounds: null,
    welcome: null,
    guide: null,
    knowledge: null,
    videoSource: null,
  };
bc518174   王天杨   提交两个项目文件
49
  
bc518174   王天杨   提交两个项目文件
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  export const DEFAULT_HOME_BG_URLS: readonly string[] = [
    "/backgrounds/daocheng-radio-array.png",
    "/backgrounds/daocheng-campus-aerial.png",
  ];
  
  export const DEFAULT_CAROUSEL_BG_URLS: readonly string[] = [
    "https://images.unsplash.com/photo-1419242902214-272b3f66ee7a?auto=format&fit=crop&w=1920&q=80",
    "https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1920&q=80",
    "https://images.unsplash.com/photo-1543722530-d2c3201371e7?auto=format&fit=crop&w=1920&q=80",
    "https://images.unsplash.com/photo-1444703686971-f7864ae02629?auto=format&fit=crop&w=1920&q=80",
    "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=1920&q=80",
    "https://images.unsplash.com/photo-1464802686167-b939a6910659?auto=format&fit=crop&w=1920&q=80",
  ];
  
  export type WelcomeMessages = Record<"zh-CN" | "en" | "bo", string>;
  
  export function loadHomeBackgrounds(): string[] {
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
67
68
    if (!mem.homeBackgrounds?.length) return [...DEFAULT_HOME_BG_URLS];
    return mem.homeBackgrounds.filter((u) => typeof u === "string" && u.length > 0);
bc518174   王天杨   提交两个项目文件
69
70
71
  }
  
  export function loadCarouselBackgrounds(): string[] {
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
72
73
    if (!mem.carouselBackgrounds?.length) return [...DEFAULT_CAROUSEL_BG_URLS];
    return mem.carouselBackgrounds.filter((u) => typeof u === "string" && u.length > 0);
bc518174   王天杨   提交两个项目文件
74
75
76
  }
  
  export function loadWelcome(defaults: WelcomeMessages): WelcomeMessages {
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
77
78
79
80
81
82
83
    if (!mem.welcome) return { ...defaults };
    const w = mem.welcome;
    return {
      "zh-CN": typeof w["zh-CN"] === "string" ? w["zh-CN"] : defaults["zh-CN"],
      en: typeof w.en === "string" ? w.en : defaults.en,
      bo: typeof w.bo === "string" ? w.bo : defaults.bo,
    };
bc518174   王天杨   提交两个项目文件
84
85
86
87
88
89
90
91
92
93
94
95
96
  }
  
  export type GuideLangKey = "zh-CN" | "en" | "bo";
  
  export type GuideLocalePatch = {
    intro?: string;
    touchItems?: string[];
    softwareItems?: string[];
  };
  
  export type GuideOverrides = Partial<Record<GuideLangKey, GuideLocalePatch>>;
  
  export function loadGuideOverrides(): GuideOverrides {
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
97
98
    if (!mem.guide) return {};
    return mem.guide;
bc518174   王天杨   提交两个项目文件
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
  }
  
  export function mergeGuideLocales(
    base: readonly GuideLocale[],
    overrides: GuideOverrides
  ): GuideLocale[] {
    return base.map((loc) => {
      const key = loc.htmlLang as GuideLangKey;
      const p = overrides[key];
      if (!p) return { ...loc };
      return {
        ...loc,
        intro: p.intro ?? loc.intro,
        touchItems: p.touchItems ?? loc.touchItems,
        softwareItems: p.softwareItems ?? loc.softwareItems,
      };
    });
  }
  
  export type KnowledgeEntry = {
    id: string;
    type: "文字" | "图片" | "视频";
    title: string;
    content: string;
    date: string;
    tags: string[];
    image?: string;
    videoUrl?: string;
  };
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
129
130
131
132
133
134
135
136
137
138
139
140
141
  export function newKnowledgeEntryId(): string {
    try {
      if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
        return crypto.randomUUID();
      }
    } catch {
      // ignore
    }
    const t = Date.now().toString(36);
    const r = () => Math.random().toString(36).slice(2, 10);
    return `k_${t}_${r()}${r()}`;
  }
  
bc518174   王天杨   提交两个项目文件
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  export type VideoProtocol = "HLS" | "WebRTC" | "RTSP";
  
  export type VideoSourceConfig = {
    protocol: VideoProtocol;
    url: string;
    note: string;
  };
  
  export const DEFAULT_VIDEO_SOURCE: VideoSourceConfig = {
    protocol: "HLS",
    url: "",
    note: "",
  };
  
  export const DEFAULT_KNOWLEDGE_ENTRIES: KnowledgeEntry[] = [
    {
      id: "k1",
      type: "文字",
      title: "黑洞的形成与演化",
      content:
        "黑洞是宇宙中最神秘的天体之一,当大质量恒星耗尽核燃料后,会发生引力坍缩并形成事件视界。事件视界内的光与物质无法逃逸,外部可通过吸积盘辐射与引力波信号间接探测。",
      date: "2026-03-15",
      tags: ["黑洞", "天体物理"],
      image: "/knowledge/black-hole.png",
    },
    {
      id: "k2",
      type: "图片",
      title: "猎户座大星云观测图",
      content:
        "使用稻城天文台2.5米望远镜拍摄的猎户座大星云高清图像,展示了恒星诞生的壮观景象。图像涵盖电离氢区与年轻星团,可用于公众科普与教学对比不同时段观测条件。",
      date: "2026-03-20",
      tags: ["星云", "观测数据"],
      image:
        "https://images.unsplash.com/photo-1628632642012-290ae1f4b1ac?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&w=400",
    },
    {
      id: "k3",
      type: "视频",
      title: "日全食观测全程记录",
      content:
        "2026年3月稻城地区日全食的完整观测记录,包含贝利珠、日冕等精彩现象。片中含安全观测提示与关键时间节点标注,适合展厅循环播放与导览讲解引用。",
      date: "2026-03-21",
      tags: ["日食", "观测记录"],
      image: "/knowledge/total-solar-eclipse.png",
    },
    {
      id: "k4",
      type: "文字",
      title: "暗物质探测最新进展",
      content:
        "稻城天文台参与的暗物质探测项目取得重大突破,可能为理解宇宙组成提供新线索。团队结合多波段数据与统计方法,正在验证新的信号模型并推进国际合作数据共享。",
      date: "2026-03-18",
      tags: ["暗物质", "科研进展"],
      image: "/knowledge/dark-matter.png",
    },
    {
      id: "k5",
      type: "图片",
      title: "银河中心区域深空摄影",
      content:
        "长曝光拍摄的银河中心区域,清晰展现了恒星密集区和暗物质带。后期校准了平场与偏置帧,可在高对比度显示器上呈现银心附近尘埃带的层次细节。",
      date: "2026-03-10",
      tags: ["银河系", "深空摄影"],
      image:
        "https://images.unsplash.com/photo-1631594274394-279ab758c1ed?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&w=400",
    },
    {
      id: "k6",
      type: "视频",
      title: "天文望远镜操作教程",
      content:
        "详细介绍如何使用稻城天文台的大型望远镜进行天体观测和数据采集。内容涵盖指向校准、导星设置、曝光序列与安全收镜流程,并附常见故障排查要点。",
      date: "2026-03-05",
      tags: ["教程", "望远镜"],
      image: "/knowledge/telescope.png",
    },
    {
      id: "k7",
      type: "文字",
      title: "火星冲日与行星观测要点",
      content:
        "冲日期间火星视直径最大、亮度最高,是行星观测与摄影的窗口期,需注意大气视宁度与设备校准。建议选择透明度高、气流稳定的夜晚,并预留色散与滤镜的对比实验时间。",
      date: "2026-03-12",
      tags: ["行星", "观测记录"],
      image: "/knowledge/planet-observation.png",
    },
    {
      id: "k8",
      type: "图片",
      title: "月球环形山特写",
      content:
        "高分辨率月面成像,展示哥白尼环形山及周边辐射纹细节,可用于科普对比不同时相月貌。拍摄采用短曝光叠加,突出环形山壁与中央峰的明暗过渡。",
      date: "2026-03-08",
      tags: ["月球", "观测数据"],
      image: "/knowledge/moon-crater.png",
    },
    {
      id: "k9",
      type: "视频",
      title: "流星雨观测指南短片",
      content:
        "英仙座与双子座流星雨观测技巧:光害规避、曝光参数与记录表格示例,适合公众开放夜播放。片中归纳了辐射点高度与流量峰值时段,便于安排户外讲解动线。",
      date: "2026-03-01",
      tags: ["流星雨", "教程"],
      image: "/knowledge/meteor-shower.png",
    },
    {
      id: "k10",
      type: "文字",
      title: "系外行星凌星测光简介",
      content:
        "当行星从恒星盘面前方经过会造成微小亮度下降,稻城台站可参与后续跟进与科普数据展示。简介测光曲线形态与噪声来源,帮助观众理解“凌星”信号的物理含义。",
      date: "2026-02-28",
      tags: ["系外行星", "科研进展"],
      image: "/knowledge/exoplanet.png",
    },
  ];
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
261
  /** 未从服务器拉取前,知识库列表为空(不读本地演示数据) */
bc518174   王天杨   提交两个项目文件
262
  export function loadKnowledgeEntries(): KnowledgeEntry[] {
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
263
264
265
266
    if (!mem.knowledge) return [];
    return mem.knowledge.filter(
      (x): x is KnowledgeEntry => x && typeof x.id === "string" && typeof x.title === "string"
    );
bc518174   王天杨   提交两个项目文件
267
268
  }
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
269
270
271
  export function loadVideoSourceConfig(): VideoSourceConfig {
    if (!mem.videoSource) return { ...DEFAULT_VIDEO_SOURCE };
    return { ...mem.videoSource };
bc518174   王天杨   提交两个项目文件
272
273
  }
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
  export type KioskServerBundle = {
    homeBackgrounds: string[];
    carouselBackgrounds?: string[];
    welcome: WelcomeMessages;
    guide: GuideOverrides | Record<string, never> | unknown[];
    knowledge: Array<Record<string, unknown>>;
    videoSource?: VideoSourceConfig | Record<string, unknown>;
  };
  
  function normalizeKnowledgeRow(row: Record<string, unknown>): KnowledgeEntry | null {
    if (typeof row.id !== "string" || typeof row.title !== "string") return null;
    const t = row.type;
    const type: KnowledgeEntry["type"] =
      t === "图片" || t === "视频" || t === "文字" ? t : "文字";
    const tagsRaw = row.tags;
    const tags = Array.isArray(tagsRaw)
      ? tagsRaw.filter((x): x is string => typeof x === "string")
      : [];
    const videoRaw = row.videoUrl ?? row.video_url;
    const imageRaw = row.image;
    return {
      id: row.id,
      type,
      title: row.title,
      content: typeof row.content === "string" ? row.content : "",
      date: typeof row.date === "string" ? row.date : "",
      tags,
      image: typeof imageRaw === "string" && imageRaw ? imageRaw : undefined,
      videoUrl: typeof videoRaw === "string" && videoRaw ? videoRaw : undefined,
    };
bc518174   王天杨   提交两个项目文件
304
305
  }
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
306
307
308
309
310
311
312
313
314
315
316
  function normalizeVideoSource(raw: unknown): VideoSourceConfig | null {
    if (!raw || typeof raw !== "object") return null;
    const o = raw as Record<string, unknown>;
    const p = o.protocol;
    const protocol: VideoSourceConfig["protocol"] =
      p === "WebRTC" || p === "RTSP" || p === "HLS" ? p : "HLS";
    return {
      protocol,
      url: typeof o.url === "string" ? o.url : "",
      note: typeof o.note === "string" ? o.note : "",
    };
bc518174   王天杨   提交两个项目文件
317
318
  }
  
3a3dc915   王天杨   feat: 稻城亚丁项目批量更新
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  /** 用 bundle 覆盖内存中的展台数据(唯一数据源入口,不写 localStorage) */
  export function applyServerBundle(data: KioskServerBundle): void {
    const bg = data.homeBackgrounds;
    if (Array.isArray(bg)) {
      mem.homeBackgrounds = bg.filter((u) => typeof u === "string" && u !== "");
    }
    const car = data.carouselBackgrounds;
    if (Array.isArray(car) && car.length > 0) {
      mem.carouselBackgrounds = car.filter((u): u is string => typeof u === "string" && u !== "");
    }
    const w = data.welcome;
    if (w && typeof w["zh-CN"] === "string" && typeof w.en === "string" && typeof w.bo === "string") {
      mem.welcome = {
        "zh-CN": w["zh-CN"],
        en: w.en,
        bo: w.bo,
      };
    }
    const g = data.guide;
    if (Array.isArray(g) && g.length === 0) {
      mem.guide = {};
    } else if (g && typeof g === "object" && !Array.isArray(g)) {
      mem.guide = g as GuideOverrides;
    }
    const kn = data.knowledge;
    if (Array.isArray(kn)) {
      const list: KnowledgeEntry[] = [];
      for (const row of kn) {
        if (!row || typeof row !== "object") continue;
        const e = normalizeKnowledgeRow(row as Record<string, unknown>);
        if (e) list.push(e);
      }
      mem.knowledge = list;
    }
    const vs = normalizeVideoSource(data.videoSource);
    if (vs) {
      mem.videoSource = vs;
    }
bc518174   王天杨   提交两个项目文件
357
358
    emitKioskUpdate();
  }