新增 README 並優化載入效能

根本原因:
專案缺少說明文件;bundle 為單一 910KB 主檔改版快取效率差,且每次開頁
與連續操作都會重複呼叫 Google Sheets API。

影響:
主 chunk 降至 216KB(react/antd 拆出可長期快取,jsrsasign 僅非安全來源
才動態載入);平台清單 sessionStorage 快取 5 分鐘、平台分頁記憶體快取
60 秒;機台圖片懶載入;InputNumber 改用 suffix 消除棄用警告。

修法:
- vite.config.ts 加入 manualChunks 拆分 react / antd+dayjs
- GoogleSheetService 加入兩層快取
- Image 元件預設 loading="lazy"
- SDGame 三個 InputNumber 的 addonAfter 改為 suffix
- 新增 README.md(功能、開發、技術備註、部署)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:15:03 +08:00
co-authored by Claude Fable 5
parent d73f2ea566
commit 1637a56908
5 changed files with 94 additions and 7 deletions
+3 -3
View File
@@ -246,7 +246,7 @@ const SDGame = (props: ISDGame) => {
step={0.1}
value={delay}
onChange={(v: number) => setDelay(+v || 0)}
addonAfter="秒"
suffix="秒"
/>
</div>
@@ -262,7 +262,7 @@ const SDGame = (props: ISDGame) => {
min={0}
value={ratioStop}
onChange={(v: number) => { const n = +v || 0; setRatioStop(n); GameManager.SlotData.RatioStop = n; }}
addonAfter="倍"
suffix="倍"
/>
}
</div>
@@ -279,7 +279,7 @@ const SDGame = (props: ISDGame) => {
min={0}
value={countStop}
onChange={(v: number) => { const n = +v || 0; setCountStop(n); GameManager.SlotData.CountStop = n; }}
addonAfter="轉"
suffix="轉"
/>
}
</div>
+1 -1
View File
@@ -8,7 +8,7 @@ const Image = (props: AvatarProps) => {
event.target.src = fallImg;
}
return (
<img {...props} onError={onError} />
<img loading="lazy" {...props} onError={onError} />
);
};
+34 -2
View File
@@ -180,8 +180,32 @@ function projectToCode(project: string): string {
return code;
}
/** 讀「專案清單」取得平台選項(同 quickopen:略過停運與空列) */
const PLATFORM_LIST_CACHE_KEY: string = "lp_bot_platform_list_cache";
const PLATFORM_LIST_CACHE_TTL: number = 5 * 60 * 1000;
/** 讀「專案清單」取得平台選項(同 quickopen:略過停運與空列);sessionStorage 快取 5 分鐘加速開頁 */
export async function fetchPlatformList(): Promise<PlatformOption[]> {
try {
const cached: string = sessionStorage.getItem(PLATFORM_LIST_CACHE_KEY);
if (cached) {
const { ts, data } = JSON.parse(cached);
if (Date.now() - ts < PLATFORM_LIST_CACHE_TTL && Array.isArray(data) && data.length > 0) {
return data;
}
}
} catch (e) {
//
}
const options: PlatformOption[] = await fetchPlatformListRemote();
try {
sessionStorage.setItem(PLATFORM_LIST_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: options }));
} catch (e) {
//
}
return options;
}
async function fetchPlatformListRemote(): Promise<PlatformOption[]> {
const rows: string[][] = await getValues(`${PROJECT_LIST_SHEET}!A4:B`);
const options: PlatformOption[] = [];
let emptyCount: number = 0;
@@ -235,10 +259,18 @@ function ensureSlash(url: string): string {
return url.endsWith("/") ? url : `${url}/`;
}
/** 讀平台分頁全部列 */
const PLATFORM_ROWS_CACHE_TTL: number = 60 * 1000;
const _platformRowsCache: Map<string, { ts: number; rows: string[][] }> = new Map();
/** 讀平台分頁全部列(記憶體快取 60 秒,避免「開遊戲」與「登入」連續操作重複抓表) */
async function getPlatformRows(platform: PlatformOption): Promise<{ sheetTitle: string; rows: string[][] }> {
const sheetTitle: string = await findSheetTitle(platform);
const cached = _platformRowsCache.get(sheetTitle);
if (cached && Date.now() - cached.ts < PLATFORM_ROWS_CACHE_TTL) {
return { sheetTitle, rows: cached.rows };
}
const rows: string[][] = await getValues(`${sheetTitle}!A1:E20`);
_platformRowsCache.set(sheetTitle, { ts: Date.now(), rows });
return { sheetTitle, rows };
}