609 lines
19 KiB
TypeScript
609 lines
19 KiB
TypeScript
import axios, { AxiosRequestConfig } from 'axios'
|
||
|
||
interface ApiClient {
|
||
get<T>(url: string, config?: AxiosRequestConfig): Promise<T>
|
||
post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>
|
||
put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>
|
||
delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>
|
||
}
|
||
|
||
// API 基址:优先级 window.__APP_CONFIG__.apiBase > 环境变量 > 默认 '/api'
|
||
// - 开发/Vite 代理:'/api'(由 vite.config 代理到后端 :8000)
|
||
// - digital.99hui.com 同域部署:保持 '/api'
|
||
// - 仅跨域部署时配置后端公网地址
|
||
function resolveBaseURL(): string {
|
||
const cfg = (window as any).__APP_CONFIG__
|
||
if (cfg && cfg.apiBase) return cfg.apiBase as string
|
||
const env = import.meta.env.VITE_API_BASE as string | undefined
|
||
if (env) return env
|
||
return '/api'
|
||
}
|
||
|
||
// 统一认证 token(由 uniapp 壳通过 URL 注入,见 utils/uniapp-bridge.ts)
|
||
let _authToken: string | null = null
|
||
export function setAuthToken(token: string | null) {
|
||
_authToken = token
|
||
}
|
||
export function getAuthToken(): string | null {
|
||
return _authToken
|
||
}
|
||
|
||
// 创建 axios 实例(复用现有项目模式)
|
||
const createRequest = (config?: AxiosRequestConfig): ApiClient => {
|
||
const request = axios.create({
|
||
baseURL: resolveBaseURL(),
|
||
timeout: 30000,
|
||
...config
|
||
})
|
||
|
||
// 请求拦截器:注入认证头
|
||
request.interceptors.request.use((cfg) => {
|
||
if (_authToken) {
|
||
cfg.headers = cfg.headers || {}
|
||
;(cfg.headers as any).Authorization = `Bearer ${_authToken}`
|
||
}
|
||
return cfg
|
||
})
|
||
|
||
// 响应拦截器
|
||
request.interceptors.response.use(
|
||
(res) => {
|
||
const data = fixDatetimeTZ(res.data)
|
||
if (data && data.code && data.code !== 200) {
|
||
console.error('API Error:', data.message)
|
||
return Promise.reject(new Error(data.message))
|
||
}
|
||
// 解包标准响应 { code, message, data }
|
||
if (data && data.code === 200) {
|
||
return data.data !== undefined ? data.data : data
|
||
}
|
||
return data
|
||
},
|
||
(err) => {
|
||
const msg = err.response?.data?.detail || err.response?.data?.message || err.message || '网络错误'
|
||
console.error('Network Error:', msg)
|
||
return Promise.reject(err)
|
||
}
|
||
)
|
||
|
||
// The response interceptor unwraps the API envelope before callers receive it.
|
||
return request as unknown as ApiClient
|
||
}
|
||
|
||
// 递归修复时区标识(复用现有项目逻辑)
|
||
function fixDatetimeTZ(obj: any): any {
|
||
if (typeof obj === 'string') {
|
||
return obj.replace(/T(\d{2}:\d{2}:\d{2})\+00:00/g, 'T$1\+08:00')
|
||
}
|
||
if (Array.isArray(obj)) return obj.map(fixDatetimeTZ)
|
||
if (obj && typeof obj === 'object') {
|
||
const result: any = {}
|
||
for (const k in obj) result[k] = fixDatetimeTZ(obj[k])
|
||
return result
|
||
}
|
||
return obj
|
||
}
|
||
|
||
const request = createRequest()
|
||
|
||
// ==================== 分身管理 API ====================
|
||
|
||
export interface Avatar {
|
||
id: string
|
||
name: string
|
||
displayName: string
|
||
description: string
|
||
photoUrl?: string
|
||
status: 'active' | 'inactive' | 'training'
|
||
tokenBalance: number
|
||
createdAt: string
|
||
updatedAt: string
|
||
config?: Record<string, any>
|
||
}
|
||
|
||
// 获取分身列表
|
||
export const getAvatarList = (params?: { page?: number; limit?: number }) =>
|
||
request.get<{ data: Avatar[]; total: number }>('/avatar', { params })
|
||
|
||
// 获取分身详情
|
||
export const getAvatarDetail = (id: string) =>
|
||
request.get<Avatar>(`/avatar/${id}`)
|
||
|
||
// 创建分身
|
||
export const createAvatar = (data: Partial<Avatar>) =>
|
||
request.post<Avatar>('/avatar', data)
|
||
|
||
// 更新分身
|
||
export const updateAvatar = (id: string, data: Partial<Avatar>) =>
|
||
request.put<Avatar>(`/avatar/${id}`, data)
|
||
|
||
export const uploadAvatarPhoto = (id: string, file: File) => {
|
||
const form = new FormData()
|
||
form.append('file', file)
|
||
return request.post<{ photoUrl: string }>(`/avatar/${id}/photo`, form, {
|
||
headers: { 'Content-Type': 'multipart/form-data' }
|
||
})
|
||
}
|
||
|
||
// 删除分身
|
||
export const deleteAvatar = (id: string) =>
|
||
request.delete(`/avatar/${id}`)
|
||
|
||
// ==================== Token 管理 API ====================
|
||
|
||
export interface TokenBalance {
|
||
balance: number
|
||
totalGranted: number
|
||
totalConsumed: number
|
||
}
|
||
|
||
export interface TokenUsageSummary {
|
||
avatarId: string
|
||
source: string
|
||
promptTokens: number
|
||
completionTokens: number
|
||
totalTokens: number
|
||
requestCount: number
|
||
}
|
||
|
||
export interface TokenPaymentOrder {
|
||
id: string
|
||
orderNo: string
|
||
planId: string
|
||
paymentMethod: 'wechat' | 'alipay'
|
||
payType: 'WECHAT' | 'ALIPAY'
|
||
payWay: 'APP' | 'LITE' | 'JSAPI'
|
||
pointsAmount: number
|
||
price: number
|
||
status: 'pending' | 'paid' | 'failed'
|
||
providerStatus: string
|
||
payMessage: string
|
||
failureReason: string
|
||
balance: number
|
||
}
|
||
|
||
// 获取 Token 余额
|
||
export const getTokenBalance = () =>
|
||
request.get<TokenBalance>('/token/balance')
|
||
|
||
// 获取充值套餐
|
||
export const getRechargePlans = () =>
|
||
request.get<Array<{ id: string; name: string; amount: number; price: number }>>('/token/plans')
|
||
|
||
// 创建会会支付订单;支付回调成功后积分到账。
|
||
export const chargeToken = (
|
||
planId: string,
|
||
paymentMethod: 'wechat' | 'alipay',
|
||
payScene: 'APP' | 'LITE' | 'JSAPI'
|
||
) => request.post<TokenPaymentOrder>('/token/charge', { planId, paymentMethod, payScene })
|
||
|
||
export const getTokenPaymentStatus = (orderId: string) =>
|
||
request.get<TokenPaymentOrder>(`/token/payment/${orderId}`)
|
||
|
||
// 按分身和使用场景汇总 Token 消耗
|
||
export const getTokenUsage = () =>
|
||
request.get<TokenUsageSummary[]>('/token/usage')
|
||
|
||
// ==================== 授权管理 API ====================
|
||
|
||
export type AvatarPermission = 'friend' | 'chat' | 'publish' | 'browse' | 'interact' | 'takeover'
|
||
|
||
export interface AvatarPermissionSettings {
|
||
avatarId: string
|
||
permissions: AvatarPermission[]
|
||
takeoverReplyDelaySeconds: number
|
||
disabledAvatarIds?: string[]
|
||
}
|
||
|
||
export const getAvatarPermissionSettings = (avatarId: string) =>
|
||
request.get<AvatarPermissionSettings>(`/avatar/${avatarId}/permission-settings`)
|
||
|
||
export const updateAvatarPermissionSettings = (
|
||
avatarId: string,
|
||
permissions: AvatarPermission[],
|
||
takeoverReplyDelaySeconds: number
|
||
) => request.put<AvatarPermissionSettings>(`/avatar/${avatarId}/permission-settings`, {
|
||
permissions,
|
||
takeoverReplyDelaySeconds,
|
||
})
|
||
|
||
export interface TakeoverStatus {
|
||
enabled: boolean
|
||
status: 'disabled' | 'connecting' | 'ready' | 'needs_login' | 'error'
|
||
message: string
|
||
pendingCount: number
|
||
takeoverReplyDelaySeconds: number
|
||
lastPolledAt: string | null
|
||
}
|
||
|
||
export const getTakeoverStatus = (avatarId: string) =>
|
||
request.get<TakeoverStatus>(`/avatar/${avatarId}/takeover/status`)
|
||
|
||
export interface Authorization {
|
||
id: string
|
||
avatarId: string
|
||
targetType: 'user' | 'organization' | 'application'
|
||
targetId: string
|
||
targetName: string
|
||
permissions: string[]
|
||
status: 'active' | 'inactive'
|
||
takeoverEnabled: boolean
|
||
takeoverMode: 'immediate' | 'delayed'
|
||
takeoverDelaySeconds: number
|
||
createdAt: string
|
||
}
|
||
|
||
export type AuthorizationInput = Pick<
|
||
Authorization,
|
||
'targetType' | 'targetId' | 'targetName' | 'permissions'
|
||
>
|
||
|
||
// 获取授权列表
|
||
export const getAuthorizationList = (avatarId: string) =>
|
||
request.get<Authorization[]>(`/avatar/${avatarId}/authorizations`)
|
||
|
||
// 添加授权
|
||
export const createAuthorization = (avatarId: string, data: AuthorizationInput) =>
|
||
request.post<Authorization>(`/avatar/${avatarId}/authorizations`, data)
|
||
|
||
// 更新授权
|
||
export const updateAuthorization = (avatarId: string, data: Partial<Authorization> & { id: string }) =>
|
||
request.put<Authorization>(`/avatar/${avatarId}/authorizations`, data)
|
||
|
||
// 删除授权
|
||
export const deleteAuthorization = (avatarId: string, authorizationId: string) =>
|
||
request.delete<{ id: string }>(`/avatar/${avatarId}/authorizations/${authorizationId}`)
|
||
|
||
// 更新单聊接管配置
|
||
export const updateTakeoverConfig = (avatarId: string, data: {
|
||
authorizationId: string
|
||
takeoverEnabled: boolean
|
||
takeoverMode?: 'immediate' | 'delayed'
|
||
takeoverDelaySeconds?: number
|
||
}) =>
|
||
request.put<Authorization>(`/avatar/${avatarId}/authorizations/takeover`, data)
|
||
|
||
// ==================== 组织管理 API ====================
|
||
|
||
export interface Organization {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
emoji: string
|
||
type: 'team' | 'company' | 'community'
|
||
role: 'admin' | 'member' | 'viewer'
|
||
memberCount: number
|
||
createdAt: string
|
||
}
|
||
|
||
export interface CreateOrganizationInput {
|
||
name: string
|
||
desc?: string
|
||
emoji?: string
|
||
type?: 'team' | 'company' | 'community'
|
||
}
|
||
|
||
// 获取组织列表
|
||
export const getOrganizationList = (params?: any) =>
|
||
request.get<{ data: Organization[]; total: number }>('/organizations', { params })
|
||
|
||
// 创建组织
|
||
export const createOrganization = (data: CreateOrganizationInput) =>
|
||
request.post<Organization>('/organizations', data)
|
||
|
||
// ==================== 知识库管理 API ====================
|
||
|
||
export interface KnowledgeDoc {
|
||
id: string
|
||
avatarId: string
|
||
filename: string
|
||
fileType: string
|
||
fileSize: number
|
||
fileUrl: string
|
||
status: string
|
||
filePresent?: boolean
|
||
vectorized?: boolean
|
||
embeddingModel?: string
|
||
chunkCount?: number
|
||
errorMessage?: string
|
||
indexStage?: string
|
||
indexProgress?: number
|
||
createdAt: string
|
||
}
|
||
|
||
export interface QAPair {
|
||
id: string
|
||
avatarId: string
|
||
question: string
|
||
answer: string
|
||
enabled?: boolean
|
||
createdAt: string
|
||
updatedAt: string
|
||
}
|
||
|
||
export interface SearchResult {
|
||
docId: string
|
||
filename: string
|
||
fileType: string
|
||
snippet: string
|
||
score: number
|
||
}
|
||
|
||
// 文档列表
|
||
export const getKnowledgeDocs = (avatarId: string) =>
|
||
request.get<KnowledgeDoc[]>(`/avatar/${avatarId}/knowledge/docs`)
|
||
|
||
const KNOWLEDGE_UPLOAD_CHUNK_SIZE = 5 * 1024 * 1024
|
||
|
||
const uploadKnowledgeChunk = async (
|
||
avatarId: string,
|
||
uploadId: string,
|
||
chunkIndex: number,
|
||
chunk: Blob,
|
||
onProgress?: (loaded: number) => void
|
||
) => {
|
||
const form = new FormData()
|
||
form.append('file', chunk, `chunk-${chunkIndex}`)
|
||
let reportedLoaded = 0
|
||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||
try {
|
||
await request.post(
|
||
`/avatar/${avatarId}/knowledge/uploads/${uploadId}/chunks/${chunkIndex}`,
|
||
form,
|
||
{
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
timeout: 2 * 60 * 1000,
|
||
onUploadProgress: (event) => {
|
||
reportedLoaded = Math.max(reportedLoaded, Math.min(event.loaded, chunk.size))
|
||
onProgress?.(reportedLoaded)
|
||
}
|
||
}
|
||
)
|
||
return
|
||
} catch (error: any) {
|
||
const status = Number(error?.response?.status || 0)
|
||
const retryable = !status || status === 408 || status === 429 || status >= 500
|
||
if (!retryable || attempt === 3) throw error
|
||
await new Promise((resolve) => window.setTimeout(resolve, attempt * 800))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 大文件拆成 5MB 分片,避免生产代理的请求体限制拦截整个文件。
|
||
export const uploadKnowledgeDoc = async (
|
||
avatarId: string,
|
||
file: File,
|
||
onUploadProgress?: (loaded: number, total: number) => void
|
||
) => {
|
||
if (file.size > KNOWLEDGE_UPLOAD_CHUNK_SIZE) {
|
||
const totalChunks = Math.ceil(file.size / KNOWLEDGE_UPLOAD_CHUNK_SIZE)
|
||
const upload: any = await request.post(`/avatar/${avatarId}/knowledge/uploads`, {
|
||
filename: file.name,
|
||
fileSize: file.size,
|
||
totalChunks
|
||
})
|
||
let uploadedBytes = 0
|
||
for (let index = 0; index < totalChunks; index += 1) {
|
||
const start = index * KNOWLEDGE_UPLOAD_CHUNK_SIZE
|
||
const chunk = file.slice(start, Math.min(start + KNOWLEDGE_UPLOAD_CHUNK_SIZE, file.size))
|
||
await uploadKnowledgeChunk(
|
||
avatarId,
|
||
upload.uploadId,
|
||
index,
|
||
chunk,
|
||
(chunkLoaded) => onUploadProgress?.(uploadedBytes + chunkLoaded, file.size)
|
||
)
|
||
uploadedBytes += chunk.size
|
||
onUploadProgress?.(uploadedBytes, file.size)
|
||
}
|
||
return request.post<KnowledgeDoc>(
|
||
`/avatar/${avatarId}/knowledge/uploads/${upload.uploadId}/complete`,
|
||
undefined,
|
||
{ timeout: 2 * 60 * 1000 }
|
||
)
|
||
}
|
||
|
||
const form = new FormData()
|
||
form.append('file', file)
|
||
return request.post<KnowledgeDoc>(`/avatar/${avatarId}/knowledge/docs`, form, {
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
// A slow mobile uplink must not be mistaken for a failed upload.
|
||
timeout: 10 * 60 * 1000,
|
||
onUploadProgress: (event) => onUploadProgress?.(event.loaded, event.total || file.size)
|
||
})
|
||
}
|
||
|
||
// 删除文档
|
||
export const deleteKnowledgeDoc = (avatarId: string, docId: string) =>
|
||
request.delete(`/avatar/${avatarId}/knowledge/docs/${docId}`)
|
||
|
||
export const retryKnowledgeDoc = (avatarId: string, docId: string) =>
|
||
request.post<KnowledgeDoc>(`/avatar/${avatarId}/knowledge/docs/${docId}/retry`)
|
||
|
||
// 标准问答对列表
|
||
export const getQAPairs = (avatarId: string) =>
|
||
request.get<QAPair[]>(`/avatar/${avatarId}/knowledge/qa`)
|
||
|
||
// 创建问答对
|
||
export const createQAPair = (avatarId: string, data: { question: string; answer: string; enabled?: boolean }) =>
|
||
request.post<QAPair>(`/avatar/${avatarId}/knowledge/qa`, data)
|
||
|
||
// 更新问答对
|
||
export const updateQAPair = (avatarId: string, id: string, data: { question: string; answer: string; enabled?: boolean }) =>
|
||
request.put<QAPair>(`/avatar/${avatarId}/knowledge/qa/${id}`, data)
|
||
|
||
// 切换问答对启用状态
|
||
export const setQaEnabled = (avatarId: string, id: string, enabled: boolean) =>
|
||
request.put<QAPair>(`/avatar/${avatarId}/knowledge/qa/${id}/enabled`, { enabled })
|
||
|
||
// 删除问答对
|
||
export const deleteQAPair = (avatarId: string, id: string) =>
|
||
request.delete(`/avatar/${avatarId}/knowledge/qa/${id}`)
|
||
|
||
// 文档向量检索
|
||
export const searchKnowledge = (avatarId: string, q: string, topK = 5) =>
|
||
request.get<SearchResult[]>(`/avatar/${avatarId}/knowledge/search`, {
|
||
params: { q, top_k: topK }
|
||
})
|
||
|
||
// ==================== 数字分身聊天 API ====================
|
||
|
||
export interface ChatMessage {
|
||
role: 'user' | 'assistant'
|
||
content: string
|
||
attachmentIds?: string[]
|
||
}
|
||
|
||
export interface ChatResponse {
|
||
answer: string
|
||
source: 'qa' | 'knowledge' | 'vision' | 'qwen'
|
||
references?: Array<{ docId?: string; filename?: string; fileType?: string; snippet?: string; score?: number }>
|
||
}
|
||
|
||
export interface ChatAttachment {
|
||
id: string
|
||
avatarId: string
|
||
filename: string
|
||
mimeType: string
|
||
fileSize: number
|
||
status: 'processing' | 'ready' | 'failed'
|
||
category: 'general_image' | 'document' | 'medical_document' | 'medical_image'
|
||
summary: string
|
||
warning: string
|
||
expiresAt: string
|
||
}
|
||
|
||
export interface ChatPayload {
|
||
message: string
|
||
attachmentIds?: string[]
|
||
history?: ChatMessage[]
|
||
}
|
||
|
||
export const sendAvatarChat = (avatarId: string, payload: ChatPayload) =>
|
||
request.post<ChatResponse>(`/avatar/${avatarId}/chat`, payload)
|
||
|
||
export interface PublicAvatar {
|
||
id: string
|
||
name: string
|
||
displayName: string
|
||
description?: string
|
||
photoUrl?: string
|
||
emoji?: string
|
||
status: 'active' | 'inactive' | 'training'
|
||
}
|
||
|
||
export const createAvatarShareLink = (avatarId: string) =>
|
||
request.post<{ shareToken: string }>(`/avatar/${avatarId}/share`)
|
||
|
||
export const getPublicAvatar = (shareToken: string) =>
|
||
request.get<PublicAvatar>(`/public/avatar/${shareToken}`)
|
||
|
||
export const sendPublicAvatarChat = (shareToken: string, payload: ChatPayload) =>
|
||
request.post<ChatResponse>(`/public/avatar/${shareToken}/chat`, payload)
|
||
|
||
const imageForm = (file: File) => {
|
||
const form = new FormData()
|
||
form.append('file', file)
|
||
return form
|
||
}
|
||
|
||
export const uploadAvatarChatImage = (avatarId: string, file: File) =>
|
||
request.post<ChatAttachment>(`/avatar/${avatarId}/chat/images`, imageForm(file), {
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
timeout: 120000
|
||
})
|
||
|
||
export const uploadPublicAvatarChatImage = (shareToken: string, file: File) =>
|
||
request.post<ChatAttachment>(`/public/avatar/${shareToken}/chat/images`, imageForm(file), {
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
timeout: 120000
|
||
})
|
||
|
||
type ChatStreamHandlers = {
|
||
onMeta: (meta: Pick<ChatResponse, 'source' | 'references'>) => void
|
||
onDelta: (content: string) => void
|
||
}
|
||
|
||
const streamChat = async (path: string, payload: ChatPayload, handlers: ChatStreamHandlers) => {
|
||
const headers: Record<string, string> = { 'Content-Type': 'application/json', Accept: 'text/event-stream' }
|
||
if (_authToken) headers.Authorization = `Bearer ${_authToken}`
|
||
const response = await fetch(`${resolveBaseURL()}${path}`, { method: 'POST', headers, body: JSON.stringify(payload) })
|
||
if (!response.ok) {
|
||
const errorBody = await response.json().catch(() => null)
|
||
throw new Error(errorBody?.detail || errorBody?.message || `对话请求失败(${response.status})`)
|
||
}
|
||
if (!response.body) throw new Error('对话响应为空,请稍后重试')
|
||
|
||
const reader = response.body.getReader()
|
||
const decoder = new TextDecoder()
|
||
let buffer = ''
|
||
while (true) {
|
||
const { done, value } = await reader.read()
|
||
buffer += decoder.decode(value || new Uint8Array(), { stream: !done })
|
||
const events = buffer.split('\n\n')
|
||
buffer = events.pop() || ''
|
||
for (const eventBlock of events) {
|
||
const event = eventBlock.match(/^event:\s*(.+)$/m)?.[1] || 'message'
|
||
const data = eventBlock.match(/^data:\s*(.+)$/m)?.[1]
|
||
if (!data) continue
|
||
const parsed = JSON.parse(data)
|
||
if (event === 'meta') handlers.onMeta(parsed)
|
||
if (event === 'delta') handlers.onDelta(parsed.content || '')
|
||
if (event === 'error') throw new Error(parsed.message || '对话暂时不可用')
|
||
}
|
||
if (done) break
|
||
}
|
||
}
|
||
|
||
export const streamAvatarChat = (avatarId: string, payload: ChatPayload, handlers: ChatStreamHandlers) =>
|
||
streamChat(`/avatar/${avatarId}/chat/stream`, payload, handlers)
|
||
|
||
export const streamPublicAvatarChat = (shareToken: string, payload: ChatPayload, handlers: ChatStreamHandlers) =>
|
||
streamChat(`/public/avatar/${shareToken}/chat/stream`, payload, handlers)
|
||
|
||
// ==================== 会会用户资料 API ====================
|
||
|
||
export interface UserProfile {
|
||
userId: string
|
||
nickname: string
|
||
avatarUrl: string
|
||
}
|
||
|
||
// 获取会会系统中的用户头像与昵称
|
||
export const getUserProfile = () =>
|
||
request.get<UserProfile>('/user/profile')
|
||
|
||
// ==================== 会会短信验证码登录 API ====================
|
||
|
||
export interface SmsLoginResult {
|
||
token: string
|
||
user: UserProfile & { huihuiUserId: string; phone: string; createdAt?: string; lastLoginAt?: string }
|
||
huihui: { userId: string; nickname: string; avatarUrl: string }
|
||
}
|
||
|
||
// 发送短信验证码(演示模式会额外返回 devCode / dev 标记)
|
||
export const sendSmsCode = (phone: string) =>
|
||
request.post<{ sent: boolean; devCode?: string; dev?: boolean }>('/huihui/sms/send', { phone })
|
||
|
||
// 短信验证码登录
|
||
export const loginBySms = (phone: string, code: string) =>
|
||
request.post<SmsLoginResult>('/huihui/sms/login', { phone, code })
|
||
|
||
// 账号密码登录(会会 loginType=password)
|
||
export const loginByPassword = (account: string, password: string) =>
|
||
request.post<SmsLoginResult>('/huihui/pwd/login', { account, password })
|
||
|
||
// Validate a production Huihui access token and exchange it for an app session.
|
||
export const loginByHuihuiToken = (token: string) =>
|
||
request.post<SmsLoginResult>('/huihui/token/login', { token })
|
||
|
||
// 当前登录用户
|
||
export const getCurrentUser = () =>
|
||
request.get<UserProfile & { huihuiUserId: string; phone: string }>('/huihui/me')
|
||
|
||
// 退出登录
|
||
export const logoutUser = () =>
|
||
request.post('/huihui/logout')
|
||
|
||
export default request
|