import "./app.css"; type ThemeMode = "light" | "dark"; const themeStorageKey = "frontend-starter-theme"; const app = document.querySelector("#app"); function getPreferredTheme(): ThemeMode { const stored = window.localStorage.getItem(themeStorageKey); if (stored === "light" || stored === "dark") { return stored; } return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function applyTheme(theme: ThemeMode): void { document.documentElement.classList.toggle("dark", theme === "dark"); document.documentElement.dataset.theme = theme; window.localStorage.setItem(themeStorageKey, theme); } function toggleTheme(): void { const nextTheme: ThemeMode = document.documentElement.classList.contains("dark") ? "light" : "dark"; applyTheme(nextTheme); renderThemeLabel(nextTheme); } function renderThemeLabel(theme: ThemeMode): void { const themeLabel = document.querySelector("#theme-label"); if (themeLabel) { themeLabel.textContent = theme === "dark" ? "Dark" : "Light"; } } async function loadApiDemo(): Promise { const apiResult = document.querySelector("#api-result"); if (!apiResult) { return; } apiResult.textContent = "Loading /api/demo/hello ..."; try { const response = await fetch("/api/demo/hello"); if (!response.ok) { throw new Error(`Request failed with status ${response.status}`); } const payload = await response.json(); apiResult.textContent = JSON.stringify(payload, null, 2); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; apiResult.textContent = `API request failed: ${message}`; } } function renderShell(): void { if (!app) { return; } app.innerHTML = `

Frontend Starter

Tailwind v4 + Font Awesome Free v7 + 双主题

这个样例目录对应教程里的前端初始化主线:项目创建、样式栈、图标库、主题切换、API 联调和宿主托管。

当前页面验证什么
  • 项目级 npm 源通过 .npmrc 固定为 https://npm.feinian.net
  • 样式层使用 Tailwind v4,图标来自 Font Awesome Free v7
  • 主题默认跟随系统,并支持手动切换与持久化
  • 通过 /api/demo/hello 验证与后端样例的最小联调闭环
API Demo

          
`; } renderShell(); const initialTheme = getPreferredTheme(); applyTheme(initialTheme); renderThemeLabel(initialTheme); void loadApiDemo(); document.querySelector("#theme-toggle")?.addEventListener("click", toggleTheme); document.querySelector("#reload-api")?.addEventListener("click", () => { void loadApiDemo(); });