docs: admin dashboard redesign spec and plan
This commit is contained in:
@@ -0,0 +1,189 @@
|
|||||||
|
# 後台 Dashboard 化 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 將 `/admin` 由綠色純 CSS 表單頁改造成「墨黑 sidebar + 米白內容」嘅真 dashboard:`/admin` 做總覽、文章列表搬去 `/admin/posts`,所有 admin 頁統一視覺;維持純 Astro + CSS、無 React。
|
||||||
|
|
||||||
|
**Architecture:** `AdminLayout.astro` 做單一 shell(sidebar + CSS variables),保留原有 R2 上傳 script。新增 `getDashboardData(db)` 喺 `src/data/content.ts` 提供統計,唔改 schema。`index.astro` 改寫成 dashboard,新增 `posts.astro` 承接原文章列表。
|
||||||
|
|
||||||
|
**Tech Stack:** Astro 7 SSR、Drizzle/D1、純 CSS(CSS variables)。驗證=`npm run build`(專案冇 test/lint/typecheck)。
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-09-13-admin-dashboard-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- Modify `src/layouts/AdminLayout.astro` — shell / tokens / nav(保留 upload script)
|
||||||
|
- Modify `src/data/content.ts` — 加 `getDashboardData`
|
||||||
|
- Modify `src/pages/admin/index.astro` — dashboard
|
||||||
|
- Create `src/pages/admin/posts.astro` — 文章列表(由原 index 搬)
|
||||||
|
- Modify `src/pages/admin/post/[id].astro` — redirect+取消 link
|
||||||
|
- Modify `src/pages/admin/{cases,settings,ai}.astro`、`content/[kind].astro` — 只改外觀
|
||||||
|
- Modify `src/pages/admin/login.astro` — 重新上色
|
||||||
|
- Modify `src/AGENTS.md` — Admin 段落
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: `AdminLayout` tokens + sidebar shell
|
||||||
|
|
||||||
|
**Files:** Modify `src/layouts/AdminLayout.astro`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 換 `:root` 同佈局 CSS** — 將現有 `:root`(line 138-144)+ `.inner`/`.bar*`(149-158)換成:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--ink:#1A1917; --ink-2:#24221F; --ink-3:#2E2B27;
|
||||||
|
--bg:#FAF8F4; --surface:#fff;
|
||||||
|
--fg:#1A1917; --fg-muted:#6E6862;
|
||||||
|
--border:#E7E2D9;
|
||||||
|
--brand:#D97706; --brand-strong:#B45309; --brand-soft:#FEF3C7;
|
||||||
|
--ok:#0F6E56; --ok-soft:#E1F5EE;
|
||||||
|
--warn:#854F0B; --warn-soft:#FAEEDA;
|
||||||
|
--danger:#A32D2D; --danger-soft:#FCEBEB;
|
||||||
|
--radius-sm:4px; --radius:8px; --sidebar-w:248px;
|
||||||
|
font-family:"Noto Sans HK","PingFang HK","Microsoft JhengHei",system-ui,sans-serif;
|
||||||
|
color:var(--fg); background:var(--bg); line-height:1.65; font-size:15px;
|
||||||
|
}
|
||||||
|
a{color:var(--brand-strong);text-decoration:none} a:hover{text-decoration:underline}
|
||||||
|
.shell{display:flex;min-height:100vh}
|
||||||
|
.sidebar{position:fixed;inset:0 auto 0 0;width:var(--sidebar-w);background:var(--ink);color:#EDEAE4;display:flex;flex-direction:column;padding:22px 14px;z-index:20}
|
||||||
|
.brand{display:flex;flex-direction:column;gap:2px;padding:0 10px 18px;color:#fff}
|
||||||
|
.brand-name{font-family:'Noto Serif HK',serif;font-weight:900;font-size:17px;letter-spacing:.02em}
|
||||||
|
.brand-sub{font-size:11px;color:#9A938A;letter-spacing:.28em}
|
||||||
|
.nav{display:flex;flex-direction:column;gap:2px;flex:1;overflow:auto}
|
||||||
|
.nav-group{margin:16px 10px 6px;font-size:10.5px;letter-spacing:.22em;color:#7E776E}
|
||||||
|
.nav a{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:var(--radius-sm);color:#CFC9C1;font-size:13.5px;border-left:3px solid transparent}
|
||||||
|
.nav a:hover{background:var(--ink-2);color:#fff;text-decoration:none}
|
||||||
|
.nav a.active{background:var(--ink-2);color:var(--brand);border-left-color:var(--brand);font-weight:700}
|
||||||
|
.sidebar-foot{display:flex;flex-direction:column;gap:4px;border-top:1px solid #2E2B27;padding-top:14px}
|
||||||
|
.sidebar-foot a{padding:8px 10px;color:#9A938A;font-size:13px}
|
||||||
|
.sidebar-foot a:hover{color:#fff;text-decoration:none}
|
||||||
|
.content{flex:1;margin-left:var(--sidebar-w);min-width:0}
|
||||||
|
.main{max-width:1080px;padding:36px 40px 100px;margin:0 auto}
|
||||||
|
.topbar{display:none}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 加 mobile CSS**(同一 `<style>` 內):
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (max-width:900px){
|
||||||
|
.sidebar{transform:translateX(-100%);transition:transform .2s ease}
|
||||||
|
#nav-toggle:checked ~ .sidebar{transform:translateX(0)}
|
||||||
|
.content{margin-left:0}
|
||||||
|
.topbar{display:flex;align-items:center;gap:12px;position:sticky;top:0;z-index:15;background:var(--surface);border-bottom:1px solid var(--border);padding:12px 18px}
|
||||||
|
.topbar .brand{flex-direction:row;align-items:baseline;gap:8px;padding:0;color:var(--ink)}
|
||||||
|
.topbar .brand-sub{color:var(--fg-muted)}
|
||||||
|
.hamburger{cursor:pointer;font-size:20px;line-height:1;color:var(--ink);border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 10px}
|
||||||
|
.main{padding:24px 18px 80px}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 換 body markup**(line 29-51)成:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<body>
|
||||||
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" hidden />
|
||||||
|
<div class="shell">
|
||||||
|
<aside class="sidebar">
|
||||||
|
<a class="brand" href="/admin">
|
||||||
|
<span class="brand-name">盈豐太陽能</span>
|
||||||
|
<span class="brand-sub">後台</span>
|
||||||
|
</a>
|
||||||
|
<nav class="nav">
|
||||||
|
<p class="nav-group">總覽</p>
|
||||||
|
<a href="/admin" class={isActive("/admin") ? "active" : ""}>總覽</a>
|
||||||
|
<p class="nav-group">內容管理</p>
|
||||||
|
<a href="/admin/posts" class={isActive("/admin/posts") ? "active" : ""}>文章</a>
|
||||||
|
<a href="/admin/content/service" class={isActive("/admin/content/service") ? "active" : ""}>服務</a>
|
||||||
|
<a href="/admin/content/feature" class={isActive("/admin/content/feature") ? "active" : ""}>特色</a>
|
||||||
|
<a href="/admin/content/step" class={isActive("/admin/content/step") ? "active" : ""}>流程</a>
|
||||||
|
<a href="/admin/content/faq" class={isActive("/admin/content/faq") ? "active" : ""}>常見問題</a>
|
||||||
|
<a href="/admin/cases" class={isActive("/admin/cases") ? "active" : ""}>完成案例</a>
|
||||||
|
<p class="nav-group">系統</p>
|
||||||
|
<a href="/admin/ai" class={isActive("/admin/ai") ? "active" : ""}>AI 生成</a>
|
||||||
|
<a href="/admin/settings" class={isActive("/admin/settings") ? "active" : ""}>網站設定</a>
|
||||||
|
</nav>
|
||||||
|
<div class="sidebar-foot">
|
||||||
|
<a href="/" target="_blank">睇網站 ↗</a>
|
||||||
|
<a href="/admin/logout">登出</a>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div class="content">
|
||||||
|
<header class="topbar">
|
||||||
|
<label for="nav-toggle" class="hamburger" aria-label="開合導航">☰</label>
|
||||||
|
<a class="brand" href="/admin">
|
||||||
|
<span class="brand-name">盈豐太陽能</span>
|
||||||
|
<span class="brand-sub">後台</span>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
<main class="main"><slot /></main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>/* 原 upload script 不變 */</script>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 換 form/table/card CSS**(現有 164-206)— 保留 class 名(`.card` `.badge` `.actions` `.grid2` `.image-*` `.muted` `.err` `.field-error` `.row`),色值改用 tokens:`.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px 22px;margin-bottom:18px}`;`th`/`td` 用 `var(--border)`;input/textarea/select 用 `var(--border)`+focus `outline:2px solid var(--brand-soft);border-color:var(--brand)`;`button,.btn` 用 `background:var(--ink);border-color:var(--ink);border-radius:var(--radius-sm)`,`.btn.secondary` 白底、`button.danger` 用 `--danger`/`--danger-soft`;`.badge.published{background:var(--ok-soft);color:var(--ok)}`、`.badge.draft{background:var(--warn-soft);color:var(--warn)}`。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 驗證** — `npm run build`,Expected: success(0 errors)。
|
||||||
|
- [ ] **Step 6: 目測** — `npm run dev`,喺 ≥900px 同 <900px 開 `/admin`,確認 sidebar、active 態、漢堡開合、各頁表單/表格可讀。
|
||||||
|
- [ ] **Step 7: Commit** — `git add src/layouts/AdminLayout.astro && git commit -m "feat(admin): sidebar shell and design tokens"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: `getDashboardData(db)`
|
||||||
|
|
||||||
|
**Files:** Modify `src/data/content.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 加 import** — `import { and, asc, desc, eq, isNull, ne, sql } from "drizzle-orm";`,並喺 schema import 加入 `CONTENT_KINDS`。
|
||||||
|
- [ ] **Step 2: 加 type + function**(`getKeywords` 之後):見 spec §4,實作 `DashboardData` + `getDashboardData`(用 `groupBy` + `sql\`count(*)\``,`pick` helper 取 status 計數,`recentPosts` limit 5,`postsWithoutCover` 用 `isNull(posts.coverImage)`)。
|
||||||
|
- [ ] **Step 3: 驗證** — `npm run build`,Expected: success。
|
||||||
|
- [ ] **Step 4: Commit** — `git add src/data/content.ts && git commit -m "feat(admin): add getDashboardData query"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Dashboard `/admin` + 文章頁 `/admin/posts` + redirects
|
||||||
|
|
||||||
|
**Files:** Create `src/pages/admin/posts.astro`;Modify `src/pages/admin/index.astro`、`src/pages/admin/post/[id].astro`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 建立 `posts.astro`** — 將原 `index.astro` 查詢+文章表搬去,`<AdminLayout title="文章">`、`<h1>文章</h1>`,heading 加「+ 寫新文」;保留 `export const prerender = false`。
|
||||||
|
- [ ] **Step 2: 改寫 `index.astro`** 成 dashboard(frontmatter 用 `getDashboardData` + `getSettings`,砌 `todos`;markup 見 spec §3)。
|
||||||
|
- [ ] **Step 3: 加 dashboard CSS**(`.stats`/`.stat`/`.dash`/`.todo`/`.status-row`/`.status-dot`,RWD breakpoints 1100/900/560)。
|
||||||
|
- [ ] **Step 4: 改 redirect** — `post/[id].astro` line 29/39/96 `"/admin"` → `"/admin/posts"`;line 174 取消 link `href="/admin"` → `"/admin/posts"`。
|
||||||
|
- [ ] **Step 5: 驗證** — `npm run build`,Expected: success。
|
||||||
|
- [ ] **Step 6: 目測** — dev:登入後 `/admin` 見總覽;`/admin/posts` 新增/儲存/刪文後返 `/admin/posts`;統計同待辦數字正確。
|
||||||
|
- [ ] **Step 7: Commit** — `git add src/pages/admin && git commit -m "feat(admin): dashboard overview and move posts list to /admin/posts"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: 其餘 admin 頁統一視覺
|
||||||
|
|
||||||
|
**Files:** Modify `src/pages/admin/cases.astro`、`content/[kind].astro`、`settings.astro`、`ai.astro`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 移除 inline 硬編色**,改用 tokens/既有 class;成功提示改 `.notice`。
|
||||||
|
- [ ] **Step 2: 統一 section 標題/spacing**。
|
||||||
|
- [ ] **Step 3: 驗證** — `npm run build`,Expected: success。
|
||||||
|
- [ ] **Step 4: 目測** — dev 逐頁睇 cases/content 四 kind/settings/ai,確認色系一致、表單可用、圖片上傳正常。
|
||||||
|
- [ ] **Step 5: Commit** — `git add src/pages/admin && git commit -m "style(admin): align pages with dashboard tokens"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: `login.astro` 重新上色
|
||||||
|
|
||||||
|
**Files:** Modify `src/pages/admin/login.astro`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** 將 `<style is:global>` 改成同 tokens 一致(米白底、白卡髮絲邊、琥珀 focus、墨黑掣、serif 標題、`.err` 用 danger 色)。
|
||||||
|
- [ ] **Step 2: 驗證** — `npm run build`,Expected: success。
|
||||||
|
- [ ] **Step 3: 目測** — dev 登出睇 `/admin/login`,登入流程正常。
|
||||||
|
- [ ] **Step 4: Commit** — `git add src/pages/admin/login.astro && git commit -m "style(admin): restyle login page"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: 更新 DOX 文件
|
||||||
|
|
||||||
|
**Files:** Modify `src/AGENTS.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** 更新 `src/AGENTS.md` Admin 段落(路由、dashboard、sidebar、色系、`getDashboardData`)。
|
||||||
|
- [ ] **Step 2: 驗證** — `npm run build`,Expected: success。
|
||||||
|
- [ ] **Step 3: Commit** — `git add src/AGENTS.md && git commit -m "docs: document admin dashboard redesign"`
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 盈豐太陽能 — 後台 Dashboard 化重新設計 (Redesign Spec)
|
||||||
|
|
||||||
|
- **日期**:2026-09-13
|
||||||
|
- **狀態**:已與客戶確認方向
|
||||||
|
- **背景**:現時 `/admin` 後台用綠色(`#0b8a5e`)/淺綠底(`#f2f8f6`)純 CSS,同前台 editorial(暖米白+琥珀金+墨黑)唔一致;一入 `/admin` 就係文章表,冇總覽。本規格將後台改造成墨黑 sidebar + 米白內容嘅真 dashboard。
|
||||||
|
- **範圍**:只改後台(`src/layouts/AdminLayout.astro`、`src/pages/admin/**`、`src/data/content.ts`、`src/AGENTS.md`)。**唔郁**前台、D1 schema、migration、認證/middleware 邏輯。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 設計方向
|
||||||
|
|
||||||
|
**墨黑 sidebar × 暖米白內容 × 琥珀金 accent** 嘅工具感 dashboard,同前台品牌一致但偏實用。
|
||||||
|
|
||||||
|
- 沿用前台語意色:墨黑 `#1A1917`、暖米白 `#FAF8F4`、髮絲線 `#E7E2D9`、暖灰 `#6E6862`、琥珀 `#D97706`/`#B45309`。
|
||||||
|
- 狀態色只用嚟表達語意,唔做裝飾:成功 `#0F6E56`、警示/草稿琥珀、危險 `#A32D2D`。
|
||||||
|
- 形狀:直角/微圓角(4–8px)、髮絲邊框、無陰影、無漸層。
|
||||||
|
- 純 Astro SSR + CSS(CSS variables),**唔引入 React / Chakra**,維持現有 admin 合約。
|
||||||
|
|
||||||
|
## 2. 資訊架構
|
||||||
|
|
||||||
|
- **`/admin`(總覽)**:dashboard,見 §3。
|
||||||
|
- **`/admin/posts`(文章)**:由原 `index.astro` 文章列表搬過來。
|
||||||
|
- 其餘路由不變:`/admin/content/[kind]`、`/admin/cases`、`/admin/ai`、`/admin/settings`、`/admin/post/[id]`、`/admin/login`、`/admin/logout`。
|
||||||
|
|
||||||
|
### Shell(`AdminLayout.astro`)
|
||||||
|
|
||||||
|
- 桌面(≥900px):左側固定 sidebar 248px、墨黑底;內容區暖米白、`max-width 1080px`。
|
||||||
|
- Sidebar:品牌 serif「盈豐太陽能」+細字「後台」;導航分組:
|
||||||
|
- **總覽** → `/admin`
|
||||||
|
- **內容管理** → 文章 `/admin/posts`、服務、特色、流程、常見問題、完成案例
|
||||||
|
- **系統** → AI 生成、網站設定
|
||||||
|
- 底部:睇網站 ↗(新視窗)、登出
|
||||||
|
- Active 項:琥珀文字+左邊 3px 琥珀條+微亮底。
|
||||||
|
- 手機(<900px):sidebar 收埋,頂欄顯示品牌+純 CSS checkbox 漢堡開合。
|
||||||
|
- 所有色值集中做 CSS variables(`--ink`/`--ink-2`/`--bg`/`--surface`/`--fg`/`--fg-muted`/`--border`/`--brand`/`--brand-strong`/`--brand-soft`/`--ok`/`--warn`/`--danger`/`--radius-sm`/`--radius`/`--sidebar-w`)。
|
||||||
|
- 保留原有 R2 上傳 `<script>` 同 `ImageField` 依賴嘅 `.image-url` / `.image-preview` / `.image-status` class。
|
||||||
|
|
||||||
|
## 3. `/admin` 總覽內容
|
||||||
|
|
||||||
|
- 頁首:h1「總覽」+更新時間。
|
||||||
|
- **統計卡 ×4**(大 serif 琥珀數字、髮絲邊框、無陰影):
|
||||||
|
1. 文章 — 總數;細字:已發布 N · 草稿 N
|
||||||
|
2. 完成案例 — 總數;細字:已發布 N · 草稿 N
|
||||||
|
3. 首頁內容 — 總數;細字:服務 · 特色 · 流程 · FAQ
|
||||||
|
4. 關鍵字佇列 — 待生成數;細字:已生成 N · 已略過 N
|
||||||
|
- **待辦提示**(主欄):條件式列出,每項可點去對應頁;全清顯示「一切妥當」:
|
||||||
|
- N 篇文章仍是草稿 → `/admin/posts`
|
||||||
|
- N 篇文章未有封面圖 → `/admin/posts`
|
||||||
|
- N 個關鍵字待生成 → `/admin/ai`
|
||||||
|
- N 個案例仍是草稿 → `/admin/cases`
|
||||||
|
- N 個首頁內容項目仍是草稿 → `/admin/content/service`
|
||||||
|
- **系統狀態**(側欄):AI 生成 啟用/停用、`AI_API_KEY`/`TAVILY_API_KEY` 已設定/未設定、目前 model 與 Base URL(唯讀顯示)。
|
||||||
|
- **最近更新文章**(全寬):最新 5 篇(標題+slug、狀態 badge、更新日期、編輯 link),section 右上「+ 寫新文」。
|
||||||
|
|
||||||
|
## 4. 資料層
|
||||||
|
|
||||||
|
- `src/data/content.ts` 加 `getDashboardData(db): Promise<DashboardData>`,集中提供 dashboard 統計(沿用「查詢集中喺 content.ts」慣例)。
|
||||||
|
- `DashboardData`:`posts{published,draft}`、`cases{published,draft}`、`content[{kind,published,draft}]`、`keywords{pending,generated,skipped}`、`recentPosts`、`postsWithoutCover`。
|
||||||
|
- `getDashboardData` 只讀 DB;AI secrets(`env.AI_API_KEY`/`env.TAVILY_API_KEY`)同 `ai_enabled` 喺頁面層處理。
|
||||||
|
- **冇 schema 改動、冇 migration。**
|
||||||
|
|
||||||
|
## 5. 各頁統一
|
||||||
|
|
||||||
|
- `cases`/`content/[kind]`/`settings`/`ai` 只改外觀(卡片、badge、掣、表格、提示),表單 action 同流程不變。
|
||||||
|
- badge:已發布=淡綠、草稿=琥珀、已略過/muted=灰;成功提示由 `.badge.published` 改為 `.notice`。
|
||||||
|
- `post/[id].astro` 存/刪/取消後 redirect 去 `/admin/posts`。
|
||||||
|
- `login.astro` 用同一 tokens 重新上色。
|
||||||
|
|
||||||
|
## 6. 驗證
|
||||||
|
|
||||||
|
- `npm run build`(唯一自動驗證;專案冇 test/lint/typecheck)。
|
||||||
|
- `npm run dev` 目測:所有 admin 頁、RWD(≥900 / <900)、登入/登出、圖片上傳、表單存檔 redirect、`/blog/[slug]` 404 不受影響。
|
||||||
|
|
||||||
|
## 7. 文件同步
|
||||||
|
|
||||||
|
- `src/AGENTS.md` Admin 段落:`/admin` 由文章列表改為總覽、文章列表喺 `/admin/posts`、`AdminLayout` 改為墨黑 sidebar shell、色系改用 CSS variables、`getDashboardData` 位置。
|
||||||
Reference in New Issue
Block a user