diff --git a/Client/src/App.tsx b/Client/src/App.tsx
index 15cc993..0e81055 100644
--- a/Client/src/App.tsx
+++ b/Client/src/App.tsx
@@ -61,7 +61,7 @@ function App() {
if (auth.username) {
// 已登录:照片墙入口 → 照片墙,否则博客页。
view = wantsPhoto
- ?
+ ?
:
} else if (wantsPhoto) {
// #photo 未登录:复用登录页,但去掉顶部服务器负载栏。
diff --git a/Client/src/api/photos.ts b/Client/src/api/photos.ts
new file mode 100644
index 0000000..673030f
--- /dev/null
+++ b/Client/src/api/photos.ts
@@ -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 {
+ 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 {
+ 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 {
+ 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(带鉴权,故不能直接用作
的服务器 URL)。
+ * 调用方负责在不用时 URL.revokeObjectURL 释放。 */
+export async function fetchPhotoObjectUrl(token: string, id: string): Promise {
+ 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())
+}
diff --git a/Client/src/components/PhotoWallPage.tsx b/Client/src/components/PhotoWallPage.tsx
index 0075c47..db0c144 100644
--- a/Client/src/components/PhotoWallPage.tsx
+++ b/Client/src/components/PhotoWallPage.tsx
@@ -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(initial.els)
diff --git a/Client/src/hooks/useAuth.ts b/Client/src/hooks/useAuth.ts
index 1639093..875afd5 100644
--- a/Client/src/hooks/useAuth.ts
+++ b/Client/src/hooks/useAuth.ts
@@ -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
/** 退出登录并清除浏览器里的会话(两种 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 }
}