Files
stash/stash-web/src/manage.ts
T
CNWei 27e411fd2c refactor(db): 删除旧分区展示块与兼容字段
- v23 迁移删除 section_block 表,以及 node 的 view_mode / empty_text / show_in_sidebar / allow_groups
- 重建 node 表并保留 44 条内容、15 个分组、5 张卡片,外键检查通过
- 种子库直接生成 card / card_group,不再依赖 section_block
- 删除旧 /api/catalog、/api/categories、/api/groups/{id} 与 categories 模块
- 分组接口不再接收/返回 format,前端 GroupNode 同步移除
- README/HANDOFF 更新到 v23;管理页提示改为『格式由每条内容决定』
2026-11-02 20:24:08 +08:00

202 lines
5.7 KiB
TypeScript
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.
/**
* 原子模型的后台接口:分组树 + 卡片。
*
* 分组是唯一容器,卡片只是「勾选分组」的展示组合;两者都通过业务键关联。
*/
export type GroupFormat = 'apps' | 'posters' | 'articles'
export type GroupEntry = {
id: string
format?: GroupFormat
name?: string
title?: string
href?: string
description?: string
desc?: string
}
export type GroupNode = {
key: string
parentKey?: string | null
title: string
icon: string
barColor: string
previewLimit?: number | null
sort: number
/** 是否启用;禁用后前台不展示 */
enabled: boolean
entries: GroupEntry[]
children: GroupNode[]
}
export type CardGroupRef = {
groupKey: string
previewLimit?: number | null
}
export type CardItem = {
id: number
key: string
title: string
sort: number
groups: CardGroupRef[]
}
export type ManageData = {
groups: GroupNode[]
cards: CardItem[]
}
export type SimpleResult = { ok: true } | { ok: false; error: string }
async function errorMessage(response: Response, fallback: string): Promise<string> {
const payload = (await response.json().catch(() => null)) as { error?: string } | null
return payload?.error ?? `${fallback}(${response.status})`
}
export async function fetchManage(): Promise<ManageData> {
try {
const response = await fetch('/api/catalog2', { credentials: 'same-origin' })
if (!response.ok) return { groups: [], cards: [] }
return (await response.json()) as ManageData
} catch {
return { groups: [], cards: [] }
}
}
export async function createNode(input: {
key: string
title: string
parentKey?: string
format?: GroupFormat
icon?: string
sort?: number
enabled?: boolean
}): Promise<SimpleResult> {
try {
const response = await fetch('/api/nodes', {
method: 'POST',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(input),
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '创建失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function updateNode(
key: string,
patch: {
title?: string
parentKey?: string
sort?: number
format?: GroupFormat
icon?: string
enabled?: boolean
},
): Promise<SimpleResult> {
try {
const response = await fetch(`/api/nodes/${encodeURIComponent(key)}`, {
method: 'PATCH',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(patch),
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '保存失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function deleteNode(key: string): Promise<SimpleResult> {
try {
const response = await fetch(`/api/nodes/${encodeURIComponent(key)}`, {
method: 'DELETE',
credentials: 'same-origin',
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '删除失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function createCard(title: string): Promise<{ ok: true; id: number } | { ok: false; error: string }> {
try {
const response = await fetch('/api/cards', {
method: 'POST',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ title }),
})
if (!response.ok) return { ok: false, error: await errorMessage(response, '创建失败') }
const payload = (await response.json()) as { id: number }
return { ok: true, id: payload.id }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function updateCard(
id: number,
patch: { title?: string; sort?: number },
): Promise<SimpleResult> {
try {
const response = await fetch(`/api/cards/${id}`, {
method: 'PATCH',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(patch),
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '保存失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function deleteCard(id: number): Promise<SimpleResult> {
try {
const response = await fetch(`/api/cards/${id}`, {
method: 'DELETE',
credentials: 'same-origin',
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '删除失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
export async function setCardGroups(
id: number,
groups: CardGroupRef[],
): Promise<SimpleResult> {
try {
const response = await fetch(`/api/cards/${id}/groups`, {
method: 'PUT',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(groups),
})
return response.ok ? { ok: true } : { ok: false, error: await errorMessage(response, '保存失败') }
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : '网络错误' }
}
}
/** 把分组树拍平成下拉选项 */
export function flattenGroups(
groups: GroupNode[],
depth = 0,
): { key: string; title: string; depth: number; enabled: boolean }[] {
return groups.flatMap((group) => [
{
key: group.key,
title: group.title,
depth,
enabled: group.enabled !== false,
},
...flattenGroups(group.children, depth + 1),
])
}