From f48f0c8d51f171a5c07a20399bf0701d527cb936 Mon Sep 17 00:00:00 2001 From: noelorin Date: Sat, 26 Sep 2026 03:13:48 +0800 Subject: [PATCH] =?UTF-8?q?chore(product):=20replace=20template=20branding?= =?UTF-8?q?=20with=20=E5=BC=B9=E5=BC=B9=20Web=20copy=20and=20product=20SEO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESIGN.md | 8 +-- README.md | 6 ++- app/web/src/components/pages/HomeBody.tsx | 63 +++++++++++++++++++++++ app/web/src/layouts/AppLayout.astro | 16 ++---- app/web/src/lib/seo.ts | 20 +++---- app/web/src/pages/llms-full.txt.ts | 20 +++---- app/web/src/views/dashboard.astro | 22 ++------ app/web/src/views/home.astro | 38 ++------------ e2e/home.spec.ts | 26 ++++++++++ packages/i18n/package.json | 2 +- packages/i18n/src/messages/en.ts | 39 +++++++------- packages/i18n/src/messages/zh-CN.ts | 42 +++++++-------- 12 files changed, 171 insertions(+), 131 deletions(-) create mode 100644 app/web/src/components/pages/HomeBody.tsx create mode 100644 e2e/home.spec.ts diff --git a/DESIGN.md b/DESIGN.md index 7d8d7f7..4c6d51f 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,6 +1,6 @@ -# App Template — 设计方向 +# 弹弹 Web — 设计方向 -脚手架视觉基线:**深空工业中控终端**。不绑定具体产品叙事;下游可在此 palette / type 上换肤,但 **`@app/design-tokens` 色板 token 路径约定不变**(改值可以,改结构需同步全局)。 +产品视觉基线:**shadcn/ui Dashboard**(媒体五页侧栏/顶栏导航、卡片网格、中性灰阶);模板深空工业 token 仍可用于非媒体页,但 **`@app/design-tokens` 色板 token 路径约定不变**(改值可以,改结构需同步全局)。 ## Style anchor 深色工业 HUD / 控制台:近黑底、信号黄强调、发丝线框、等宽铭牌。 @@ -36,8 +36,8 @@ ## Interaction - 主题:系统 / 亮 / 暗(`app-theme`) - 语言:cookie `app-lang` + Accept-Language -- SSR 输出结构与内容;动效只在浏览器 effect 内绑定 -- 尊重 `prefers-reduced-motion` +- **薄 SSR**:服务端输出布局 / head / 导航;页面主体(`components/pages/*Body`)`client:load` 在浏览器渲染 +- 动效只在浏览器 effect 内绑定;尊重 `prefers-reduced-motion` ## 范围 `tokens.css` · `globals.css` · `AppLayout.astro` · `ThemeSwitch` · `UserMenu` · `home` / `dashboard` 视图 · `ui/*` diff --git a/README.md b/README.md index 588f928..3bf913e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# App Template — Astro + tRPC + Drizzle Monorepo +# 弹弹 Web — 本地片源 · 在线弹幕 -可复用全栈脚手架:**不绑定产品业务**,只保留鉴权 + `user` 垂直示例,演示完整分层接线。 +多用户 Web 媒体中心:WebDAV/OpenList 挂载、媒体库扫描与 Bangumi 刮削、ArtPlayer 播放与弹幕(开放网络 + 本地 XML)。 + +基于 monorepo 分层(types → models → db → dao → trpc → web),本仓库按**产品仓**维护。 ``` . diff --git a/app/web/src/components/pages/HomeBody.tsx b/app/web/src/components/pages/HomeBody.tsx new file mode 100644 index 0000000..43e4d9a --- /dev/null +++ b/app/web/src/components/pages/HomeBody.tsx @@ -0,0 +1,63 @@ +/** + * 首页主体:薄 SSR — Astro 壳负责 head/导航,本组件在浏览器渲染。 + */ +import { t, type Locale } from "@app/i18n"; + +export interface HomeBodyProps { + locale: Locale; +} + +export default function HomeBody({ locale }: HomeBodyProps) { + const tr = (key: Parameters[1]) => t(locale, key); + const libraryHref = locale === "zh-CN" ? "/zh-CN/library" : "/en/library"; + const loginHref = locale === "zh-CN" ? "/zh-CN/auth/login" : "/en/auth/login"; + + return ( +
+
+

{tr("meta.tagline")}

+

+ {tr("home.heroLine1")} +
+ {tr("home.heroLine2")} +

+ +

+ {tr("home.subtitle")} +

+ +
+ +
+
+

{tr("home.stackTitle")}

+

+ {tr("home.stackBody")} +

+
+
+

{tr("home.layerTitle")}

+

+ {tr("home.layerBody")} +

+
+
+
+ ); +} diff --git a/app/web/src/layouts/AppLayout.astro b/app/web/src/layouts/AppLayout.astro index a5a3026..242a3dc 100644 --- a/app/web/src/layouts/AppLayout.astro +++ b/app/web/src/layouts/AppLayout.astro @@ -40,7 +40,6 @@ const { const brandName = brand ?? t(locale, "meta.brand"); const nav = navProp ?? []; -const isProductBrand = Boolean(brand) && brand !== t(locale, "meta.brand"); const siteSeo = getSiteSeo(locale); const pageSeoInput: PageSeoInput = { @@ -54,12 +53,9 @@ const jsonLdHtml = toJsonLdScript(buildDefaultJsonLd(siteSeo, pageSeoInput)); const pageTitle = headTags.find((tTag) => tTag.key === "og:title")?.value ?? title; const homeHref = locale === "zh-CN" ? "/zh-CN" : "/en"; -const brandHref = isProductBrand ? (locale === "zh-CN" ? "/zh-CN/library" : "/en/library") : homeHref; -const brandInitial = isProductBrand - ? brandName.charAt(0) - : locale === "zh-CN" - ? "模" - : "A"; +const libraryHref = locale === "zh-CN" ? "/zh-CN/library" : "/en/library"; +const brandHref = hideSideRail ? libraryHref : homeHref; +const brandInitial = brandName.charAt(0) || "弹"; --- @@ -222,10 +218,8 @@ const brandInitial = isProductBrand
)} diff --git a/app/web/src/lib/seo.ts b/app/web/src/lib/seo.ts index 337f96a..36f3dbe 100644 --- a/app/web/src/lib/seo.ts +++ b/app/web/src/lib/seo.ts @@ -17,8 +17,8 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig { const name = t(locale, "meta.brand"); const isZh = locale === "zh-CN"; const description = isZh - ? "Astro + tRPC + Drizzle monorepo 应用脚手架,开箱即用的分层基线。" - : "Astro + tRPC + Drizzle monorepo application scaffold with a ready vertical baseline."; + ? "挂载 WebDAV/OpenList 片源的 Web 媒体库:扫描、Bangumi 刮削、ArtPlayer 播放与在线弹幕。" + : "Web media library for WebDAV/OpenList: scan, Bangumi scrape, ArtPlayer playback and online danmaku."; return { siteUrl, @@ -29,15 +29,15 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig { organization: { name, url: siteUrl }, llms: { summary: isZh - ? "这是可复用的应用脚手架模板:Astro SSR + React islands + tRPC + Drizzle/Turso,附带鉴权与用户垂直示例。" - : "Reusable application scaffold: Astro SSR + React islands + tRPC + Drizzle/Turso, with auth and a user vertical example.", + ? "弹弹 Web:多用户 Web 媒体中心。WebDAV 挂载、媒体库扫描、Bangumi 刮削、ArtPlayer 播放与弹弹play 开放弹幕网络中继,本地 XML 兜底。" + : "DanDan Web: multi-user web media hub. WebDAV mounts, library scan, Bangumi scraping, ArtPlayer playback, dandanplay open danmaku relay with local XML fallback.", details: [ isZh - ? "分层:types → models → db → dao → trpc → web。业务实体按 user 切片扩展,禁止跨层。" - : "Layers: types → models → db → dao → trpc → web. Extend domains via the user slice; never skip layers.", + ? "分层:types → models → db → dao → trpc → web。媒体域按垂直切片扩展。" + : "Layers: types → models → db → dao → trpc → web. Extend the media domain as a vertical slice.", isZh - ? "不绑定具体产品业务;works 等领域逻辑应在业务仓库中添加。" - : "No product domain baked in; add domains such as works in your product repo.", + ? "挂载凭据仅存服务端加密;浏览器不直连网盘密码。" + : "Mount credentials are encrypted server-side; the browser never sees remote passwords.", ], pages: PUBLIC_PATHS.map((basePath) => ({ path: pathForLocale(locale, basePath), @@ -45,9 +45,9 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig { description, })), discouraged: isZh - ? ["/dashboard、/settings 等需登录页面", "/auth/* 等认证页", "/api/* 接口响应"] + ? ["/library、/watch 等需登录页面", "/auth/* 等认证页", "/api/* 接口响应"] : [ - "/dashboard, /settings and other app pages", + "/library, /watch and other app pages", "/auth/* and auth pages", "/api/* responses", ], diff --git a/app/web/src/pages/llms-full.txt.ts b/app/web/src/pages/llms-full.txt.ts index 1405007..f3003bf 100644 --- a/app/web/src/pages/llms-full.txt.ts +++ b/app/web/src/pages/llms-full.txt.ts @@ -15,16 +15,16 @@ function buildFor(locale: Locale): string { } return buildLlmsFullTxt(siteSeo, { - title: `${t(locale, "meta.brand")} · scaffold index`, + title: `${t(locale, "meta.brand")} · product guide`, sections: [ { heading: t(locale, "home.stackTitle"), path: "/zh-CN", summary: t(locale, "home.stackBody"), points: [ - "Astro 7 SSR + React 19 islands", - "tRPC v11 + Drizzle ORM + Turso/libSQL", - "Tailwind CSS 4 + Biome + Yarn 4 workspaces + Turbo", + "WebDAV / OpenList 挂载代理(凭据服务端加密)", + "扫描入库 + Bangumi 刮削 + ArtPlayer 播放", + "弹弹play 开放弹幕网络中继,本地 XML 兜底", ], }, { @@ -32,19 +32,19 @@ function buildFor(locale: Locale): string { path: "/zh-CN", summary: t(locale, "home.layerBody"), points: [ - "types → models → db → dao → trpc → web", - "user / auth 为垂直示例,可替换为业务实体", + "多用户:每人独立媒体库与播放进度", + "媒体页 shadcn Dashboard 导航:媒体库 / 浏览 / 挂载 / 弹幕设置", ], }, ], faqs: [ { - question: "这个模板包含业务逻辑吗?", - answer: "不包含产品业务。仅保留鉴权与 user 垂直示例,用于演示分层接线;具体业务应在下游仓库添加。", + question: "如何接入 OpenList?", + answer: "在挂载管理填写 OpenList 暴露的 WebDAV 地址(如 https://host/dav)与账号,测试连接后保存。", }, { - question: "如何新增业务实体?", - answer: "按 user 切片扩展:@app/types 加 schema → @app/models 建表 → @app/dao 加查询 → @app/trpc 加 service/router → @app/web 挂页面。", + question: "弹幕从哪里来?", + answer: "优先走弹弹play 开放弹幕网络(需配置 AppId/AppSecret);失败或未配置时可在播放页导入本地 XML。", }, ], }); diff --git a/app/web/src/views/dashboard.astro b/app/web/src/views/dashboard.astro index 13f7a56..99642bf 100644 --- a/app/web/src/views/dashboard.astro +++ b/app/web/src/views/dashboard.astro @@ -1,7 +1,5 @@ --- -import { alternatePath, type Locale, t } from "@app/i18n"; -import { Toaster } from "@app/ui"; -import UserList from "~/components/UserList"; +import { alternatePath, type Locale, localePath, t } from "@app/i18n"; import AppLayout from "~/layouts/AppLayout.astro"; export interface Props { @@ -10,6 +8,9 @@ export interface Props { const { locale } = Astro.props; const other: Locale = locale === "zh-CN" ? "en" : "zh-CN"; + +// 产品:/dashboard 即媒体库(保留路由以免旧链接与 e2e 404) +return Astro.redirect(localePath(locale, "/library")); --- -
-
-

{t(locale, "dashboard.label")}

-

{t(locale, "dashboard.heading")}

- -

- {t(locale, "dashboard.desc")} -

-
- -
-
-
- +

{t(locale, "dashboard.desc")}

diff --git a/app/web/src/views/home.astro b/app/web/src/views/home.astro index 9bbbcee..efa3eff 100644 --- a/app/web/src/views/home.astro +++ b/app/web/src/views/home.astro @@ -1,5 +1,6 @@ --- import { alternatePath, type Locale, t } from "@app/i18n"; +import HomeBody from "~/components/pages/HomeBody"; import AppLayout from "~/layouts/AppLayout.astro"; export interface Props { @@ -7,49 +8,16 @@ export interface Props { } const { locale } = Astro.props; -const tr = (key: Parameters[1]) => t(locale, key); const otherLocale: Locale = locale === "zh-CN" ? "en" : "zh-CN"; const switchHref = alternatePath(Astro.url.pathname, otherLocale); -const dashHref = locale === "zh-CN" ? "/zh-CN/dashboard" : "/en/dashboard"; -const loginHref = locale === "zh-CN" ? "/zh-CN/auth/login" : "/en/auth/login"; --- -
-
-

{tr("meta.tagline")}

-

- {tr("home.heroLine1")}
{tr("home.heroLine2")} -

- -

- {tr("home.subtitle")} -

- -
- -
-
-

{tr("home.stackTitle")}

-

- {tr("home.stackBody")} -

-
-
-

{tr("home.layerTitle")}

-

- {tr("home.layerBody")} -

-
-
-
+
diff --git a/e2e/home.spec.ts b/e2e/home.spec.ts new file mode 100644 index 0000000..180be1e --- /dev/null +++ b/e2e/home.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from "@playwright/test"; + +test("中文首页渲染 hero 与 CTA", async ({ page }) => { + await page.goto("/zh-CN"); + const h1 = page.getByRole("heading", { level: 1 }); + await expect(h1).toContainText("本地片源"); + await expect(h1).toContainText("在线弹幕"); + await expect(page.getByRole("link", { name: "登录 / 注册" })).toBeVisible(); + await expect(page.getByRole("link", { name: "打开媒体库" })).toBeVisible(); +}); + +test("英文首页渲染英文文案", async ({ page }) => { + await page.goto("/en"); + const h1 = page.getByRole("heading", { level: 1 }); + await expect(h1).toBeVisible(); + await expect(h1).not.toContainText("应用"); + await expect(page.getByRole("link", { name: "登录 / 注册" })).toHaveCount(0); +}); + +test("首页无客户端控制台错误", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + await page.goto("/zh-CN"); + await page.waitForLoadState("networkidle"); + expect(errors).toEqual([]); +}); diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 60b2f84..f7463f8 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -17,7 +17,7 @@ "scripts": { "build": "tsc --project tsconfig.build.json", "typecheck": "tsc --noEmit", - "lint": "biome check ." + "lint": "../../node_modules/.bin/biome check ." }, "devDependencies": { "@types/node": "^22.15.29", diff --git a/packages/i18n/src/messages/en.ts b/packages/i18n/src/messages/en.ts index b915ad9..48d928c 100644 --- a/packages/i18n/src/messages/en.ts +++ b/packages/i18n/src/messages/en.ts @@ -2,13 +2,13 @@ import type { MessageKey } from "./zh-CN.js"; /** 与 zh-CN 同构;类型强制 key 对齐。 */ export const en: Record = { - "meta.brand": "App Template", + "meta.brand": "DanDan Web", "meta.product": "DanDan Web", "meta.productTagline": "Local files · online danmaku", - "meta.tagline": "Astro · tRPC · Drizzle scaffold", + "meta.tagline": "Local video · online danmaku", "nav.home": "Home", - "nav.dashboard": "Dashboard", + "nav.dashboard": "Console", "nav.primary": "Primary nav", "nav.brandHome": "Home", "nav.library": "Library", @@ -17,26 +17,26 @@ export const en: Record = { "nav.mounts": "Mounts", "nav.danmaku": "Danmaku settings", - "home.title": "App Template", + "home.title": "DanDan Web", "home.subtitle": - "Astro + tRPC + Drizzle monorepo scaffold. Sign in to open the dashboard user-list example.", - "home.heroLine1": "App", - "home.heroLine2": "Template", - "home.ctaDashboard": "Open dashboard", + "Mount WebDAV / OpenList libraries, scan into your media library, play with ArtPlayer and online danmaku.", + "home.heroLine1": "Local files", + "home.heroLine2": "Online danmaku", + "home.ctaDashboard": "Open library", "home.ctaLogin": "Sign in / Register", - "home.stackTitle": "Stack", + "home.stackTitle": "Features", "home.stackBody": - "Astro 7 SSR · React 19 islands · tRPC v11 · Drizzle + Turso · Tailwind 4 · Biome · Yarn 4 + Turbo.", - "home.layerTitle": "Layering", + "WebDAV mounts · Bangumi scraping · ArtPlayer + danmaku · multi-user progress · media dashboard.", + "home.layerTitle": "Get started", "home.layerBody": - "types → models → db → dao → trpc → web. Copy the user vertical slice for new entities; never skip layers.", - "home.footer": "APP TEMPLATE · SSR", + "Register → add a WebDAV mount → scan in Browse → play from the Library with danmaku.", + "home.footer": "DANDAN WEB · SSR", - "dashboard.title": "Dashboard", - "dashboard.heading": "Users", - "dashboard.label": "Users", + "dashboard.title": "Console", + "dashboard.heading": "Media hub", + "dashboard.label": "Console", "dashboard.desc": - "Full vertical example: tRPC → service → dao → db (scaffold baseline — replace with your domain).", + "Redirecting to the library… If nothing happens, open Library in the sidebar.", "library.title": "Library", "library.scan": "Scan mounts", @@ -117,7 +117,7 @@ export const en: Record = { "auth.login.title": "Sign in", "auth.login.heading": "Sign in", - "auth.login.subtitle": "Continue with email and password", + "auth.login.subtitle": "Continue with username and password", "auth.login.submit": "Sign in", "auth.login.noAccount": "No account yet?", "auth.login.toRegister": "Register", @@ -144,8 +144,9 @@ export const en: Record = { "auth.settings.changeHeading": "Change password", "auth.settings.changeSubmit": "Update password", "auth.settings.hint": "Other sessions will be signed out after a password change.", + "auth.field.username": "Username", "auth.field.name": "Name", - "auth.field.email": "Email", + "auth.field.email": "Email (optional, for password reset)", "auth.field.password": "Password", "auth.field.currentPassword": "Current password", "auth.field.newPassword": "New password", diff --git a/packages/i18n/src/messages/zh-CN.ts b/packages/i18n/src/messages/zh-CN.ts index 6325ebb..353c79e 100644 --- a/packages/i18n/src/messages/zh-CN.ts +++ b/packages/i18n/src/messages/zh-CN.ts @@ -1,16 +1,15 @@ /** * zh-CN 文案:key 权威源。en.ts 必须与本文件 key 同构。 * 展示名与 description 的插值用 {name}。 - * 只保留脚手架基线文案;业务文案按同样模式追加。 */ export const zhCN = { - "meta.brand": "应用模板", + "meta.brand": "弹弹 Web", "meta.product": "弹弹 Web", "meta.productTagline": "本地片源 · 在线弹幕", - "meta.tagline": "Astro · tRPC · Drizzle 脚手架", + "meta.tagline": "本地视频 · 在线弹幕", "nav.home": "首页", - "nav.dashboard": "仪表盘", + "nav.dashboard": "控制台", "nav.primary": "主导航", "nav.brandHome": "首页", "nav.library": "媒体库", @@ -19,25 +18,23 @@ export const zhCN = { "nav.mounts": "挂载管理", "nav.danmaku": "弹幕设置", - "home.title": "应用模板", - "home.subtitle": "Astro + tRPC + Drizzle monorepo 脚手架。登录后可进入仪表盘查看用户列表示例。", - "home.heroLine1": "应用", - "home.heroLine2": "模板", - "home.ctaDashboard": "打开仪表盘", + "home.title": "弹弹 Web", + "home.subtitle": "挂载 WebDAV / OpenList 片源,扫描入库,用 ArtPlayer 播放并加载在线弹幕。", + "home.heroLine1": "本地片源", + "home.heroLine2": "在线弹幕", + "home.ctaDashboard": "打开媒体库", "home.ctaLogin": "登录 / 注册", - "home.stackTitle": "技术栈", + "home.stackTitle": "能力", "home.stackBody": - "Astro 7 SSR · React 19 islands · tRPC v11 · Drizzle + Turso · Tailwind 4 · Biome · Yarn 4 + Turbo。", - "home.layerTitle": "分层约定", - "home.layerBody": - "types → models → db → dao → trpc → web。业务实体按 user 垂直切片复制扩展,禁止跨层引用。", - "home.footer": "APP TEMPLATE · SSR", + "WebDAV 挂载 · Bangumi 刮削 · ArtPlayer + 弹幕 · 多用户进度 · shadcn 风媒体库。", + "home.layerTitle": "快速开始", + "home.layerBody": "注册登录 → 挂载管理添加 WebDAV → 文件浏览扫描 → 媒体库播放并看弹幕。", + "home.footer": "DANDAN WEB · SSR", - "dashboard.title": "仪表盘", - "dashboard.heading": "用户列表", - "dashboard.label": "Users", - "dashboard.desc": - "tRPC → service → dao → db 的完整垂直示例(脚手架基线,可替换为你的业务实体)。", + "dashboard.title": "控制台", + "dashboard.heading": "媒体中心", + "dashboard.label": "Console", + "dashboard.desc": "正在前往媒体库……若未跳转,请手动打开侧栏「媒体库」。", "library.title": "媒体库", "library.scan": "扫描挂载", @@ -117,7 +114,7 @@ export const zhCN = { "auth.login.title": "登录", "auth.login.heading": "登录", - "auth.login.subtitle": "使用邮箱与密码继续", + "auth.login.subtitle": "使用用户名与密码继续", "auth.login.submit": "登录", "auth.login.noAccount": "还没有账号?", "auth.login.toRegister": "注册", @@ -144,8 +141,9 @@ export const zhCN = { "auth.settings.changeHeading": "修改密码", "auth.settings.changeSubmit": "更新密码", "auth.settings.hint": "更新密码后其它设备的会话会失效。", + "auth.field.username": "用户名", "auth.field.name": "名称", - "auth.field.email": "邮箱", + "auth.field.email": "邮箱(可选,用于找回密码)", "auth.field.password": "密码", "auth.field.currentPassword": "当前密码", "auth.field.newPassword": "新密码",