- POST /api/items + DELETE /api/items/{key}:新增应用/海报/图文,按分区/分组挂载;PATCH 支持 nodeKey 移动
- 分组 CRUD:POST /api/categories/{id}/groups、PATCH/DELETE /api/groups/{id}(删除级联组内条目)
- PATCH /api/categories/{id} 增加 blockNodeKey,首页卡片可选择预览分组
- /admin 新增「内容」Tab(新增/删除条目),「分区」Tab 内联分组管理与卡片预览
- 测试 59 passed(条目创建/移动/删除、分组增删改)
120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
/**
|
||
* 条目编辑的前端接口。
|
||
*
|
||
* 读取是公开的(编辑表单要能预览),写入必须登录,并用 `updatedAt` 做乐观锁。
|
||
*/
|
||
export type EntryRecord = {
|
||
key: string
|
||
name: string
|
||
description: string
|
||
icon: string
|
||
href: string
|
||
cover: string
|
||
banner: string
|
||
badge: string
|
||
comment: string
|
||
summary: string
|
||
body: string
|
||
tags: string[]
|
||
updated_at: number
|
||
}
|
||
|
||
export type EntryInput = {
|
||
name: string
|
||
description: string
|
||
icon: string
|
||
tags: string[]
|
||
badge: string
|
||
comment: string
|
||
summary: string
|
||
body: string
|
||
cover: string
|
||
banner: string
|
||
href: string
|
||
}
|
||
|
||
export type ItemResult =
|
||
| { ok: true; entry: EntryRecord }
|
||
| { ok: false; status: number; error: string }
|
||
|
||
export async function fetchItem(key: string): Promise<ItemResult> {
|
||
try {
|
||
const response = await fetch(`/api/items/${encodeURIComponent(key)}`, {
|
||
credentials: 'same-origin',
|
||
})
|
||
if (response.ok) {
|
||
return { ok: true, entry: (await response.json()) as EntryRecord }
|
||
}
|
||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||
return { ok: false, status: response.status, error: payload?.error ?? '读取失败' }
|
||
} catch (error) {
|
||
return { ok: false, status: 0, error: error instanceof Error ? error.message : '网络错误' }
|
||
}
|
||
}
|
||
|
||
export async function saveItem(
|
||
key: string,
|
||
updatedAt: number,
|
||
item: EntryInput,
|
||
): Promise<ItemResult> {
|
||
try {
|
||
const response = await fetch(`/api/items/${encodeURIComponent(key)}`, {
|
||
method: 'PATCH',
|
||
credentials: 'same-origin',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify({ updatedAt, item }),
|
||
})
|
||
|
||
if (response.ok) {
|
||
return { ok: true, entry: (await response.json()) as EntryRecord }
|
||
}
|
||
|
||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||
return {
|
||
ok: false,
|
||
status: response.status,
|
||
error: payload?.error ?? `保存失败(${response.status})`,
|
||
}
|
||
} catch (error) {
|
||
return { ok: false, status: 0, error: error instanceof Error ? error.message : '网络错误' }
|
||
}
|
||
}
|
||
|
||
/** 新建条目:指定分区 / 分组与字段 */
|
||
export async function createItem(input: {
|
||
key: string
|
||
nodeKey: string
|
||
item: EntryInput
|
||
}): Promise<SimpleResult> {
|
||
try {
|
||
const response = await fetch('/api/items', {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'content-type': 'application/json' },
|
||
body: JSON.stringify(input),
|
||
})
|
||
if (response.ok) return { ok: true }
|
||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||
return { ok: false, error: payload?.error ?? `创建失败(${response.status})` }
|
||
} catch (error) {
|
||
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
|
||
}
|
||
}
|
||
|
||
/** 删除条目 */
|
||
export async function deleteItem(key: string): Promise<SimpleResult> {
|
||
try {
|
||
const response = await fetch(`/api/items/${encodeURIComponent(key)}`, {
|
||
method: 'DELETE',
|
||
credentials: 'same-origin',
|
||
})
|
||
if (response.ok) return { ok: true }
|
||
const payload = (await response.json().catch(() => null)) as { error?: string } | null
|
||
return { ok: false, error: payload?.error ?? `删除失败(${response.status})` }
|
||
} catch (error) {
|
||
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
|
||
}
|
||
}
|
||
|
||
export type SimpleResult = { ok: true } | { ok: false; error: string }
|