feat(system): 添加固件版本名称缓存及云端获取功能
- 在 luxsinApi.ts 中实现固件版本名称的本地缓存机制,缓存时长为30分钟 - 新增读取和写入 localStorage 缓存的函数,避免频繁调用云端API - 实现 fetchFirmwareVersionName 函数,从云端获取固件版本名称并结合本地缓存使用 - 在 SystemPage.tsx 中优先从缓存读取固件版本名称,未命中时异步调用云端接口 - 增加加载状态展示,接口异常或无数据时用本地格式化版本名称兜底显示 - 避免因缓存无效或接口异常导致版本名称缺失,提升系统页固件版本显示的稳定性和体验
This commit is contained in:
@@ -645,6 +645,101 @@ export function buildLuxsinAudioUrl(resourcePath: string): string {
|
||||
return `https://api.luxsin.com.cn/audio/${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Firmware version-name cache. `getVerName` results are stable for a given
|
||||
* model+version, so we cache them in `localStorage` for 30 minutes to avoid
|
||||
* hitting the cloud API every time the SystemPage is opened.
|
||||
*/
|
||||
const VER_NAME_CACHE_PREFIX = "luxsin:verName:";
|
||||
const VER_NAME_CACHE_TTL_MS = 30 * 60 * 1000;
|
||||
|
||||
function verNameCacheKey(model: string, version: string): string {
|
||||
return `${VER_NAME_CACHE_PREFIX}${model}:${version}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously read a cached firmware version name for `model`+`version`.
|
||||
* Returns `null` when there is no entry, it is malformed, or it has expired
|
||||
* (expired entries are dropped so the next fetch refreshes them). Storage
|
||||
* access is best-effort — any failure (private mode / quota) yields `null`.
|
||||
*/
|
||||
export function readCachedFirmwareVersionName(
|
||||
model: string,
|
||||
version: string | number,
|
||||
): string | null {
|
||||
const m = String(model ?? "").trim();
|
||||
const v = String(version ?? "").trim();
|
||||
if (!m || !v) return null;
|
||||
const key = verNameCacheKey(m, v);
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw) as { verName?: unknown; ts?: unknown };
|
||||
const verName = typeof parsed?.verName === "string" ? parsed.verName.trim() : "";
|
||||
const ts = typeof parsed?.ts === "number" ? parsed.ts : 0;
|
||||
if (!verName || !ts) return null;
|
||||
if (Date.now() - ts > VER_NAME_CACHE_TTL_MS) {
|
||||
localStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return verName;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeCachedFirmwareVersionName(
|
||||
model: string,
|
||||
version: string,
|
||||
verName: string,
|
||||
): void {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
verNameCacheKey(model, version),
|
||||
JSON.stringify({ verName, ts: Date.now() }),
|
||||
);
|
||||
} catch {
|
||||
/* storage unavailable (private mode / quota) — caching is best-effort */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the firmware display version name from the cloud catalog.
|
||||
* `GET getVerName?model=<device>&version=<version>` → `{ code, msg, data: { verName } }`.
|
||||
* Serves from the 30-minute `localStorage` cache when available; otherwise the
|
||||
* protocol (http/https) follows the current page, matching the other
|
||||
* `//api.luxsin.com.cn/audio` calls; dev goes through the Vite proxy to avoid
|
||||
* Origin-based 403/CORS. Returns `null` when unavailable so callers can fall back.
|
||||
*/
|
||||
export async function fetchFirmwareVersionName(
|
||||
model: string,
|
||||
version: string | number,
|
||||
): Promise<string | null> {
|
||||
const m = String(model ?? "").trim();
|
||||
const v = String(version ?? "").trim();
|
||||
if (!m || !v) return null;
|
||||
|
||||
// Serve from the browser cache within the TTL to avoid frequent requests.
|
||||
const cached = readCachedFirmwareVersionName(m, v);
|
||||
if (cached) return cached;
|
||||
|
||||
const query = `getVerName?model=${encodeURIComponent(m)}&version=${encodeURIComponent(v)}`;
|
||||
const url = import.meta.env.DEV
|
||||
? `/luxsin-audio-api/${query}`
|
||||
: `//api.luxsin.com.cn/audio/${query}`;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return null;
|
||||
const json = (await res.json()) as { data?: { verName?: unknown } };
|
||||
const verName = json?.data?.verName;
|
||||
const result = typeof verName === "string" && verName.trim() ? verName.trim() : null;
|
||||
if (result) writeCachedFirmwareVersionName(m, v, result);
|
||||
return result;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches `getBrand`; body is custom Base64 text, decodes to JSON array of `{ id, name }`. */
|
||||
export async function fetchLuxsinAudioBrands(): Promise<LuxsinAudioBrand[]> {
|
||||
const res = await fetch(buildLuxsinAudioUrl("getBrand"));
|
||||
|
||||
Reference in New Issue
Block a user