初始提交:一场soul的创业实验-永平 网站与小程序

Made-with: Cursor
This commit is contained in:
卡若
2026-03-07 22:58:43 +08:00
commit b7c35a89b0
513 changed files with 89020 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
/**
* 管理端 JWT 本地存储localStorage与 soul-api JWT 鉴权配合
*/
const ADMIN_TOKEN_KEY = 'admin_token'
export function getAdminToken(): string | null {
try {
return localStorage.getItem(ADMIN_TOKEN_KEY)
} catch {
return null
}
}
export function setAdminToken(token: string): void {
try {
localStorage.setItem(ADMIN_TOKEN_KEY, token)
} catch {
// ignore
}
}
export function clearAdminToken(): void {
try {
localStorage.removeItem(ADMIN_TOKEN_KEY)
} catch {
// ignore
}
}

View File

@@ -0,0 +1,86 @@
/**
* 统一 API 请求封装
* 规则API 路径与现网完全一致,仅通过 baseUrl 区分环境Next 或未来 Gin
* 鉴权:管理端使用 JWT自动带 Authorization: Bearer <token>token 存 localStorage
*/
import { getAdminToken } from './auth'
/** 未设置环境变量时使用的默认 API 地址(零配置部署) */
const DEFAULT_API_BASE = 'https://soulapi.quwanzhi.com'
const getBaseUrl = (): string => {
const url = import.meta.env.VITE_API_BASE_URL
if (typeof url === 'string' && url.length > 0) return url.replace(/\/$/, '')
return DEFAULT_API_BASE
}
/** 请求完整 URLbaseUrl + pathpath 必须与现网一致(如 /api/orders */
export function apiUrl(path: string): string {
const base = getBaseUrl()
const p = path.startsWith('/') ? path : `/${path}`
return base ? `${base}${p}` : p
}
export type RequestInitWithBody = RequestInit & { data?: unknown }
/**
* 发起请求。path 为与现网一致的 API 路径(如 /api/admin、/api/orders
* 若有 admin_tokenJWT则自动带 Authorization: Bearercredentials: 'include' 保留以兼容需 Cookie 的接口。
*/
export async function request<T = unknown>(
path: string,
options: RequestInitWithBody = {}
): Promise<T> {
const { data, ...init } = options
const url = apiUrl(path)
const headers = new Headers(init.headers as HeadersInit)
const token = getAdminToken()
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
if (data !== undefined && data !== null && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
const body = data !== undefined && data !== null ? JSON.stringify(data) : init.body
const res = await fetch(url, {
...init,
headers,
body,
credentials: 'include',
})
const contentType = res.headers.get('Content-Type') || ''
const json: T = contentType.includes('application/json')
? ((await res.json()) as T)
: (res as unknown as T)
if (!res.ok) {
const err = new Error((json as { error?: string })?.error || `HTTP ${res.status}`) as Error & {
status: number
data: T
}
err.status = res.status
err.data = json
throw err
}
return json
}
/** GET */
export function get<T = unknown>(path: string, init?: RequestInit): Promise<T> {
return request<T>(path, { ...init, method: 'GET' })
}
/** POST */
export function post<T = unknown>(path: string, data?: unknown, init?: RequestInit): Promise<T> {
return request<T>(path, { ...init, method: 'POST', data })
}
/** PUT */
export function put<T = unknown>(path: string, data?: unknown, init?: RequestInit): Promise<T> {
return request<T>(path, { ...init, method: 'PUT', data })
}
/** DELETE */
export function del<T = unknown>(path: string, init?: RequestInit): Promise<T> {
return request<T>(path, { ...init, method: 'DELETE' })
}