管理後台

本頁內容

EmDash 管理後台是嵌入在 Astro 網站中的 React 單頁應用,為編輯與管理員提供完整的內容管理介面。

架構概覽

┌────────────────────────────────────────────────────────────────┐
│                    Astro 外殼                                  │
│  /_emdash/admin/[...path].astro                              │
│                                                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    React SPA                             │  │
│  │                                                          │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │  │
│  │  │  TanStack   │  │  TanStack   │  │     Kumo        │   │  │
│  │  │   Router    │  │   Query     │  │   元件          │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────────┘   │  │
│  │                                                          │  │
│  │  ┌────────────────────────────────────────────────────┐  │  │
│  │  │           REST API 客戶端                         │  │  │
│  │  │           /_emdash/api/*                           │  │  │
│  │  └────────────────────────────────────────────────────┘  │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────┘

管理後台是 React「大型島嶼」。Astro 負責外殼與驗證;管理後台內的導航與渲染全部在客戶端完成。

技術棧

層級技術作用
路由TanStack Router類型安全的客戶端路由
資料TanStack Query服務端狀態、快取、變更
UIKumo可存取元件(Base UI + Tailwind)
表格TanStack Table排序、篩選、分頁
表單React Hook Form + Zod與伺服器 schema 一致的驗證
圖標Phosphor統一圖標
編輯器TipTap富文字編輯(Portable Text)

路由結構

管理後台掛載在 /_emdash/admin/,使用客戶端路由。

路徑介面
/儀表板
/content/:collection內容列表
/content/:collection/:id內容編輯器
/content/:collection/new新建條目
/media媒體庫
/content-typesSchema 構建器(僅管理員)
/menus導航選單
/widgets小工具區域
/taxonomies分類 / 標籤管理
/settings網站設定
/plugins/:pluginId/*外掛頁面

清單驅動的 UI

管理後台不把集合或外掛寫死在程式碼裡,而是從伺服器拉取清單(manifest)。

GET /_emdash/api/manifest

回應:

{
	"collections": [
		{
			"slug": "posts",
			"label": "Blog Posts",
			"labelSingular": "Post",
			"icon": "file-text",
			"supports": ["drafts", "revisions", "preview"],
			"fields": [
				{ "slug": "title", "type": "string", "required": true },
				{ "slug": "content", "type": "portableText" }
			]
		}
	],
	"plugins": [
		{
			"id": "audit-log",
			"label": "Audit Log",
			"adminPages": [{ "path": "history", "label": "Audit History" }],
			"widgets": [{ "id": "recent-activity", "title": "Recent Activity" }]
		}
	],
	"taxonomies": [{ "name": "category", "label": "Categories", "hierarchical": true }],
	"version": "abc123"
}

管理後台僅憑此清單構建導航、表單與編輯器。優點包括:

  • Schema 變更立即生效 — 無需重建管理後台
  • 外掛 UI 自動接入 — 頁面與小元件來自清單
  • 邊界類型安全 — Zod schema 保留在伺服器端

資料流

  1. 加載管理後台 SPA — 初始化 TanStack Router
  2. 取得清單 — TanStack Query 快取集合/外掛元資料
  3. 構建導航 — 側欄由清單生成
  4. 用戶導航 — 客戶端路由,無整頁刷新
  5. 拉取資料 — TanStack Query 請求 REST API
  6. 渲染表單 — 由清單中的欄位描述生成欄位編輯器
  7. 提交變更 — 通過 TanStack Query 變更與樂觀更新
  8. 服務端驗證 — 服務端 Zod,錯誤以 JSON 返回

REST API 端點

管理後台僅通過 REST API 通信。

內容 API

方法端點用途
GET/api/content/:collection列出條目
POST/api/content/:collection建立條目
GET/api/content/:collection/:id取得條目
PUT/api/content/:collection/:id更新條目
DELETE/api/content/:collection/:id軟刪除
GET/api/content/:collection/:id/revisions修訂列表
POST/api/content/:collection/:id/preview-url生成預覽 URL

Schema API

方法端點用途
GET/api/schema導出完整 schema
GET/api/schema/collections列出集合
POST/api/schema/collections建立集合
PUT/api/schema/collections/:slug更新集合
DELETE/api/schema/collections/:slug刪除集合
POST/api/schema/collections/:slug/fields添加欄位
PUT/api/schema/collections/:slug/fields/:field更新欄位
DELETE/api/schema/collections/:slug/fields/:field刪除欄位

媒體 API

方法端點用途
GET/api/media列出媒體
POST/api/media/upload-url取得簽署上傳 URL
POST/api/media/:id/confirm確認上傳完成
DELETE/api/media/:id刪除媒體
GET/api/media/file/:key提供媒體文件

其他 API

端點用途
/api/settings網站設定(GET/POST)
/api/menus/*導航選單
/api/widget-areas/*小工具管理
/api/taxonomies/*分類術語
/api/admin/plugins/*外掛狀態

分頁

所有列表端點均使用基於遊標的分頁。

{
  "items": [...],
  "nextCursor": "eyJpZCI6IjAxSjEyMzQ1NiJ9"
}

取得下一頁:

GET /api/content/posts?cursor=eyJpZCI6IjAxSjEyMzQ1NiJ9

外掛管理後台 UI

外掛可通過頁面與儀表板小工具擴展管理後台。整合會生成帶靜態 import 的虛擬模塊。

// virtual:emdash/plugin-admins(生成)
import * as pluginAdmin0 from "@emdash-cms/plugin-seo/admin";
import * as pluginAdmin1 from "@emdash-cms/plugin-analytics/admin";

export const pluginAdmins = {
	seo: pluginAdmin0,
	analytics: pluginAdmin1,
};

外掛頁面

外掛頁面掛載在 /_emdash/admin/plugins/:pluginId/* 下。

// @emdash-cms/plugin-seo/src/admin.tsx
export const pages = [
	{
		path: "settings",
		component: SEOSettingsPage,
		label: "SEO Settings",
	},
];

渲染 URL:/_emdash/admin/plugins/seo/settings

儀表板小工具

外掛可向儀表板添加小工具。

export const widgets = [
	{
		id: "seo-overview",
		component: SEOWidget,
		title: "SEO Overview",
		size: "half", // "full" | "half" | "third"
	},
];

驗證

管理後台外殼路由由 Astro 中間件強制驗證。

// 簡化的中間件示例
export async function onRequest({ request, locals }, next) {
	const session = await getSession(request);

	if (request.url.includes("/_emdash/admin")) {
		if (!session?.user) {
			return redirect("/_emdash/admin/login");
		}
		locals.user = session.user;
	}

	return next();
}

管理後台 SPA 本身不處理登錄;設定會話 Cookie 的是 Astro 頁面。

基於角色的存取

不同角色可見的管理後台範圍不同。

角色可見區塊
Editor儀表板、分配的集合、媒體
Admin另含內容類型、全部集合、設定
Developer另含 CLI 存取、生成的類型

清單端點會按請求用戶角色過濾集合與功能。

內容編輯器

內容編輯器根據欄位定義動態生成表單。

// 簡化的編輯器渲染示例
function ContentEditor({ collection, fields }) {
	return (
		<form>
			{fields.map((field) => (
				<FieldWidget
					key={field.slug}
					type={field.type}
					label={field.label}
					required={field.required}
					options={field.options}
				/>
			))}
		</form>
	);
}

每種欄位類型有對應的小元件。

欄位類型小元件
string文字輸入
text文字域
number數字輸入
boolean開關
datetime日期時間選擇器
select下拉框
multiSelect多選
portableTextTipTap 編輯器
image媒體選擇器
reference條目選擇器

富文字編輯器

Portable Text 欄位使用 TipTap(ProseMirror)編輯。

用戶輸入 → TipTap(ProseMirror JSON)→ 保存 → Portable Text(DB)
加載 → Portable Text(DB)→ TipTap(ProseMirror JSON)→ 展示

在加載/保存邊界通過 portableTextToProsemirror()prosemirrorToPortableText() 轉換。

支持的塊:

  • 段落、標題(H1–H6)
  • 項目符號與編號列表
  • 引用、程式碼塊
  • 圖片(來自媒體庫)
  • 連結

來自外掛或導入的未知塊以只讀佔位保留。

媒體庫

媒體庫提供:

  • 網格與列表視圖
  • 按類型、日期搜尋與篩選
  • 拖放上傳
  • 帶元資料的圖片預覽
  • 批量選擇與刪除

上傳使用簽署 URL,由客戶端直傳存儲。

  1. 請求上傳 URLPOST /api/media/upload-url
  2. 直傳 — 客戶端向簽署 URL(R2/S3)PUT 文件
  3. 確認上傳POST /api/media/:id/confirm
  4. 服務端提取元資料 — 尺寸、MIME 類型等

該方式可繞過 Workers 請求體大小限制,並獲得真實上傳進度。

下一步