feat(client): photos API client + expose auth token
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
07b0299438
commit
9685336903
@ -61,7 +61,7 @@ function App() {
|
||||
if (auth.username) {
|
||||
// 已登录:照片墙入口 → 照片墙,否则博客页。
|
||||
view = wantsPhoto
|
||||
? <PhotoWallPage onExit={goStart} />
|
||||
? <PhotoWallPage onExit={goStart} token={auth.token ?? ''} />
|
||||
: <BlogPage username={auth.username} onSignOut={goStart} />
|
||||
} else if (wantsPhoto) {
|
||||
// #photo 未登录:复用登录页,但去掉顶部服务器负载栏。
|
||||
|
||||
57
Client/src/api/photos.ts
Normal file
57
Client/src/api/photos.ts
Normal file
@ -0,0 +1,57 @@
|
||||
// 照片墙图片接口封装。基址与 auth.ts 一致:生产留空走同源相对路径。
|
||||
const API_BASE =
|
||||
(import.meta.env.VITE_API_BASE as string | undefined) ?? 'http://localhost:8080'
|
||||
|
||||
export interface PhotoItem {
|
||||
id: string
|
||||
}
|
||||
|
||||
/** 上传失败的可区分原因,供 UI 给出对应提示 */
|
||||
export type UploadError = 'full' | 'too-large' | 'bad-type' | 'failed'
|
||||
|
||||
function authHeaders(token: string): Record<string, string> {
|
||||
return { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
|
||||
/** 列出当前用户的图片 id 与数量上限 */
|
||||
export async function listPhotos(token: string): Promise<{ items: PhotoItem[]; max: number }> {
|
||||
const res = await fetch(`${API_BASE}/api/web/photos`, { headers: authHeaders(token) })
|
||||
if (!res.ok) throw new Error(`list failed: ${res.status}`)
|
||||
return (await res.json()) as { items: PhotoItem[]; max: number }
|
||||
}
|
||||
|
||||
/** 上传一张图片(base64 data URL),返回服务器分配的图片 id */
|
||||
export async function uploadPhoto(token: string, dataUrl: string): Promise<string> {
|
||||
const res = await fetch(`${API_BASE}/api/web/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
|
||||
const reason: UploadError =
|
||||
res.status === 409
|
||||
? 'full'
|
||||
: res.status === 413
|
||||
? 'too-large'
|
||||
: res.status === 415
|
||||
? 'bad-type'
|
||||
: 'failed'
|
||||
throw new Error(reason)
|
||||
}
|
||||
|
||||
/** 删除一张图片 */
|
||||
export async function deletePhoto(token: string, id: string): Promise<void> {
|
||||
const res = await fetch(`${API_BASE}/api/web/photos/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: authHeaders(token),
|
||||
})
|
||||
if (!res.ok && res.status !== 404) throw new Error(`delete failed: ${res.status}`)
|
||||
}
|
||||
|
||||
/** 取图片字节为 objectURL(带鉴权,故不能直接用作 <img src> 的服务器 URL)。
|
||||
* 调用方负责在不用时 URL.revokeObjectURL 释放。 */
|
||||
export async function fetchPhotoObjectUrl(token: string, id: string): Promise<string> {
|
||||
const res = await fetch(`${API_BASE}/api/web/photos/${id}`, { headers: authHeaders(token) })
|
||||
if (!res.ok) throw new Error(`get failed: ${res.status}`)
|
||||
return URL.createObjectURL(await res.blob())
|
||||
}
|
||||
@ -21,6 +21,8 @@ import {
|
||||
interface PhotoWallPageProps {
|
||||
/** 退出照片墙(清登录态 + 抹掉地址栏 #photo,回到导航页) */
|
||||
onExit: () => void
|
||||
/** 当前会话 JWT,供后续服务端图片接口使用(Task 6 正式消费) */
|
||||
token: string
|
||||
}
|
||||
|
||||
type ElType = 'photo' | 'note' | 'tape' | 'sticker' | 'stamp' | 'doodle'
|
||||
@ -115,7 +117,8 @@ function loadInitial() {
|
||||
return { els, bgIndex, nextId, maxZ }
|
||||
}
|
||||
|
||||
export function PhotoWallPage({ onExit }: PhotoWallPageProps) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function PhotoWallPage({ onExit, token: _token }: PhotoWallPageProps) {
|
||||
const initial = useMemo(() => loadInitial(), [])
|
||||
|
||||
const [els, setEls] = useState<El[]>(initial.els)
|
||||
|
||||
@ -35,6 +35,8 @@ function loadSession(): Session | null {
|
||||
export interface Auth {
|
||||
/** 已登录则为当前用户名,否则为 null */
|
||||
username: string | null
|
||||
/** 当前会话 token(JWT),未登录为 null。用于受保护接口的 Authorization 头 */
|
||||
token: string | null
|
||||
/** 校验凭据;remember 为 true 时持久化到 localStorage,否则只存 sessionStorage */
|
||||
signIn: (username: string, password: string, remember: boolean) => Promise<void>
|
||||
/** 退出登录并清除浏览器里的会话(两种 storage 都清) */
|
||||
@ -73,5 +75,5 @@ export function useAuth(): Auth {
|
||||
sessionStorage.removeItem(STORAGE_KEY)
|
||||
}, [])
|
||||
|
||||
return { username: session?.username ?? null, signIn, signOut }
|
||||
return { username: session?.username ?? null, token: session?.token ?? null, signIn, signOut }
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user