2026-10-13 22:50:37 +08:00
|
|
|
import Taro from '@tarojs/taro'
|
2026-10-15 21:56:45 +08:00
|
|
|
import { frontendPreviewProfile, isFrontendPreview } from './frontendPreview'
|
2026-10-13 22:50:37 +08:00
|
|
|
|
|
|
|
|
const BASE_URL = 'http://localhost:8080'
|
|
|
|
|
|
|
|
|
|
type ApiResult<T> = {
|
|
|
|
|
code: number
|
|
|
|
|
message: string
|
|
|
|
|
data: T
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RequestOptions = Omit<Taro.request.Option, 'url'>
|
|
|
|
|
|
|
|
|
|
export async function request<T>(url: string, options: RequestOptions = {}): Promise<T> {
|
2026-10-15 21:56:45 +08:00
|
|
|
if (isFrontendPreview()) {
|
|
|
|
|
if (url === '/api/user/profile' && (!options.method || options.method === 'GET')) {
|
|
|
|
|
return { ...frontendPreviewProfile, elementReport: { ...frontendPreviewProfile.elementReport, scores: [] } } as T
|
|
|
|
|
}
|
|
|
|
|
throw new Error('纯前端预览暂不请求后端或保存修改')
|
|
|
|
|
}
|
2026-10-13 22:50:37 +08:00
|
|
|
const token = Taro.getStorageSync('token')
|
|
|
|
|
const headers = {
|
|
|
|
|
'content-type': 'application/json',
|
|
|
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
|
|
|
...(options.header || {})
|
|
|
|
|
}
|
|
|
|
|
const res = await Taro.request<ApiResult<T>>({
|
|
|
|
|
url: `${BASE_URL}${url}`,
|
|
|
|
|
...options,
|
|
|
|
|
header: headers
|
|
|
|
|
})
|
|
|
|
|
if (res.data.code !== 0) {
|
|
|
|
|
throw new Error(res.data.message || '请求失败')
|
|
|
|
|
}
|
|
|
|
|
return res.data.data
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 21:56:45 +08:00
|
|
|
export function resolveAssetUrl(url?: string) {
|
|
|
|
|
if (!url) return ''
|
|
|
|
|
return /^https?:\/\//.test(url) || url.startsWith('wxfile://') || url.startsWith('http://tmp/')
|
|
|
|
|
? url
|
|
|
|
|
: `${BASE_URL}${url.startsWith('/') ? url : `/${url}`}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function uploadAvatar(filePath: string): Promise<UserProfile> {
|
|
|
|
|
if (isFrontendPreview()) throw new Error('纯前端预览暂不上传头像或保存修改')
|
|
|
|
|
const token = Taro.getStorageSync('token')
|
|
|
|
|
const response = await Taro.uploadFile({
|
|
|
|
|
url: `${BASE_URL}/api/user/avatar`,
|
|
|
|
|
filePath,
|
|
|
|
|
name: 'file',
|
|
|
|
|
header: token ? { Authorization: `Bearer ${token}` } : {}
|
|
|
|
|
})
|
|
|
|
|
let result: ApiResult<UserProfile>
|
|
|
|
|
try {
|
|
|
|
|
result = JSON.parse(response.data) as ApiResult<UserProfile>
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error('头像上传失败')
|
|
|
|
|
}
|
|
|
|
|
if (result.code !== 0) {
|
|
|
|
|
throw new Error(result.message || '头像上传失败')
|
|
|
|
|
}
|
|
|
|
|
return result.data
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-13 22:50:37 +08:00
|
|
|
export const api = {
|
|
|
|
|
wxLogin: (code: string) => request<{ sessionKey: string; phoneBound: boolean }>('/api/auth/wx-login', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
data: { code }
|
|
|
|
|
}),
|
|
|
|
|
sendSms: (phone: string) => request<{ mockCode?: string }>('/api/auth/sms', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
data: { phone }
|
|
|
|
|
}),
|
|
|
|
|
bindPhone: (sessionKey: string, phone: string, captcha: string) => request<{ token: string; user: UserProfile }>('/api/auth/bind-phone', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
data: { sessionKey, phone, captcha }
|
|
|
|
|
}),
|
|
|
|
|
profile: () => request<UserProfile>('/api/user/profile'),
|
|
|
|
|
updateProfile: (data: Partial<UserProfile>) => request<UserProfile>('/api/user/profile', {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
data
|
|
|
|
|
}),
|
2026-10-15 21:56:45 +08:00
|
|
|
uploadAvatar,
|
2026-10-13 22:50:37 +08:00
|
|
|
exchangeStatus: () => request<{ points: number; available: boolean; message: string }>('/api/points/exchange-status'),
|
|
|
|
|
agreement: (type: string) => request<{ title: string; body: string }>(`/api/content/agreement/${type}`),
|
|
|
|
|
follow: () => request<Record<string, string>>('/api/content/follow'),
|
|
|
|
|
homeOverview: () => request<HomeOverview>('/api/home/overview'),
|
|
|
|
|
tiangong: () => request<TiangongItem[]>('/api/tiangong/items'),
|
|
|
|
|
tiangongDetail: (id: string) => request<TiangongDetail>(`/api/tiangong/items/${id}`),
|
|
|
|
|
dailyHome: () => request<DailyHome>('/api/daily/home'),
|
|
|
|
|
pinDailyCard: (cardId: string) => request<{ pinnedCardId: string | null }>(`/api/daily/cards/${cardId}/pin`, {
|
|
|
|
|
method: 'POST'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ElementScore = {
|
|
|
|
|
name: string
|
|
|
|
|
color: string
|
|
|
|
|
value: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type UserProfile = {
|
|
|
|
|
id: number
|
2026-10-15 21:56:45 +08:00
|
|
|
avatarUrl?: string
|
2026-10-13 22:50:37 +08:00
|
|
|
nickname: string
|
|
|
|
|
phone?: string
|
|
|
|
|
gender: string
|
|
|
|
|
birthday: string
|
|
|
|
|
birthHour: string
|
|
|
|
|
province: string
|
|
|
|
|
city: string
|
|
|
|
|
district: string
|
|
|
|
|
points: number
|
|
|
|
|
remainingEdits: number
|
|
|
|
|
elementReport: {
|
|
|
|
|
scores: ElementScore[]
|
|
|
|
|
favorable: string
|
|
|
|
|
unfavorable: string
|
|
|
|
|
dayMaster: string
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type CalendarDay = {
|
|
|
|
|
day: number
|
|
|
|
|
weekName: string
|
|
|
|
|
label: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type LuckyPanel = {
|
|
|
|
|
title: string
|
|
|
|
|
value: string
|
|
|
|
|
accent: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type FortuneCard = {
|
|
|
|
|
id: string
|
|
|
|
|
title: string
|
|
|
|
|
category: string
|
|
|
|
|
categoryIcon: string
|
|
|
|
|
verdict: string
|
|
|
|
|
avoid: string
|
|
|
|
|
summary: string
|
|
|
|
|
detail: string
|
|
|
|
|
color: string
|
|
|
|
|
gradient: string
|
|
|
|
|
mascot: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type LifeTip = {
|
|
|
|
|
id: string
|
|
|
|
|
title: string
|
|
|
|
|
icon: string
|
|
|
|
|
content: string
|
|
|
|
|
color: string
|
|
|
|
|
mark: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type DailyHome = {
|
|
|
|
|
date: string
|
|
|
|
|
lunarDate: string
|
|
|
|
|
weekName: string
|
|
|
|
|
fortuneLevel: string
|
|
|
|
|
greeting: string
|
|
|
|
|
dailySummary: string
|
|
|
|
|
calendarDays: CalendarDay[]
|
|
|
|
|
luckyPanels: LuckyPanel[]
|
|
|
|
|
cards: FortuneCard[]
|
|
|
|
|
lifeTips: LifeTip[]
|
|
|
|
|
pinnedCardId?: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type DayFortune = {
|
|
|
|
|
label: '昨日' | '今日' | '明日'
|
|
|
|
|
date: string
|
|
|
|
|
weekName: string
|
|
|
|
|
lunarDate: string
|
|
|
|
|
status: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type LuckyColor = {
|
|
|
|
|
name: string
|
|
|
|
|
value: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type DimensionItem = {
|
|
|
|
|
id: string
|
|
|
|
|
title: string
|
|
|
|
|
icon: string
|
|
|
|
|
headline: string
|
|
|
|
|
detail: string
|
|
|
|
|
suitable: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type HomeOverview = {
|
|
|
|
|
days: DayFortune[]
|
|
|
|
|
activeDay: '昨日' | '今日' | '明日'
|
|
|
|
|
summary: string
|
|
|
|
|
overallIndex: number
|
|
|
|
|
luckyColors: LuckyColor[]
|
|
|
|
|
luckyNumbers: string[]
|
|
|
|
|
luckyDirection: string
|
|
|
|
|
luckyHours: Array<{ branch: string; time: string }>
|
|
|
|
|
dimensions: DimensionItem[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type TiangongItem = {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
|
|
|
|
imageUrl: string
|
|
|
|
|
unlocked: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type TiangongDetail = {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
|
|
|
|
title: string
|
|
|
|
|
imageUrl: string
|
|
|
|
|
introduction: string
|
|
|
|
|
blessing: string
|
|
|
|
|
}
|