docs: document admin dashboard redesign
This commit is contained in:
+5
-3
@@ -9,7 +9,7 @@
|
|||||||
- 擁有 `src/` 全層,亦直接擁有以下冇獨立 child doc 的部分:
|
- 擁有 `src/` 全層,亦直接擁有以下冇獨立 child doc 的部分:
|
||||||
- `theme/system.ts` — Chakra system(tokens / semanticTokens / fonts / globalCss),前台顏色同字體的唯一來源。現行設計:editorial 極簡風(暖米白 `#FAF8F4` 底、琥珀金 `brand` accent、墨黑 `ink`、Noto Serif HK 標題),詳見 `docs/superpowers/specs/2026-09-11-editorial-redesign-design.md`。
|
- `theme/system.ts` — Chakra system(tokens / semanticTokens / fonts / globalCss),前台顏色同字體的唯一來源。現行設計:editorial 極簡風(暖米白 `#FAF8F4` 底、琥珀金 `brand` accent、墨黑 `ink`、Noto Serif HK 標題),詳見 `docs/superpowers/specs/2026-09-11-editorial-redesign-design.md`。
|
||||||
- `layouts/Base.astro` — 前台 SEO head(canonical / OG / Twitter / theme-color / JSON-LD `jsonLd` prop / sitemap link)+ Noto Sans HK(非阻塞載入,`media="print" onload` + `<noscript>` fallback)+ 同意後先載入嘅 analytics(`gaId` / `metaPixelId` props,見下面 Analytics & consent)。
|
- `layouts/Base.astro` — 前台 SEO head(canonical / OG / Twitter / theme-color / JSON-LD `jsonLd` prop / sitemap link)+ Noto Sans HK(非阻塞載入,`media="print" onload` + `<noscript>` fallback)+ 同意後先載入嘅 analytics(`gaId` / `metaPixelId` props,見下面 Analytics & consent)。
|
||||||
- `layouts/AdminLayout.astro` — 後台外框,純 CSS、與前台一致的色系。
|
- `layouts/AdminLayout.astro` — 後台外框:墨黑左側 sidebar(`/admin` 總覽、內容管理、系統分組)+米白內容區,純 CSS、色值集中喺 CSS variables(`--ink` / `--brand` / `--bg` 等),手機用純 CSS checkbox 漢堡開合;同時保留 R2 上傳 script(`ImageField` 依賴 `.image-url` / `.image-preview` / `.image-status`)。
|
||||||
- `config.ts` — build-time 靜態頁用的 `SITE_NAME` / `SITE_TAGLINE`(可管理內容一律放 D1,唔放呢度)。
|
- `config.ts` — build-time 靜態頁用的 `SITE_NAME` / `SITE_TAGLINE`(可管理內容一律放 D1,唔放呢度)。
|
||||||
- `middleware.ts` — 保護 `/admin`:設 `locals.isAdmin`,無 `ADMIN_PASSWORD` 回 503,未登入導向 `/admin/login`。
|
- `middleware.ts` — 保護 `/admin`:設 `locals.isAdmin`,無 `ADMIN_PASSWORD` 回 503,未登入導向 `/admin/login`。
|
||||||
- `env.d.ts` — `App.Locals.isAdmin` 型別宣告。
|
- `env.d.ts` — `App.Locals.isAdmin` 型別宣告。
|
||||||
@@ -49,14 +49,16 @@
|
|||||||
### Admin(`pages/admin/`)
|
### Admin(`pages/admin/`)
|
||||||
|
|
||||||
- 所有 `/admin` 路由**必須** `export const prerender = false`。
|
- 所有 `/admin` 路由**必須** `export const prerender = false`。
|
||||||
|
- **視覺**:全部用 `AdminLayout.astro` 的墨黑 sidebar shell,色值一律用其 CSS variables(唔好硬編色)。`/admin` 係總覽 dashboard。
|
||||||
- **認證**由 `middleware.ts` 統一處理;登入喺 `admin/login.astro`(`checkPassword` + `createSession`),登出喺 `logout.ts`。
|
- **認證**由 `middleware.ts` 統一處理;登入喺 `admin/login.astro`(`checkPassword` + `createSession`),登出喺 `logout.ts`。
|
||||||
- **UI**:全部用 `AdminLayout.astro`,純 Astro SSR 表單(`POST` + `formData`),**唔引入** React / Chakra。
|
- **UI**:全部用 `AdminLayout.astro`,純 Astro SSR 表單(`POST` + `formData`),**唔引入** React / Chakra。
|
||||||
- **流程**:每個 POST 處理完 `Astro.redirect` 返對應列表頁;`/admin/ai` 用 POST/Redirect/Get + `?ok=<key>` + `okMessages` 顯示成功提示(避免重複提交)。
|
- **流程**:每個 POST 處理完 `Astro.redirect` 返對應列表頁;`/admin/ai` 用 POST/Redirect/Get + `?ok=<key>` + `okMessages` 顯示成功提示(避免重複提交)。成功提示用 `.notice`(唔好再用 `.badge.published`)。
|
||||||
|
- **總覽**:`index.astro`(`/admin`)係 dashboard,資料由 `data/content.ts` 的 `getDashboardData(db)` 提供(統計卡/待辦提示/最近更新文章/系統狀態);AI secrets 由頁面層 `getEnv()` 讀。
|
||||||
- **表單驗證**:所有 admin 表單經 `parseForm(form, schema)`(`src/schemas/`),失敗時以 `errors` 逐欄顯示,唔好手寫逐欄檢查。
|
- **表單驗證**:所有 admin 表單經 `parseForm(form, schema)`(`src/schemas/`),失敗時以 `errors` 逐欄顯示,唔好手寫逐欄檢查。
|
||||||
- **通用動作**:`add` / `save` / `delete` / `up` / `down`(排序以交換 `sortOrder` 實作)。
|
- **通用動作**:`add` / `save` / `delete` / `up` / `down`(排序以交換 `sortOrder` 實作)。
|
||||||
- **通用內容編輯器**:`content/[kind].astro`,`kind ∈ service | feature | step | faq`(見 `db/schema.ts` 的 `CONTENT_KINDS`),欄位標籤由檔案內 `META` 定義。
|
- **通用內容編輯器**:`content/[kind].astro`,`kind ∈ service | feature | step | faq`(見 `db/schema.ts` 的 `CONTENT_KINDS`),欄位標籤由檔案內 `META` 定義。
|
||||||
- **網站設定**:`settings.astro` 用 `data/settings-fields.ts` 的 `SETTINGS_GROUPS` / `ALL_SETTING_KEYS` 產生表單,逐 key upsert。
|
- **網站設定**:`settings.astro` 用 `data/settings-fields.ts` 的 `SETTINGS_GROUPS` / `ALL_SETTING_KEYS` 產生表單,逐 key upsert。
|
||||||
- **文章**:`index.astro` 列表、`post/[id].astro` 新增/編輯(`id === "new"` 為新增);slug 自動 `slugify` 並用 `uniqueSlug` 去重。
|
- **文章**:列表喺 `posts.astro`(`/admin/posts`)、編輯喺 `post/[id].astro`(`id === "new"` 為新增);slug 自動 `slugify` 並用 `uniqueSlug` 去重。文章存/刪後 redirect 去 `/admin/posts`。
|
||||||
- **AI 生成**:`ai.astro`(`/admin/ai`)管 AI 設定 + 關鍵字佇列,同步呼叫 `generateBlogPost`;成功會 redirect 去新草稿 `/admin/post/<id>`。生成按鈕用 inline `onsubmit` 顯示「生成中…」。
|
- **AI 生成**:`ai.astro`(`/admin/ai`)管 AI 設定 + 關鍵字佇列,同步呼叫 `generateBlogPost`;成功會 redirect 去新草稿 `/admin/post/<id>`。生成按鈕用 inline `onsubmit` 顯示「生成中…」。
|
||||||
- **圖片**:圖片欄位(settings 的 `hero_image` / `og_image`、`cases.imageUrl`、`posts.coverImage`)用 `components/admin/ImageField.astro`,可揀檔即時上傳去 R2(binding `MEDIA`),亦可貼 URL;client 端縮到最大寬 1600px 並轉 WebP 先上傳。上傳 endpoint `/admin/upload`(受 middleware 保護,`scope ∈ settings | cases | posts`、JPG/PNG/WebP/GIF、上限 8MB),出圖 endpoint `/media/[...key]`(公開、`immutable` cache)。換圖/刪除時由 `lib/media.ts` 的 `deleteMedia` 清舊 R2 檔。
|
- **圖片**:圖片欄位(settings 的 `hero_image` / `og_image`、`cases.imageUrl`、`posts.coverImage`)用 `components/admin/ImageField.astro`,可揀檔即時上傳去 R2(binding `MEDIA`),亦可貼 URL;client 端縮到最大寬 1600px 並轉 WebP 先上傳。上傳 endpoint `/admin/upload`(受 middleware 保護,`scope ∈ settings | cases | posts`、JPG/PNG/WebP/GIF、上限 8MB),出圖 endpoint `/media/[...key]`(公開、`immutable` cache)。換圖/刪除時由 `lib/media.ts` 的 `deleteMedia` 清舊 R2 檔。
|
||||||
- 前台可見性靠 `status`(`published` / `draft`);列表頁顯示全部,前台只顯示 published。
|
- 前台可見性靠 `status`(`published` / `draft`);列表頁顯示全部,前台只顯示 published。
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ D1 讀取查詢層(`content.ts`)同後台網站設定欄位定義(`setting
|
|||||||
- **查詢集中**:所有前台/Blog 的 D1 查詢寫喺 `content.ts`,唔好散落喺頁面或元件。
|
- **查詢集中**:所有前台/Blog 的 D1 查詢寫喺 `content.ts`,唔好散落喺頁面或元件。
|
||||||
- **只回前台可見**:`getItems` / `getCases` / `getPublishedPosts` 只回 `status = "published"`,並按 `sortOrder` 或 `publishedAt DESC` 排序;`getPostBySlug` 同時要求 published。
|
- **只回前台可見**:`getItems` / `getCases` / `getPublishedPosts` 只回 `status = "published"`,並按 `sortOrder` 或 `publishedAt DESC` 排序;`getPostBySlug` 同時要求 published。
|
||||||
- **首頁聚合**:`getHomeData(db)` 用 `Promise.all` 一次過攞 settings + services/features/steps/faqs + cases,形狀係 `HomeData`。
|
- **首頁聚合**:`getHomeData(db)` 用 `Promise.all` 一次過攞 settings + services/features/steps/faqs + cases,形狀係 `HomeData`。
|
||||||
|
- **後台總覽聚合**:`getDashboardData(db)` 回 `DashboardData`(`posts`/`cases`/`content`/`keywords` 各狀態計數+`recentPosts` 最新 5 篇+`postsWithoutCover`),供 `/admin` dashboard 用;只讀 DB,AI secrets 由頁面層 `getEnv()` 提供。
|
||||||
- **關鍵字佇列**:`getKeywords(db)` 回全部 `blog_keywords`(按 `createdAt`、`id` 排序),供 `/admin/ai` 用。
|
- **關鍵字佇列**:`getKeywords(db)` 回全部 `blog_keywords`(按 `createdAt`、`id` 排序),供 `/admin/ai` 用。
|
||||||
- **Slug 去重**:`uniqueSlug(db, base, selfId?)` 撞 slug 就加 `-2`、`-3`…;`selfId` 用喺更新自己時排除自己。admin 文章同 AI 生成都用。
|
- **Slug 去重**:`uniqueSlug(db, base, selfId?)` 撞 slug 就加 `-2`、`-3`…;`selfId` 用喺更新自己時排除自己。admin 文章同 AI 生成都用。
|
||||||
- **`Db` 型別**:`DrizzleD1Database<typeof schema>`。
|
- **`Db` 型別**:`DrizzleD1Database<typeof schema>`。
|
||||||
|
|||||||
Reference in New Issue
Block a user