实现登录注册与模型大厅、预览视图

This commit is contained in:
2025-09-13 12:56:18 +08:00
parent 2d08c9f2d0
commit 5176724172
15 changed files with 741 additions and 29 deletions
+138
View File
@@ -0,0 +1,138 @@
// Lightweight API client for Basic Auth backend
// Base URL can be configured via Vite env VITE_API_BASE, defaults to '/api'
export type User = {
id: string
username: string
}
export type Me = User & {
avatarUrl?: string
}
export type ModelSummary = {
id: string
title: string
desc?: string
authorId: string
authorName?: string
createdAt?: string
updatedAt?: string
}
export type ModelDetail = ModelSummary & {
model: any
}
export type ListModelsQuery = {
q?: string
authorId?: string
page?: number
pageSize?: number
sort?: 'new' | 'old'
}
const API_BASE: string = (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_API_BASE) || '/api'
function buildAuthHeader(basicToken?: string): Record<string, string> {
if (!basicToken) return {}
return { Authorization: `Basic ${basicToken}` }
}
async function handle<T>(res: Response): Promise<T> {
if (!res.ok) {
let msg = res.status + ' ' + res.statusText
try {
const data = await res.json()
if (data && data.message) msg = data.message
} catch {}
throw new Error(msg)
}
const ct = res.headers.get('content-type') || ''
if (ct.includes('application/json')) return (await res.json()) as T
return (await res.text()) as unknown as T
}
export const api = {
base: API_BASE,
// Users
async register(username: string, password: string) {
const res = await fetch(`${API_BASE}/users/register`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password })
})
return handle<User>(res)
},
async me(basicToken?: string) {
const res = await fetch(`${API_BASE}/users/me`, {
headers: { ...buildAuthHeader(basicToken) },
})
return handle<Me>(res)
},
async updateMe(payload: { username?: string; avatarBase64?: string; avatarMime?: string }, basicToken?: string) {
const res = await fetch(`${API_BASE}/users/me`, {
method: 'PUT',
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
body: JSON.stringify(payload)
})
return handle<Me>(res)
},
avatarUrl(userId: string) {
return `${API_BASE}/users/${encodeURIComponent(userId)}/avatar`
},
// Models
async createModel(
payload: { title: string; desc?: string; model: any; previewBase64?: string; previewMime?: string },
basicToken?: string
) {
const res = await fetch(`${API_BASE}/models`, {
method: 'POST',
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
body: JSON.stringify(payload)
})
return handle<ModelDetail>(res)
},
async updateModel(id: string, payload: Partial<{ title: string; desc: string; model: any; previewBase64: string; previewMime: string }>, basicToken?: string) {
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`, {
method: 'PUT',
headers: { 'content-type': 'application/json', ...buildAuthHeader(basicToken) },
body: JSON.stringify(payload)
})
return handle<ModelDetail>(res)
},
async deleteModel(id: string, basicToken?: string) {
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`, {
method: 'DELETE',
headers: { ...buildAuthHeader(basicToken) },
})
return handle<{ ok: true }>(res)
},
async getModel(id: string) {
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}`)
return handle<ModelDetail>(res)
},
async listModels(query: ListModelsQuery = {}) {
const usp = new URLSearchParams()
if (query.q) usp.set('q', query.q)
if (query.authorId) usp.set('authorId', query.authorId)
if (query.page) usp.set('page', String(query.page))
if (query.pageSize) usp.set('pageSize', String(query.pageSize))
if (query.sort) usp.set('sort', query.sort)
const res = await fetch(`${API_BASE}/models${usp.toString() ? '?' + usp.toString() : ''}`)
return handle<{ items: ModelSummary[]; total?: number; page?: number; pageSize?: number }>(res)
},
async downloadModelJson(id: string) {
const res = await fetch(`${API_BASE}/models/${encodeURIComponent(id)}/model.json`)
return handle<any>(res)
},
previewUrl(id: string) {
return `${API_BASE}/models/${encodeURIComponent(id)}/preview`
},
}
export type BasicCredential = { username: string; password: string }
export function encodeBasic({ username, password }: BasicCredential) {
return btoa(`${username}:${password}`)
}