Files
yingfungsolar/docs/superpowers/plans/2026-09-13-admin-dashboard.md
T

190 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 後台 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` 做單一 shellsidebar + CSS variables),保留原有 R2 上傳 script。新增 `getDashboardData(db)``src/data/content.ts` 提供統計,唔改 schema。`index.astro` 改寫成 dashboard,新增 `posts.astro` 承接原文章列表。
**Tech Stack:** Astro 7 SSR、Drizzle/D1、純 CSSCSS variables)。驗證=`npm run build`(專案冇 testlinttypecheck)。
**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: 換 formtablecard 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: success0 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`** 成 dashboardfrontmatter 用 `getDashboardData` + `getSettings`,砌 `todos`markup 見 spec §3)。
- [ ] **Step 3: 加 dashboard CSS**`.stats``.stat``.dash``.todo``.status-row``.status-dot`RWD breakpoints 1100900560)。
- [ ] **Step 4: 改 redirect**`post/[id].astro` line 293996 `"/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 逐頁睇 casescontent 四 kindsettings/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"`