首页新增 what‘s new 入口,维护一个 changelog 文档
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
# 更新说明
|
||||||
|
|
||||||
|
> 本文档记录 Luxsin B9 控制器的功能更新与问题修复,每次发布新版本后在此追加记录。
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- 首页新增「更新说明」入口,支持在线查看版本更新记录
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- 无
|
||||||
@@ -16,6 +16,7 @@ import IOPage from "./pages/IOPage";
|
|||||||
import VUPage from "./pages/VUPage";
|
import VUPage from "./pages/VUPage";
|
||||||
import SelectPage from "./pages/SelectPage";
|
import SelectPage from "./pages/SelectPage";
|
||||||
import CommunityPage from "./pages/CommunityPage";
|
import CommunityPage from "./pages/CommunityPage";
|
||||||
|
import ChangelogPage from "./pages/ChangelogPage";
|
||||||
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
|
||||||
import DesktopAIShell from "./components/DesktopAIShell";
|
import DesktopAIShell from "./components/DesktopAIShell";
|
||||||
import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
|
import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
|
||||||
@@ -66,6 +67,7 @@ function AppRouter() {
|
|||||||
<Route path="/select" component={SelectPage} />
|
<Route path="/select" component={SelectPage} />
|
||||||
<Route path="/ai" component={AIPageRoute} />
|
<Route path="/ai" component={AIPageRoute} />
|
||||||
<Route path="/community" component={CommunityPage} />
|
<Route path="/community" component={CommunityPage} />
|
||||||
|
<Route path="/changelog" component={ChangelogPage} />
|
||||||
<Route path="/404" component={NotFound} />
|
<Route path="/404" component={NotFound} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "Demomodus: Vom Gerät getrennt.",
|
"powerOffToastDemo": "Demomodus: Vom Gerät getrennt.",
|
||||||
"powerOffToastSent": "Abschaltbefehl gesendet.",
|
"powerOffToastSent": "Abschaltbefehl gesendet.",
|
||||||
"doubleClickHint": "Doppelklick zum Eintreten",
|
"doubleClickHint": "Doppelklick zum Eintreten",
|
||||||
"backToLegacy": "Alte Version"
|
"backToLegacy": "Alte Version",
|
||||||
|
"changelog": "Neuerungen",
|
||||||
|
"changelogLoadFailed": "Laden fehlgeschlagen, bitte später erneut versuchen"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "VU-Meter Galerie"
|
"title": "VU-Meter Galerie"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "Demo mode: disconnected from the device.",
|
"powerOffToastDemo": "Demo mode: disconnected from the device.",
|
||||||
"powerOffToastSent": "Shutdown command sent.",
|
"powerOffToastSent": "Shutdown command sent.",
|
||||||
"doubleClickHint": "Double-click to enter",
|
"doubleClickHint": "Double-click to enter",
|
||||||
"backToLegacy": "Legacy version"
|
"backToLegacy": "Legacy version",
|
||||||
|
"changelog": "What's New",
|
||||||
|
"changelogLoadFailed": "Failed to load, please try again later"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "VU meter gallery"
|
"title": "VU meter gallery"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "Modo demo: desconectado del dispositivo.",
|
"powerOffToastDemo": "Modo demo: desconectado del dispositivo.",
|
||||||
"powerOffToastSent": "Comando de apagado enviado.",
|
"powerOffToastSent": "Comando de apagado enviado.",
|
||||||
"doubleClickHint": "Doble clic para entrar",
|
"doubleClickHint": "Doble clic para entrar",
|
||||||
"backToLegacy": "Versión anterior"
|
"backToLegacy": "Versión anterior",
|
||||||
|
"changelog": "Novedades",
|
||||||
|
"changelogLoadFailed": "Error al cargar, inténtelo de nuevo más tarde"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "Galería de VU-metro"
|
"title": "Galería de VU-metro"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "Mode démo : déconnecté de l'appareil.",
|
"powerOffToastDemo": "Mode démo : déconnecté de l'appareil.",
|
||||||
"powerOffToastSent": "Commande d'extinction envoyée.",
|
"powerOffToastSent": "Commande d'extinction envoyée.",
|
||||||
"doubleClickHint": "Double-clic pour entrer",
|
"doubleClickHint": "Double-clic pour entrer",
|
||||||
"backToLegacy": "Ancienne version"
|
"backToLegacy": "Ancienne version",
|
||||||
|
"changelog": "Nouveautés",
|
||||||
|
"changelogLoadFailed": "Échec du chargement, veuillez réessayer plus tard"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "Galerie VU-mètre"
|
"title": "Galerie VU-mètre"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "演示模式:已中斷與裝置的連線",
|
"powerOffToastDemo": "演示模式:已中斷與裝置的連線",
|
||||||
"powerOffToastSent": "已傳送關機指令",
|
"powerOffToastSent": "已傳送關機指令",
|
||||||
"doubleClickHint": "按兩下進入",
|
"doubleClickHint": "按兩下進入",
|
||||||
"backToLegacy": "返回舊版"
|
"backToLegacy": "返回舊版",
|
||||||
|
"changelog": "更新說明",
|
||||||
|
"changelogLoadFailed": "載入失敗,請稍後再試"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "VU 表庫"
|
"title": "VU 表庫"
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
"powerOffToastDemo": "演示模式:已断开与设备的连接",
|
"powerOffToastDemo": "演示模式:已断开与设备的连接",
|
||||||
"powerOffToastSent": "已发送关机指令",
|
"powerOffToastSent": "已发送关机指令",
|
||||||
"doubleClickHint": "双击进入",
|
"doubleClickHint": "双击进入",
|
||||||
"backToLegacy": "返回旧版"
|
"backToLegacy": "返回旧版",
|
||||||
|
"changelog": "更新说明",
|
||||||
|
"changelogLoadFailed": "加载失败,请稍后重试"
|
||||||
},
|
},
|
||||||
"vuPage": {
|
"vuPage": {
|
||||||
"title": "VU 表库"
|
"title": "VU 表库"
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/* ============================================================
|
||||||
|
CHANGELOG PAGE — Release notes / 更新说明
|
||||||
|
Content is loaded from /changelog.md (public dir) at runtime,
|
||||||
|
so updating the notes only requires editing that markdown file.
|
||||||
|
============================================================ */
|
||||||
|
import BottomNav from "@/components/BottomNav";
|
||||||
|
import { useDevice } from "@/contexts/DeviceContext";
|
||||||
|
import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
|
||||||
|
import { resolveLocalePack } from "@/locales/resolveLocale";
|
||||||
|
import localeZh from "@/locales/data-zh.json";
|
||||||
|
import { ChevronLeft } from "lucide-react";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
import { Streamdown } from "streamdown";
|
||||||
|
|
||||||
|
type HomeLocale = NonNullable<(typeof localeZh)["home"]>;
|
||||||
|
|
||||||
|
export default function ChangelogPage() {
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
const { deviceState } = useDevice();
|
||||||
|
|
||||||
|
const homeText = useMemo((): HomeLocale => {
|
||||||
|
const pack = resolveLocalePack(deviceState?.language);
|
||||||
|
return (pack.home ?? {}) as HomeLocale;
|
||||||
|
}, [deviceState?.language]);
|
||||||
|
|
||||||
|
const pageTitle = usePageTitleWithConnection(homeText.changelog ?? "更新说明");
|
||||||
|
|
||||||
|
const [content, setContent] = useState<string | null>(null);
|
||||||
|
const [failed, setFailed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
fetch(`${import.meta.env.BASE_URL}changelog.md`)
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then((text) => {
|
||||||
|
if (!cancelled) setContent(text);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setFailed(true);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-[100dvh] bg-black flex flex-col pb-[calc(env(safe-area-inset-bottom,0px)+56px)]">
|
||||||
|
<div className="page-header">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setLocation("/")}
|
||||||
|
className="mr-4 text-white/60 active:text-white transition-colors"
|
||||||
|
aria-label="Back"
|
||||||
|
>
|
||||||
|
<ChevronLeft size={24} />
|
||||||
|
</button>
|
||||||
|
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">{pageTitle}</h1>
|
||||||
|
<div className="w-8" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 px-4 pt-4 overflow-y-auto">
|
||||||
|
<div
|
||||||
|
className="ios-list-group p-4"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(180deg, rgba(34,36,40,0.95) 0%, rgba(24,26,30,0.95) 100%)",
|
||||||
|
border: "1px solid rgba(255,255,255,0.1)",
|
||||||
|
borderRadius: 14,
|
||||||
|
boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{failed ? (
|
||||||
|
<p className="text-[14px] text-white/45 text-center py-10">{homeText.changelogLoadFailed ?? "加载失败,请稍后重试"}</p>
|
||||||
|
) : content ? (
|
||||||
|
<div className="markdown-body text-[14px] leading-relaxed text-white/85 [&_h1]:text-[18px] [&_h1]:font-bold [&_h1]:text-white [&_h1]:mb-3 [&_h2]:text-[16px] [&_h2]:font-semibold [&_h2]:text-[#00FFF6] [&_h2]:mt-5 [&_h2]:mb-2 [&_h3]:text-[14px] [&_h3]:font-semibold [&_h3]:text-white/90 [&_h3]:mt-3 [&_h3]:mb-1.5 [&_li]:mb-1 [&_blockquote]:border-l-2 [&_blockquote]:border-white/20 [&_blockquote]:pl-3 [&_blockquote]:text-white/55 [&_hr]:border-white/10 [&_hr]:my-4 [&_code]:bg-white/10 [&_code]:rounded [&_code]:px-1 [&_code]:text-[13px]">
|
||||||
|
<Streamdown>{content}</Streamdown>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center py-10">
|
||||||
|
<div className="w-6 h-6 rounded-full border-2 border-white/20 border-t-[#00FFF6] animate-spin" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNav />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
SquareArrowRightEnter,
|
SquareArrowRightEnter,
|
||||||
SquareArrowRightExit,
|
SquareArrowRightExit,
|
||||||
FileMusic,
|
FileMusic,
|
||||||
|
BookOpenCheck,
|
||||||
Minus,
|
Minus,
|
||||||
Plus,
|
Plus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -954,6 +955,11 @@ export default function Home() {
|
|||||||
thumbnail={`${import.meta.env.BASE_URL}images/vu/vu${(ds.vu ?? 0) + 1}.png`}
|
thumbnail={`${import.meta.env.BASE_URL}images/vu/vu${(ds.vu ?? 0) + 1}.png`}
|
||||||
onClick={() => setLocation("/vu")}
|
onClick={() => setLocation("/vu")}
|
||||||
/>
|
/>
|
||||||
|
<ListRow
|
||||||
|
icon={<BookOpenCheck size={18} />}
|
||||||
|
label={homeText.changelog ?? "更新说明"}
|
||||||
|
onClick={() => setLocation("/changelog")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user