初始化工程

This commit is contained in:
筱野
2025-08-10 21:09:19 +08:00
commit 42c71f5d13
30 changed files with 12882 additions and 0 deletions

176
src/services/authService.ts Normal file
View File

@@ -0,0 +1,176 @@
import httpService from './httpService'
import tokenManager from '../utils/tokenManager'
import type { ApiResponse } from './httpService'
// 用户接口
export interface User {
id: string
nickname: string
avatar?: string
phone?: string
email?: string
status: 'active' | 'inactive' | 'banned'
createdAt: string
updatedAt: string
}
// 登录响应接口
export interface LoginResponse {
token: string
refreshToken: string
user: User
expiresAt: number
}
// 认证服务类
class AuthService {
// 用户登录
async login(data: { phone: string; code: string }): Promise<ApiResponse<LoginResponse>> {
const response = await httpService.post('/auth/login', data, {
needAuth: false,
showLoading: true,
loadingText: '登录中...'
})
// 登录成功后保存token
if (response.success && response.data) {
tokenManager.setToken({
accessToken: response.data.token,
refreshToken: response.data.refreshToken,
expiresAt: response.data.expiresAt
})
}
return response
}
// 发送验证码
async sendSmsCode(phone: string): Promise<ApiResponse<{ success: boolean }>> {
return httpService.post('/auth/sms-code', { phone }, {
needAuth: false,
showLoading: true,
loadingText: '发送中...'
})
}
// 刷新token
async refreshToken(): Promise<ApiResponse<{ token: string; expiresAt: number }>> {
const refreshToken = tokenManager.getRefreshToken()
if (!refreshToken) {
throw new Error('没有刷新令牌')
}
const response = await httpService.post('/auth/refresh', {
refreshToken
}, {
needAuth: false
})
// 更新token
if (response.success && response.data) {
tokenManager.setToken({
accessToken: response.data.token,
expiresAt: response.data.expiresAt
})
}
return response
}
// 用户登出
async logout(): Promise<void> {
try {
// 调用登出接口
await httpService.post('/auth/logout', {}, {
showLoading: true,
loadingText: '退出中...'
})
} catch (error) {
console.error('登出接口调用失败:', error)
} finally {
// 清除本地token
tokenManager.clearTokens()
}
}
// 获取当前用户信息
async getCurrentUser(): Promise<ApiResponse<User>> {
return httpService.get('/auth/me')
}
// 更新用户信息
async updateProfile(data: Partial<User>): Promise<ApiResponse<User>> {
return httpService.put('/auth/profile', data, {
showLoading: true,
loadingText: '更新中...'
})
}
// 修改密码
async changePassword(data: {
oldPassword: string
newPassword: string
}): Promise<ApiResponse<{ success: boolean }>> {
return httpService.put('/auth/password', data, {
showLoading: true,
loadingText: '修改中...'
})
}
// 绑定手机号
async bindPhone(data: {
phone: string
code: string
}): Promise<ApiResponse<{ success: boolean }>> {
return httpService.post('/auth/bind-phone', data, {
showLoading: true,
loadingText: '绑定中...'
})
}
// 解绑手机号
async unbindPhone(data: {
code: string
}): Promise<ApiResponse<{ success: boolean }>> {
return httpService.post('/auth/unbind-phone', data, {
showLoading: true,
loadingText: '解绑中...'
})
}
// 检查登录状态
isLoggedIn(): boolean {
return tokenManager.hasValidToken()
}
// 获取当前用户token
getToken(): string | null {
return tokenManager.getAccessToken()
}
// 清除登录状态
clearAuth(): void {
tokenManager.clearTokens()
}
// 账号注销
async deleteAccount(data: {
password: string
code: string
}): Promise<ApiResponse<{ success: boolean }>> {
const response = await httpService.delete('/auth/account', data, {
showLoading: true,
loadingText: '注销中...'
})
// 注销成功后清除本地数据
if (response.success) {
this.clearAuth()
}
return response
}
}
// 导出认证服务实例
export default new AuthService()

74
src/services/commonApi.ts Normal file
View File

@@ -0,0 +1,74 @@
import Taro from '@tarojs/taro'
import httpService from './httpService'
import type { ApiResponse } from './httpService'
// 图片上传响应
export interface UploadResponse {
url: string
filename: string
size: number
}
// 通用API服务类
class CommonApiService {
// ==================== 文件上传接口 ====================
// 上传单个图片
async uploadImage(filePath: string): Promise<ApiResponse<UploadResponse>> {
const uploadTask = Taro.uploadFile({
url: `${httpService['baseURL']}/upload/image`,
filePath,
name: 'file',
header: {
...httpService['buildHeaders']({ url: '', needAuth: true })
}
})
return new Promise((resolve, reject) => {
uploadTask.then(response => {
try {
const data = JSON.parse(response.data)
resolve(data)
} catch (error) {
reject(new Error('上传响应解析失败'))
}
}).catch(reject)
})
}
// 批量上传图片
async uploadImages(filePaths: string[]): Promise<ApiResponse<UploadResponse[]>> {
try {
Taro.showLoading({ title: '上传图片中...', mask: true })
const uploadPromises = filePaths.map(filePath => this.uploadImage(filePath))
const results = await Promise.all(uploadPromises)
return {
code: 200,
success: true,
message: '上传成功',
data: results.map(result => result.data)
}
} catch (error) {
throw error
} finally {
Taro.hideLoading()
}
}
// ==================== 用户信息接口(基础版本) ====================
// 更新用户信息
async updateUserProfile(data: any): Promise<ApiResponse<any>> {
return httpService.put('/user/profile', data, {
showLoading: true,
loadingText: '保存中...'
})
}
}
// 导出通用API服务实例
export default new CommonApiService()

134
src/services/demoApi.ts Normal file
View File

@@ -0,0 +1,134 @@
import httpService from './httpService'
import type { ApiResponse } from './httpService'
// 用户信息接口
export interface UserProfile {
id: string
nickname: string
avatar?: string
age?: number
gender: 'male' | 'female'
interests: string[]
acceptNotification: boolean
}
// 反馈评价接口
export interface Feedback {
id: string
matchId: string
userId: string
photos: string[]
rating: number
recommend: 'yes' | 'no' | 'neutral'
aspects: string[]
comments: string
createdAt: string
}
// DynamicFormDemo页面API服务类
class DemoApiService {
// ==================== 用户信息相关接口 ====================
// 获取用户信息
async getUserProfile(): Promise<ApiResponse<UserProfile>> {
return httpService.get('/user/profile')
}
// 更新用户信息
async updateUserProfile(data: Partial<UserProfile>): Promise<ApiResponse<UserProfile>> {
return httpService.put('/user/profile', data, {
showLoading: true,
loadingText: '保存中...'
})
}
// 上传头像
async uploadAvatar(filePath: string): Promise<ApiResponse<{ url: string }>> {
return httpService.post('/user/avatar', { filePath }, {
showLoading: true,
loadingText: '上传头像中...'
})
}
// ==================== 反馈评价相关接口 ====================
// 提交活动评价
async submitFeedback(data: {
matchId: string
photos?: string[]
rating: number
recommend: 'yes' | 'no' | 'neutral'
aspects: string[]
comments: string
}): Promise<ApiResponse<Feedback>> {
return httpService.post('/feedback', data, {
showLoading: true,
loadingText: '提交评价中...'
})
}
// 获取我的评价列表
async getMyFeedbacks(params?: {
page?: number
limit?: number
}): Promise<ApiResponse<{ list: Feedback[]; total: number }>> {
return httpService.get('/feedback/my', params)
}
// 获取活动的所有评价
async getMatchFeedbacks(matchId: string, params?: {
page?: number
limit?: number
}): Promise<ApiResponse<{ list: Feedback[]; total: number }>> {
return httpService.get(`/feedback/match/${matchId}`, params)
}
// ==================== 通用表单提交接口 ====================
// 提交表单数据(通用接口)
async submitForm(formType: string, formData: any[]): Promise<ApiResponse<any>> {
return httpService.post('/forms/submit', {
type: formType,
data: formData
}, {
showLoading: true,
loadingText: '提交中...'
})
}
// 保存表单草稿
async saveFormDraft(formType: string, formData: any[]): Promise<ApiResponse<{ id: string }>> {
return httpService.post('/forms/draft', {
type: formType,
data: formData
}, {
showLoading: true,
loadingText: '保存中...'
})
}
// 获取表单草稿
async getFormDrafts(formType: string): Promise<ApiResponse<{ id: string; data: any[]; createdAt: string }[]>> {
return httpService.get('/forms/drafts', { type: formType })
}
// 删除表单草稿
async deleteFormDraft(id: string): Promise<ApiResponse<{ success: boolean }>> {
return httpService.delete(`/forms/draft/${id}`)
}
// ==================== 兴趣爱好相关接口 ====================
// 获取兴趣爱好选项
async getInterestOptions(): Promise<ApiResponse<{ label: string; value: string }[]>> {
return httpService.get('/interests')
}
// 获取推荐的兴趣爱好
async getRecommendedInterests(): Promise<ApiResponse<string[]>> {
return httpService.get('/interests/recommended')
}
}
// 导出API服务实例
export default new DemoApiService()

326
src/services/httpService.ts Normal file
View File

@@ -0,0 +1,326 @@
import Taro from '@tarojs/taro'
import tokenManager from '../utils/tokenManager'
import envConfig, { isDevelopment, getEnvInfo } from '../config/env'
// 请求方法类型
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
// 请求配置接口
export interface RequestConfig {
url: string
method?: HttpMethod
data?: any
params?: Record<string, any>
headers?: Record<string, string>
needAuth?: boolean // 是否需要token认证
showLoading?: boolean // 是否显示加载提示
loadingText?: string // 加载提示文本
}
// 响应数据接口
export interface ApiResponse<T = any> {
code: number
data: T
message: string
success: boolean
}
// HTTP状态码常量
export const HTTP_STATUS = {
SUCCESS: 200,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
SERVER_ERROR: 500
}
class HttpService {
private baseURL: string
private timeout: number
private enableLog: boolean
constructor() {
// 使用环境配置
this.baseURL = `${envConfig.apiBaseURL}/api/${envConfig.apiVersion}`
this.timeout = envConfig.timeout
this.enableLog = envConfig.enableLog
// 在开发环境下输出配置信息
if (isDevelopment()) {
console.log('🌍 HTTP服务初始化:', {
baseURL: this.baseURL,
timeout: this.timeout,
envInfo: getEnvInfo()
})
}
}
// 构建完整URL
private buildUrl(url: string, params?: Record<string, any>): string {
const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}`
if (params) {
const searchParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value))
}
})
const queryString = searchParams.toString()
return queryString ? `${fullUrl}?${queryString}` : fullUrl
}
return fullUrl
}
// 构建请求头
private buildHeaders(config: RequestConfig): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Environment': envConfig.name, // 添加环境标识
...config.headers
}
// 如果需要认证添加token
if (config.needAuth !== false) {
const authHeader = tokenManager.getAuthHeader()
Object.assign(headers, authHeader)
}
return headers
}
// 日志输出
private log(level: 'info' | 'warn' | 'error', message: string, data?: any) {
if (!this.enableLog) return
const logMethod = console[level] || console.log
const timestamp = new Date().toLocaleTimeString()
if (data) {
logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`, data)
} else {
logMethod(`[${timestamp}] HTTP ${level.toUpperCase()}: ${message}`)
}
}
// 处理响应
private handleResponse<T>(response: any): Promise<ApiResponse<T>> {
return new Promise((resolve, reject) => {
const { statusCode, data } = response
this.log('info', `响应状态码: ${statusCode}`, { data })
// HTTP状态码检查
if (statusCode !== HTTP_STATUS.SUCCESS) {
this.handleHttpError(statusCode)
reject(new Error(`HTTP Error: ${statusCode}`))
return
}
// 业务状态码检查
if (data && typeof data === 'object') {
if (data.success === false || (data.code && data.code !== 0 && data.code !== 200)) {
this.handleBusinessError(data)
reject(new Error(data.message || '请求失败'))
return
}
}
resolve(data)
})
}
// 处理HTTP错误
private handleHttpError(statusCode: number): void {
let message = '网络请求失败'
switch (statusCode) {
case HTTP_STATUS.UNAUTHORIZED:
message = '登录已过期,请重新登录'
tokenManager.clearTokens()
// 可以在这里跳转到登录页面
break
case HTTP_STATUS.FORBIDDEN:
message = '没有权限访问该资源'
break
case HTTP_STATUS.NOT_FOUND:
message = '请求的资源不存在'
break
case HTTP_STATUS.SERVER_ERROR:
message = '服务器内部错误'
break
default:
message = `请求失败 (${statusCode})`
}
this.log('error', `HTTP错误 ${statusCode}: ${message}`)
Taro.showToast({
title: message,
icon: 'none',
duration: 2000
})
}
// 处理业务错误
private handleBusinessError(data: any): void {
const message = data.message || '操作失败'
this.log('error', `业务错误: ${message}`, data)
Taro.showToast({
title: message,
icon: 'none',
duration: 2000
})
}
// 统一请求方法
async request<T = any>(config: RequestConfig): Promise<ApiResponse<T>> {
const {
url,
method = 'GET',
data,
params,
showLoading = false,
loadingText = '请求中...'
} = config
const fullUrl = this.buildUrl(url, method === 'GET' ? params : undefined)
this.log('info', `发起请求: ${method} ${fullUrl}`, {
data: method !== 'GET' ? data : undefined,
params: method === 'GET' ? params : undefined
})
// 检查token如果需要认证
if (config.needAuth === true && !tokenManager.hasValidToken()) {
Taro.showToast({
title: '请先登录',
icon: 'none'
})
throw new Error('Token无效或已过期')
}
// 显示加载提示
if (showLoading) {
Taro.showLoading({
title: loadingText,
mask: true
})
}
try {
const requestConfig = {
url: fullUrl,
method: method,
data: method !== 'GET' ? data : undefined,
header: this.buildHeaders(config),
timeout: this.timeout
}
const response = await Taro.request(requestConfig)
return this.handleResponse<T>(response)
} catch (error) {
this.log('error', '请求失败', error)
// 在模拟模式下返回模拟数据
if (envConfig.enableMock && isDevelopment()) {
this.log('info', '使用模拟数据')
return this.getMockResponse<T>(url, method)
}
Taro.showToast({
title: '网络连接失败',
icon: 'none'
})
throw error
} finally {
// 隐藏加载提示
if (showLoading) {
Taro.hideLoading()
}
}
}
// 获取模拟数据
private getMockResponse<T>(url: string, method: string): ApiResponse<T> {
this.log('info', `返回模拟数据: ${method} ${url}`)
return {
code: 200,
success: true,
message: '模拟请求成功',
data: {
mockData: true,
url,
method,
timestamp: new Date().toISOString()
} as T
}
}
// GET请求
get<T = any>(url: string, params?: Record<string, any>, config?: Partial<RequestConfig>): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'GET',
params,
...config
})
}
// POST请求
post<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'POST',
data,
...config
})
}
// PUT请求
put<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'PUT',
data,
...config
})
}
// DELETE请求
delete<T = any>(url: string, params?: Record<string, any>, config?: Partial<RequestConfig>): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'DELETE',
params,
...config
})
}
// PATCH请求
patch<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T>> {
return this.request<T>({
url,
method: 'PATCH',
data,
...config
})
}
// 获取当前环境信息
getEnvInfo() {
return {
baseURL: this.baseURL,
timeout: this.timeout,
enableLog: this.enableLog,
...getEnvInfo()
}
}
}
// 导出HTTP服务实例
export default new HttpService()