/** * 原子模型的后台接口:分组树 + 卡片。 * * 分组是唯一容器,卡片只是「勾选分组」的展示组合;两者都通过业务键关联。 */ 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 { const payload = (await response.json().catch(() => null)) as { error?: string } | null return payload?.error ?? `${fallback}(${response.status})` } export async function fetchManage(): Promise { 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 { 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 { 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 { 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 { 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 { 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 { 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), ]) }