feat(admin): 分区增删、资料设置、操作条与交互收尾

- POST/DELETE /api/categories(需登录,级联,local 保护)
- profile 设置(头像/名称/角色/简介/位置)应用到资料卡
- 详情操作条:浏览 +1、点赞切换、分享复制、跳转外链
- 编辑按钮垂直居中且留 12px;设置页按钮上间距、保存 Toast、Tabs 去滚动条
- 登录密码可见切换
- 测试 48 passed
This commit is contained in:
stash
2026-10-29 20:15:56 +08:00
parent 3c6c20e534
commit 427338b329
15 changed files with 851 additions and 54 deletions
+57 -6
View File
@@ -19,6 +19,16 @@ export type SectionSettings = {
limits: Record<string, number>
}
export type ProfileSettings = {
name: string
role: string
description: string
/** 完整文案,如「IP属地:局域网」 */
location: string
/** 头像地址;空表示用图标占位 */
avatar: string
}
export type SiteSettings = {
header: {
enabled: boolean
@@ -31,6 +41,7 @@ export type SiteSettings = {
order: string[]
}
sections: SectionSettings
profile: ProfileSettings
}
export type SettingsDocument = {
@@ -62,6 +73,13 @@ export function defaultSettings(): SiteSettings {
},
sidebar: { hidden: [], order: [] },
sections: { hidden: [], order: [], titles: {}, limits: {} },
profile: {
name: '本地管理员',
role: '管理员',
description: '本地静态站点托管与预览,所有项目都运行在这台机器上。',
location: 'IP属地:局域网',
avatar: '',
},
}
}
@@ -89,8 +107,7 @@ export async function fetchSettings(): Promise<SettingsDocument> {
export async function saveSettings(
version: number,
settings: SiteSettings,
): Promise<SettingsResult> {
try {
): Promise<SettingsResult> { try {
const response = await fetch('/api/settings', {
method: 'PATCH',
credentials: 'same-origin',
@@ -119,8 +136,7 @@ export async function saveSettings(
/**
* 按设置重排侧栏:先按 `order`,未列出的保持原顺序,`hidden` 过滤掉。
*/
export function applySidebarSettings<T extends { id: string }>(
*/export function applySidebarSettings<T extends { id: string }>(
categories: T[],
sidebar: SiteSettings['sidebar'],
): T[] {
@@ -155,8 +171,7 @@ export function sectionLimit(
return typeof override === 'number' && override > 0 ? override : fallback
}
function orderAndFilter<T extends { id: string }>(
categories: T[],
function orderAndFilter<T extends { id: string }>( categories: T[],
config: { hidden: string[]; order: string[] },
): T[] {
const hidden = new Set(config.hidden)
@@ -172,3 +187,39 @@ function orderAndFilter<T extends { id: string }>(
.sort((a, b) => (a.rank === b.rank ? a.index - b.index : a.rank - b.rank))
.map((entry) => entry.category)
}
// ---------- 分区(首页卡片 / 侧栏项)的新增与删除 ----------
export type NewCategory = { id: string; title: string; kind: 'apps' | 'posters' | 'articles' }
export type SimpleResult = { ok: true } | { ok: false; error: string }
export async function createCategory(input: NewCategory): Promise<SimpleResult> {
try {
const response = await fetch('/api/categories', {
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 deleteCategory(id: string): Promise<SimpleResult> {
try {
const response = await fetch(`/api/categories/${encodeURIComponent(id)}`, {
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 : '网络错误' }
}
}