PersonalWebApplication/Client/src/api/folders.ts
cc 8d89cee451 @
feat(photo-wall): 修复4个问题 + 特殊文件夹(共享公共相册)

**照片墙修复:**
- 右键菜单 z-index 40→9999,不再被元素遮挡
- 图片放大后强制重建 DOM(img key 绑定 w/h)
- 右键菜单添加滑条调节旋转步长(1°-90°)和缩放步长(照片 px / 其他 scale)
- Delete 键可删除右键菜单选中的元素(便签也能删)

**特殊文件夹功能(按设计文档实现):**
- 后端:FolderRegistry 解析 special_folders.txt 注册表 + FolderStore 按文件夹存图
- 后端:5 个接口(存在性检查/列表/上传/获取/删除),白名单 + 管理员鉴权
- 前端:App.tsx 按 hash 路由到特殊文件夹,复用 PhotoWallPage
- 前端:文件夹模式无张数上限,localStorage 按文件夹隔离
- 前端:管理员配额警告条(超 1GiB 时显示)
- 前端:403 无权访问时退回起始页

Co-Authored-By: Claude <noreply@anthropic.com>
@
2026-06-24 22:19:29 +08:00

99 lines
2.9 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.

// 特殊文件夹图片接口。基址与 photos.ts 一致。
const API_BASE =
(import.meta.env.VITE_API_BASE as string | undefined) ?? 'http://localhost:8080'
function authHeaders(token: string): Record<string, string> {
return { Authorization: `Bearer ${token}` }
}
/** 检查文件夹是否已注册(登录前,无鉴权) */
export async function checkFolder(name: string): Promise<boolean> {
try {
const res = await fetch(`${API_BASE}/api/web/folder`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
if (!res.ok) return false
return ((await res.json()) as { ok: boolean }).ok
} catch {
return false
}
}
export interface FolderPhotoItem {
id: string
}
export interface FolderPhotoList {
items: FolderPhotoItem[]
totalBytes: number
overQuota: boolean
}
/** 上传失败的可区分原因 */
export type UploadError = 'too-large' | 'bad-type' | 'failed'
/** 列出文件夹内所有图片,含配额信息 */
export async function listFolderPhotos(
token: string,
folder: string,
): Promise<FolderPhotoList> {
const res = await fetch(`${API_BASE}/api/web/folder/${folder}/photos`, {
headers: authHeaders(token),
})
if (res.status === 403) throw new Error('forbidden')
if (!res.ok) throw new Error(`list failed: ${res.status}`)
return (await res.json()) as FolderPhotoList
}
/** 上传图片到文件夹,返回服务器分配的 id */
export async function uploadFolderPhoto(
token: string,
folder: string,
dataUrl: string,
): Promise<string> {
const res = await fetch(`${API_BASE}/api/web/folder/${folder}/photos`, {
method: 'POST',
headers: { ...authHeaders(token), 'Content-Type': 'application/json' },
body: JSON.stringify({ dataUrl }),
})
if (res.ok) return ((await res.json()) as { id: string }).id
if (res.status === 403) throw new Error('forbidden')
const reason: UploadError =
res.status === 413
? 'too-large'
: res.status === 415
? 'bad-type'
: 'failed'
throw new Error(reason)
}
/** 删除文件夹内图片 */
export async function deleteFolderPhoto(
token: string,
folder: string,
id: string,
): Promise<void> {
const res = await fetch(`${API_BASE}/api/web/folder/${folder}/photos/${id}`, {
method: 'DELETE',
headers: authHeaders(token),
})
if (res.status === 403) throw new Error('forbidden')
if (!res.ok && res.status !== 404) throw new Error(`delete failed: ${res.status}`)
}
/** 获取文件夹内图片的 objectURL调用方负责释放 */
export async function fetchFolderPhotoUrl(
token: string,
folder: string,
id: string,
): Promise<string> {
const res = await fetch(`${API_BASE}/api/web/folder/${folder}/photos/${id}`, {
headers: authHeaders(token),
})
if (res.status === 403) throw new Error('forbidden')
if (!res.ok) throw new Error(`get failed: ${res.status}`)
return URL.createObjectURL(await res.blob())
}