first commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
---
|
||||
import { and, asc, desc, eq, gt, lt, max } from "drizzle-orm";
|
||||
import AdminLayout from "../../layouts/AdminLayout.astro";
|
||||
import { cases } from "../../db/schema";
|
||||
import { getDb } from "../../lib/db";
|
||||
import { getEnv } from "../../lib/env";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const db = getDb(getEnv().DB);
|
||||
|
||||
function str(form: FormData, key: string): string {
|
||||
return String(form.get(key) ?? "").trim();
|
||||
}
|
||||
|
||||
if (Astro.request.method === "POST") {
|
||||
const form = await Astro.request.formData();
|
||||
const action = str(form, "action");
|
||||
|
||||
if (action === "add") {
|
||||
const title = str(form, "title");
|
||||
if (title) {
|
||||
const [row] = await db.select({ m: max(cases.sortOrder) }).from(cases);
|
||||
await db.insert(cases).values({
|
||||
id: crypto.randomUUID(),
|
||||
title,
|
||||
location: str(form, "location") || null,
|
||||
completedAt: str(form, "completedAt") || null,
|
||||
description: str(form, "description") || null,
|
||||
imageUrl: str(form, "imageUrl") || null,
|
||||
sortOrder: (row?.m ?? 0) + 1,
|
||||
status: str(form, "status") === "draft" ? "draft" : "published",
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
} else if (action === "save") {
|
||||
const id = str(form, "id");
|
||||
await db
|
||||
.update(cases)
|
||||
.set({
|
||||
title: str(form, "title"),
|
||||
location: str(form, "location") || null,
|
||||
completedAt: str(form, "completedAt") || null,
|
||||
description: str(form, "description") || null,
|
||||
imageUrl: str(form, "imageUrl") || null,
|
||||
status: str(form, "status") === "draft" ? "draft" : "published",
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(cases.id, id));
|
||||
} else if (action === "delete") {
|
||||
await db.delete(cases).where(eq(cases.id, str(form, "id")));
|
||||
} else if (action === "up" || action === "down") {
|
||||
const id = str(form, "id");
|
||||
const [current] = await db.select().from(cases).where(eq(cases.id, id)).limit(1);
|
||||
if (current) {
|
||||
const [adj] = await db
|
||||
.select()
|
||||
.from(cases)
|
||||
.where(
|
||||
action === "up" ? lt(cases.sortOrder, current.sortOrder) : gt(cases.sortOrder, current.sortOrder),
|
||||
)
|
||||
.orderBy(action === "up" ? desc(cases.sortOrder) : asc(cases.sortOrder))
|
||||
.limit(1);
|
||||
if (adj) {
|
||||
await db.update(cases).set({ sortOrder: adj.sortOrder }).where(eq(cases.id, current.id));
|
||||
await db.update(cases).set({ sortOrder: current.sortOrder }).where(eq(cases.id, adj.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Astro.redirect("/admin/cases");
|
||||
}
|
||||
|
||||
const list = await db.select().from(cases).orderBy(asc(cases.sortOrder));
|
||||
---
|
||||
|
||||
<AdminLayout title="完成案例">
|
||||
<h1>完成案例</h1>
|
||||
<p class="sub">前台「完成案例」區塊會按下面次序顯示(只顯示已發布)。</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>新增案例</h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="add" />
|
||||
<div class="grid2">
|
||||
<div>
|
||||
<label for="a-title">標題</label>
|
||||
<input id="a-title" name="title" type="text" required placeholder="例:大埔上碗窯 — 雙玻光伏板" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="a-loc">地點</label>
|
||||
<input id="a-loc" name="location" type="text" placeholder="例:大埔半山" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="a-date">施工日期</label>
|
||||
<input id="a-date" name="completedAt" type="text" placeholder="例:2026年2月" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="a-img">圖片 URL</label>
|
||||
<input id="a-img" name="imageUrl" type="text" placeholder="https://..." />
|
||||
</div>
|
||||
<div style="grid-column:1/-1">
|
||||
<label for="a-desc">描述</label>
|
||||
<textarea id="a-desc" name="description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions"><button type="submit">+ 新增案例</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{
|
||||
list.length === 0 ? (
|
||||
<p class="muted">暫時未有案例。</p>
|
||||
) : (
|
||||
list.map((item, i) => (
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
<input type="hidden" name="id" value={item.id} />
|
||||
<div class="grid2">
|
||||
<div>
|
||||
<label>標題</label>
|
||||
<input name="title" type="text" value={item.title} required />
|
||||
</div>
|
||||
<div>
|
||||
<label>地點</label>
|
||||
<input name="location" type="text" value={item.location ?? ""} />
|
||||
</div>
|
||||
<div>
|
||||
<label>施工日期</label>
|
||||
<input name="completedAt" type="text" value={item.completedAt ?? ""} />
|
||||
</div>
|
||||
<div>
|
||||
<label>圖片 URL</label>
|
||||
<input name="imageUrl" type="text" value={item.imageUrl ?? ""} />
|
||||
</div>
|
||||
<div style="grid-column:1/-1">
|
||||
<label>描述</label>
|
||||
<textarea name="description">{item.description ?? ""}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label>狀態</label>
|
||||
<select name="status">
|
||||
<option value="published" selected={item.status === "published"}>已發布</option>
|
||||
<option value="draft" selected={item.status === "draft"}>草稿</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{item.imageUrl && (
|
||||
<img src={item.imageUrl} alt={item.title} style="margin-top:14px;max-width:220px;border-radius:10px;" />
|
||||
)}
|
||||
<div class="actions">
|
||||
<button type="submit" name="action" value="save">儲存</button>
|
||||
<button type="submit" name="action" value="up" class="secondary mini" disabled={i === 0}>↑ 上移</button>
|
||||
<button type="submit" name="action" value="down" class="secondary mini" disabled={i === list.length - 1}>
|
||||
↓ 下移
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
name="action"
|
||||
value="delete"
|
||||
class="danger mini"
|
||||
onclick="return confirm('確定刪除?')"
|
||||
>
|
||||
刪除
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
}
|
||||
</AdminLayout>
|
||||
Reference in New Issue
Block a user