- Brief 支持代理商附件上传 (迁移 007) - 项目新增 platform 字段 (迁移 008),前端创建/展示平台信息 - 修复 AI 规则解析:处理中文引号导致 JSON 解析失败的问题 - 修复消息中心崩溃:补全后端消息类型映射 + fallback 保护 - 项目创建时自动发送消息通知 - .gitignore 排除 backend/data/ 数据库文件 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1036 lines
28 KiB
TypeScript
1036 lines
28 KiB
TypeScript
/**
|
||
* API 客户端
|
||
* 支持双 Token JWT 认证
|
||
*/
|
||
import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios'
|
||
import type {
|
||
VideoReviewRequest,
|
||
VideoReviewResponse,
|
||
ReviewProgressResponse,
|
||
ReviewResultResponse,
|
||
ScriptReviewRequest,
|
||
ScriptReviewResponse,
|
||
} from '@/types/review'
|
||
import type {
|
||
TaskResponse,
|
||
TaskListResponse,
|
||
TaskScriptUploadRequest,
|
||
TaskVideoUploadRequest,
|
||
TaskCreateRequest,
|
||
TaskReviewRequest,
|
||
TaskStage,
|
||
ReviewTaskListResponse,
|
||
AppealRequest,
|
||
} from '@/types/task'
|
||
import type {
|
||
ProjectResponse,
|
||
ProjectListResponse,
|
||
ProjectCreateRequest,
|
||
ProjectUpdateRequest,
|
||
} from '@/types/project'
|
||
import type {
|
||
BriefResponse,
|
||
BriefCreateRequest,
|
||
} from '@/types/brief'
|
||
import type {
|
||
AgencyListResponse,
|
||
CreatorListResponse,
|
||
BrandListResponse,
|
||
} from '@/types/organization'
|
||
import type {
|
||
CreatorDashboard,
|
||
AgencyDashboard,
|
||
BrandDashboard,
|
||
} from '@/types/dashboard'
|
||
import type {
|
||
ForbiddenWordCreate,
|
||
ForbiddenWordResponse,
|
||
ForbiddenWordListResponse,
|
||
WhitelistCreate,
|
||
WhitelistResponse,
|
||
WhitelistListResponse,
|
||
CompetitorCreate,
|
||
CompetitorResponse,
|
||
CompetitorListResponse,
|
||
PlatformRuleResponse,
|
||
PlatformListResponse,
|
||
RuleValidateRequest,
|
||
RuleValidateResponse,
|
||
PlatformRuleParseRequest,
|
||
PlatformRuleParseResponse,
|
||
PlatformRuleConfirmRequest,
|
||
BrandPlatformRuleResponse,
|
||
BrandPlatformRuleListResponse,
|
||
} from '@/types/rules'
|
||
import type {
|
||
AIConfigUpdate,
|
||
AIConfigResponse,
|
||
GetModelsRequest,
|
||
ModelsListResponse,
|
||
TestConnectionRequest,
|
||
ConnectionTestResponse,
|
||
} from '@/types/ai-config'
|
||
|
||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000'
|
||
const STORAGE_KEY_ACCESS = 'miaosi_access_token'
|
||
const STORAGE_KEY_REFRESH = 'miaosi_refresh_token'
|
||
|
||
// ==================== 类型定义 ====================
|
||
|
||
export type UserRole = 'brand' | 'agency' | 'creator'
|
||
|
||
export interface User {
|
||
id: string
|
||
email?: string
|
||
phone?: string
|
||
name: string
|
||
avatar?: string
|
||
role: UserRole
|
||
is_verified: boolean
|
||
brand_id?: string
|
||
agency_id?: string
|
||
creator_id?: string
|
||
tenant_id?: string
|
||
tenant_name?: string
|
||
}
|
||
|
||
export interface LoginRequest {
|
||
email?: string
|
||
phone?: string
|
||
password?: string
|
||
email_code?: string
|
||
}
|
||
|
||
export interface RegisterRequest {
|
||
email: string
|
||
phone?: string
|
||
password: string
|
||
name: string
|
||
role: UserRole
|
||
email_code: string
|
||
}
|
||
|
||
export interface SendEmailCodeRequest {
|
||
email: string
|
||
purpose: 'register' | 'login' | 'reset_password'
|
||
}
|
||
|
||
export interface SendEmailCodeResponse {
|
||
message: string
|
||
expires_in: number
|
||
}
|
||
|
||
export interface ResetPasswordRequest {
|
||
email: string
|
||
email_code: string
|
||
new_password: string
|
||
}
|
||
|
||
export interface LoginResponse {
|
||
access_token: string
|
||
refresh_token: string
|
||
token_type: string
|
||
expires_in: number
|
||
user: User
|
||
}
|
||
|
||
export interface RefreshTokenResponse {
|
||
access_token: string
|
||
token_type: string
|
||
expires_in: number
|
||
}
|
||
|
||
export interface UploadPolicyResponse {
|
||
x_tos_algorithm: string
|
||
x_tos_credential: string
|
||
x_tos_date: string
|
||
x_tos_signature: string
|
||
policy: string
|
||
host: string
|
||
dir: string
|
||
expire: number
|
||
max_size_mb: number
|
||
}
|
||
|
||
export interface FileUploadedResponse {
|
||
url: string
|
||
file_key: string
|
||
file_name: string
|
||
file_size: number
|
||
file_type: string
|
||
}
|
||
|
||
// ==================== 用户资料类型 ====================
|
||
|
||
export interface BrandProfileInfo {
|
||
id: string
|
||
name: string
|
||
logo?: string
|
||
description?: string
|
||
contact_name?: string
|
||
contact_phone?: string
|
||
contact_email?: string
|
||
}
|
||
|
||
export interface AgencyProfileInfo {
|
||
id: string
|
||
name: string
|
||
logo?: string
|
||
description?: string
|
||
contact_name?: string
|
||
contact_phone?: string
|
||
contact_email?: string
|
||
}
|
||
|
||
export interface CreatorProfileInfo {
|
||
id: string
|
||
name: string
|
||
avatar?: string
|
||
bio?: string
|
||
douyin_account?: string
|
||
xiaohongshu_account?: string
|
||
bilibili_account?: string
|
||
}
|
||
|
||
export interface ProfileResponse {
|
||
id: string
|
||
email?: string
|
||
phone?: string
|
||
name: string
|
||
avatar?: string
|
||
role: string
|
||
is_verified: boolean
|
||
created_at?: string
|
||
brand?: BrandProfileInfo
|
||
agency?: AgencyProfileInfo
|
||
creator?: CreatorProfileInfo
|
||
}
|
||
|
||
export interface ProfileUpdateRequest {
|
||
name?: string
|
||
avatar?: string
|
||
phone?: string
|
||
description?: string
|
||
contact_name?: string
|
||
contact_phone?: string
|
||
contact_email?: string
|
||
bio?: string
|
||
douyin_account?: string
|
||
xiaohongshu_account?: string
|
||
bilibili_account?: string
|
||
}
|
||
|
||
export interface ChangePasswordRequest {
|
||
old_password: string
|
||
new_password: string
|
||
}
|
||
|
||
// ==================== 消息类型 ====================
|
||
|
||
export interface MessageItem {
|
||
id: string
|
||
type: string
|
||
title: string
|
||
content: string
|
||
is_read: boolean
|
||
related_task_id?: string
|
||
related_project_id?: string
|
||
sender_name?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export interface MessageListResponse {
|
||
items: MessageItem[]
|
||
total: number
|
||
page: number
|
||
page_size: number
|
||
}
|
||
|
||
// ==================== Token 管理 ====================
|
||
|
||
function getAccessToken(): string | null {
|
||
if (typeof window === 'undefined') return null
|
||
return localStorage.getItem(STORAGE_KEY_ACCESS)
|
||
}
|
||
|
||
function getRefreshToken(): string | null {
|
||
if (typeof window === 'undefined') return null
|
||
return localStorage.getItem(STORAGE_KEY_REFRESH)
|
||
}
|
||
|
||
function setTokens(accessToken: string, refreshToken: string): void {
|
||
if (typeof window === 'undefined') return
|
||
localStorage.setItem(STORAGE_KEY_ACCESS, accessToken)
|
||
localStorage.setItem(STORAGE_KEY_REFRESH, refreshToken)
|
||
}
|
||
|
||
function clearTokens(): void {
|
||
if (typeof window === 'undefined') return
|
||
localStorage.removeItem(STORAGE_KEY_ACCESS)
|
||
localStorage.removeItem(STORAGE_KEY_REFRESH)
|
||
}
|
||
|
||
// ==================== API 客户端 ====================
|
||
|
||
class ApiClient {
|
||
private client: AxiosInstance
|
||
private tenantId: string = 'default'
|
||
private isRefreshing = false
|
||
private refreshSubscribers: Array<(token: string) => void> = []
|
||
|
||
constructor() {
|
||
this.client = axios.create({
|
||
baseURL: `${API_BASE_URL}/api/v1`,
|
||
timeout: 30000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
})
|
||
|
||
this.setupInterceptors()
|
||
}
|
||
|
||
private setupInterceptors() {
|
||
// 请求拦截器:添加 Token 和租户 ID
|
||
this.client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||
const token = getAccessToken()
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`
|
||
}
|
||
config.headers['X-Tenant-ID'] = this.tenantId
|
||
return config
|
||
})
|
||
|
||
// 响应拦截器:处理 401 错误
|
||
this.client.interceptors.response.use(
|
||
(response) => response,
|
||
async (error: AxiosError) => {
|
||
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }
|
||
|
||
// 如果是 401 错误且不是刷新 Token 的请求
|
||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||
if (this.isRefreshing) {
|
||
// 如果正在刷新,等待刷新完成后重试
|
||
return new Promise((resolve) => {
|
||
this.refreshSubscribers.push((token: string) => {
|
||
originalRequest.headers.Authorization = `Bearer ${token}`
|
||
resolve(this.client(originalRequest))
|
||
})
|
||
})
|
||
}
|
||
|
||
originalRequest._retry = true
|
||
this.isRefreshing = true
|
||
|
||
try {
|
||
const refreshToken = getRefreshToken()
|
||
if (!refreshToken) {
|
||
throw new Error('No refresh token')
|
||
}
|
||
|
||
const response = await axios.post<RefreshTokenResponse>(
|
||
`${API_BASE_URL}/api/v1/auth/refresh`,
|
||
{ refresh_token: refreshToken }
|
||
)
|
||
|
||
const newAccessToken = response.data.access_token
|
||
setTokens(newAccessToken, refreshToken)
|
||
|
||
// 通知所有等待的请求
|
||
this.refreshSubscribers.forEach((callback) => callback(newAccessToken))
|
||
this.refreshSubscribers = []
|
||
|
||
// 重试原请求
|
||
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`
|
||
return this.client(originalRequest)
|
||
} catch (refreshError) {
|
||
// 刷新失败,清除 Token 并跳转登录
|
||
clearTokens()
|
||
if (typeof window !== 'undefined') {
|
||
window.location.href = '/login'
|
||
}
|
||
return Promise.reject(refreshError)
|
||
} finally {
|
||
this.isRefreshing = false
|
||
}
|
||
}
|
||
|
||
const message = (error.response?.data as { detail?: string })?.detail || error.message || '请求失败'
|
||
return Promise.reject(new Error(message))
|
||
}
|
||
)
|
||
}
|
||
|
||
setTenantId(tenantId: string) {
|
||
this.tenantId = tenantId
|
||
}
|
||
|
||
// ==================== 认证 ====================
|
||
|
||
/**
|
||
* 发送邮箱验证码
|
||
*/
|
||
async sendEmailCode(data: SendEmailCodeRequest): Promise<SendEmailCodeResponse> {
|
||
const response = await this.client.post<SendEmailCodeResponse>('/auth/send-code', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 重置密码
|
||
*/
|
||
async resetPassword(data: ResetPasswordRequest): Promise<{ message: string }> {
|
||
const response = await this.client.post<{ message: string }>('/auth/reset-password', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 用户注册
|
||
*/
|
||
async register(data: RegisterRequest): Promise<LoginResponse> {
|
||
const response = await this.client.post<LoginResponse>('/auth/register', data)
|
||
setTokens(response.data.access_token, response.data.refresh_token)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 用户登录
|
||
*/
|
||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||
const response = await this.client.post<LoginResponse>('/auth/login', data)
|
||
setTokens(response.data.access_token, response.data.refresh_token)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 退出登录
|
||
*/
|
||
async logout(): Promise<void> {
|
||
try {
|
||
await this.client.post('/auth/logout')
|
||
} finally {
|
||
clearTokens()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新 Token
|
||
*/
|
||
async refreshToken(): Promise<RefreshTokenResponse> {
|
||
const refreshToken = getRefreshToken()
|
||
if (!refreshToken) {
|
||
throw new Error('No refresh token')
|
||
}
|
||
const response = await axios.post<RefreshTokenResponse>(
|
||
`${API_BASE_URL}/api/v1/auth/refresh`,
|
||
{ refresh_token: refreshToken }
|
||
)
|
||
setTokens(response.data.access_token, refreshToken)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 文件上传 ====================
|
||
|
||
/**
|
||
* 获取 TOS 上传凭证
|
||
*/
|
||
async getUploadPolicy(fileType: string = 'general'): Promise<UploadPolicyResponse> {
|
||
const response = await this.client.post<UploadPolicyResponse>('/upload/policy', {
|
||
file_type: fileType,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 文件上传完成回调
|
||
*/
|
||
async fileUploaded(fileKey: string, fileName: string, fileSize: number, fileType: string): Promise<FileUploadedResponse> {
|
||
const response = await this.client.post<FileUploadedResponse>('/upload/complete', {
|
||
file_key: fileKey,
|
||
file_name: fileName,
|
||
file_size: fileSize,
|
||
file_type: fileType,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 后端代理上传(绕过浏览器直传 TOS 的 CORS/代理问题)
|
||
*/
|
||
async proxyUpload(file: File, fileType: string = 'general', onProgress?: (pct: number) => void): Promise<FileUploadedResponse> {
|
||
const formData = new FormData()
|
||
formData.append('file', file)
|
||
formData.append('file_type', fileType)
|
||
const response = await this.client.post<FileUploadedResponse>('/upload/proxy', formData, {
|
||
timeout: 300000,
|
||
headers: { 'Content-Type': 'multipart/form-data' },
|
||
onUploadProgress: (e) => {
|
||
if (e.total && onProgress) onProgress(Math.round((e.loaded / e.total) * 100))
|
||
},
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 获取私有桶文件的预签名访问 URL
|
||
*/
|
||
async getSignedUrl(url: string, expire: number = 3600): Promise<string> {
|
||
const response = await this.client.get<{ signed_url: string; expire_seconds: number }>(
|
||
'/upload/sign-url',
|
||
{ params: { url, expire } }
|
||
)
|
||
return response.data.signed_url
|
||
}
|
||
|
||
// ==================== 视频审核 ====================
|
||
|
||
/**
|
||
* 提交视频审核
|
||
*/
|
||
async submitVideoReview(data: VideoReviewRequest): Promise<VideoReviewResponse> {
|
||
const response = await this.client.post<VideoReviewResponse>('/videos/review', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询审核进度
|
||
*/
|
||
async getReviewProgress(reviewId: string): Promise<ReviewProgressResponse> {
|
||
const response = await this.client.get<ReviewProgressResponse>(
|
||
`/videos/review/${reviewId}/progress`
|
||
)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询审核结果
|
||
*/
|
||
async getReviewResult(reviewId: string): Promise<ReviewResultResponse> {
|
||
const response = await this.client.get<ReviewResultResponse>(
|
||
`/videos/review/${reviewId}/result`
|
||
)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 审核任务 ====================
|
||
|
||
/**
|
||
* 创建任务(代理商操作)
|
||
*/
|
||
async createTask(data: TaskCreateRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>('/tasks', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询任务列表
|
||
*/
|
||
async listTasks(page: number = 1, pageSize: number = 20, stage?: TaskStage): Promise<TaskListResponse> {
|
||
const response = await this.client.get<TaskListResponse>('/tasks', {
|
||
params: { page, page_size: pageSize, stage },
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询待审核任务列表
|
||
*/
|
||
async listPendingReviews(page: number = 1, pageSize: number = 20): Promise<ReviewTaskListResponse> {
|
||
const response = await this.client.get<ReviewTaskListResponse>('/tasks/pending', {
|
||
params: { page, page_size: pageSize },
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询任务详情
|
||
*/
|
||
async getTask(taskId: string): Promise<TaskResponse> {
|
||
const response = await this.client.get<TaskResponse>(`/tasks/${taskId}`)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 上传/更新任务脚本
|
||
*/
|
||
async uploadTaskScript(taskId: string, payload: TaskScriptUploadRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/script`, payload)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 上传/更新任务视频
|
||
*/
|
||
async uploadTaskVideo(taskId: string, payload: TaskVideoUploadRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/video`, payload)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 审核脚本
|
||
*/
|
||
async reviewScript(taskId: string, data: TaskReviewRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/script/review`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 审核视频
|
||
*/
|
||
async reviewVideo(taskId: string, data: TaskReviewRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/video/review`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 提交申诉(达人操作)
|
||
*/
|
||
async submitAppeal(taskId: string, data: AppealRequest): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/appeal`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 增加申诉次数(代理商操作)
|
||
*/
|
||
async increaseAppealCount(taskId: string): Promise<TaskResponse> {
|
||
const response = await this.client.post<TaskResponse>(`/tasks/${taskId}/appeal-count`)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 项目 ====================
|
||
|
||
/**
|
||
* 创建项目(品牌方操作)
|
||
*/
|
||
async createProject(data: ProjectCreateRequest): Promise<ProjectResponse> {
|
||
const response = await this.client.post<ProjectResponse>('/projects', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询项目列表
|
||
*/
|
||
async listProjects(page: number = 1, pageSize: number = 20, status?: string): Promise<ProjectListResponse> {
|
||
const response = await this.client.get<ProjectListResponse>('/projects', {
|
||
params: { page, page_size: pageSize, status },
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询项目详情
|
||
*/
|
||
async getProject(projectId: string): Promise<ProjectResponse> {
|
||
const response = await this.client.get<ProjectResponse>(`/projects/${projectId}`)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 更新项目
|
||
*/
|
||
async updateProject(projectId: string, data: ProjectUpdateRequest): Promise<ProjectResponse> {
|
||
const response = await this.client.put<ProjectResponse>(`/projects/${projectId}`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 分配代理商到项目
|
||
*/
|
||
async assignAgencies(projectId: string, agencyIds: string[]): Promise<ProjectResponse> {
|
||
const response = await this.client.post<ProjectResponse>(`/projects/${projectId}/agencies`, {
|
||
agency_ids: agencyIds,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 从项目移除代理商
|
||
*/
|
||
async removeAgencyFromProject(projectId: string, agencyId: string): Promise<ProjectResponse> {
|
||
const response = await this.client.delete<ProjectResponse>(`/projects/${projectId}/agencies/${agencyId}`)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== Brief ====================
|
||
|
||
/**
|
||
* 获取项目 Brief
|
||
*/
|
||
async getBrief(projectId: string): Promise<BriefResponse> {
|
||
const response = await this.client.get<BriefResponse>(`/projects/${projectId}/brief`)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 创建项目 Brief
|
||
*/
|
||
async createBrief(projectId: string, data: BriefCreateRequest): Promise<BriefResponse> {
|
||
const response = await this.client.post<BriefResponse>(`/projects/${projectId}/brief`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 更新项目 Brief
|
||
*/
|
||
async updateBrief(projectId: string, data: BriefCreateRequest): Promise<BriefResponse> {
|
||
const response = await this.client.put<BriefResponse>(`/projects/${projectId}/brief`, data)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 组织关系 ====================
|
||
|
||
/**
|
||
* 品牌方:查询代理商列表
|
||
*/
|
||
async listBrandAgencies(): Promise<AgencyListResponse> {
|
||
const response = await this.client.get<AgencyListResponse>('/organizations/brand/agencies')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 品牌方:邀请代理商
|
||
*/
|
||
async inviteAgency(agencyId: string): Promise<void> {
|
||
await this.client.post('/organizations/brand/agencies', { agency_id: agencyId })
|
||
}
|
||
|
||
/**
|
||
* 品牌方:移除代理商
|
||
*/
|
||
async removeAgency(agencyId: string): Promise<void> {
|
||
await this.client.delete(`/organizations/brand/agencies/${agencyId}`)
|
||
}
|
||
|
||
/**
|
||
* 品牌方:更新代理商权限
|
||
*/
|
||
async updateAgencyPermission(agencyId: string, forcePassEnabled: boolean): Promise<void> {
|
||
await this.client.put(`/organizations/brand/agencies/${agencyId}/permission`, {
|
||
force_pass_enabled: forcePassEnabled,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 代理商:查询达人列表
|
||
*/
|
||
async listAgencyCreators(): Promise<CreatorListResponse> {
|
||
const response = await this.client.get<CreatorListResponse>('/organizations/agency/creators')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 代理商:邀请达人
|
||
*/
|
||
async inviteCreator(creatorId: string): Promise<void> {
|
||
await this.client.post('/organizations/agency/creators', { creator_id: creatorId })
|
||
}
|
||
|
||
/**
|
||
* 代理商:移除达人
|
||
*/
|
||
async removeCreator(creatorId: string): Promise<void> {
|
||
await this.client.delete(`/organizations/agency/creators/${creatorId}`)
|
||
}
|
||
|
||
/**
|
||
* 代理商:查询关联品牌方
|
||
*/
|
||
async listAgencyBrands(): Promise<BrandListResponse> {
|
||
const response = await this.client.get<BrandListResponse>('/organizations/agency/brands')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 搜索代理商
|
||
*/
|
||
async searchAgencies(keyword: string): Promise<AgencyListResponse> {
|
||
const response = await this.client.get<AgencyListResponse>('/organizations/search/agencies', {
|
||
params: { keyword },
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 搜索达人
|
||
*/
|
||
async searchCreators(keyword: string): Promise<CreatorListResponse> {
|
||
const response = await this.client.get<CreatorListResponse>('/organizations/search/creators', {
|
||
params: { keyword },
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 工作台统计 ====================
|
||
|
||
/**
|
||
* 达人工作台数据
|
||
*/
|
||
async getCreatorDashboard(): Promise<CreatorDashboard> {
|
||
const response = await this.client.get<CreatorDashboard>('/dashboard/creator')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 代理商工作台数据
|
||
*/
|
||
async getAgencyDashboard(): Promise<AgencyDashboard> {
|
||
const response = await this.client.get<AgencyDashboard>('/dashboard/agency')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 品牌方工作台数据
|
||
*/
|
||
async getBrandDashboard(): Promise<BrandDashboard> {
|
||
const response = await this.client.get<BrandDashboard>('/dashboard/brand')
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 脚本预审 ====================
|
||
|
||
/**
|
||
* 脚本预审(AI 审核)
|
||
*/
|
||
async reviewScriptContent(data: ScriptReviewRequest): Promise<ScriptReviewResponse> {
|
||
const response = await this.client.post<ScriptReviewResponse>('/scripts/review', data)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 规则管理 ====================
|
||
|
||
/**
|
||
* 查询违禁词列表
|
||
*/
|
||
async listForbiddenWords(category?: string): Promise<ForbiddenWordListResponse> {
|
||
const response = await this.client.get<ForbiddenWordListResponse>('/rules/forbidden-words', {
|
||
params: category ? { category } : undefined,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 添加违禁词
|
||
*/
|
||
async addForbiddenWord(data: ForbiddenWordCreate): Promise<ForbiddenWordResponse> {
|
||
const response = await this.client.post<ForbiddenWordResponse>('/rules/forbidden-words', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 删除违禁词
|
||
*/
|
||
async deleteForbiddenWord(wordId: string): Promise<void> {
|
||
await this.client.delete(`/rules/forbidden-words/${wordId}`)
|
||
}
|
||
|
||
/**
|
||
* 查询白名单
|
||
*/
|
||
async listWhitelist(brandId?: string): Promise<WhitelistListResponse> {
|
||
const response = await this.client.get<WhitelistListResponse>('/rules/whitelist', {
|
||
params: brandId ? { brand_id: brandId } : undefined,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 添加白名单
|
||
*/
|
||
async addToWhitelist(data: WhitelistCreate): Promise<WhitelistResponse> {
|
||
const response = await this.client.post<WhitelistResponse>('/rules/whitelist', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询竞品列表
|
||
*/
|
||
async listCompetitors(brandId?: string): Promise<CompetitorListResponse> {
|
||
const response = await this.client.get<CompetitorListResponse>('/rules/competitors', {
|
||
params: brandId ? { brand_id: brandId } : undefined,
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 添加竞品
|
||
*/
|
||
async addCompetitor(data: CompetitorCreate): Promise<CompetitorResponse> {
|
||
const response = await this.client.post<CompetitorResponse>('/rules/competitors', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 删除竞品
|
||
*/
|
||
async deleteCompetitor(competitorId: string): Promise<void> {
|
||
await this.client.delete(`/rules/competitors/${competitorId}`)
|
||
}
|
||
|
||
/**
|
||
* 查询所有平台规则
|
||
*/
|
||
async listPlatformRules(): Promise<PlatformListResponse> {
|
||
const response = await this.client.get<PlatformListResponse>('/rules/platforms')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询指定平台规则
|
||
*/
|
||
async getPlatformRules(platform: string): Promise<PlatformRuleResponse> {
|
||
const response = await this.client.get<PlatformRuleResponse>(`/rules/platforms/${platform}`)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 规则冲突检测
|
||
*/
|
||
async validateRules(data: RuleValidateRequest): Promise<RuleValidateResponse> {
|
||
const response = await this.client.post<RuleValidateResponse>('/rules/validate', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 上传文档并 AI 解析平台规则
|
||
*/
|
||
async parsePlatformRule(data: PlatformRuleParseRequest): Promise<PlatformRuleParseResponse> {
|
||
const response = await this.client.post<PlatformRuleParseResponse>('/rules/platform-rules/parse', data, {
|
||
timeout: 180000, // 3 分钟,视觉模型解析图片 PDF 较慢
|
||
})
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 确认/编辑平台规则
|
||
*/
|
||
async confirmPlatformRule(ruleId: string, data: PlatformRuleConfirmRequest): Promise<BrandPlatformRuleResponse> {
|
||
const response = await this.client.put<BrandPlatformRuleResponse>(`/rules/platform-rules/${ruleId}/confirm`, data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 查询品牌方平台规则列表
|
||
*/
|
||
async listBrandPlatformRules(params?: { brand_id?: string; platform?: string; status?: string }): Promise<BrandPlatformRuleListResponse> {
|
||
const response = await this.client.get<BrandPlatformRuleListResponse>('/rules/platform-rules', { params })
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 删除平台规则
|
||
*/
|
||
async deletePlatformRule(ruleId: string): Promise<void> {
|
||
await this.client.delete(`/rules/platform-rules/${ruleId}`)
|
||
}
|
||
|
||
// ==================== AI 配置 ====================
|
||
|
||
/**
|
||
* 获取 AI 配置
|
||
*/
|
||
async getAIConfig(): Promise<AIConfigResponse> {
|
||
const response = await this.client.get<AIConfigResponse>('/ai-config')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 更新 AI 配置
|
||
*/
|
||
async updateAIConfig(data: AIConfigUpdate): Promise<AIConfigResponse> {
|
||
const response = await this.client.put<AIConfigResponse>('/ai-config', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 获取可用模型列表
|
||
*/
|
||
async getAIModels(data: GetModelsRequest): Promise<ModelsListResponse> {
|
||
const response = await this.client.post<ModelsListResponse>('/ai-config/models', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 测试 AI 连接
|
||
*/
|
||
async testAIConnection(data: TestConnectionRequest): Promise<ConnectionTestResponse> {
|
||
const response = await this.client.post<ConnectionTestResponse>('/ai-config/test', data)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 用户资料 ====================
|
||
|
||
/**
|
||
* 获取当前用户资料
|
||
*/
|
||
async getProfile(): Promise<ProfileResponse> {
|
||
const response = await this.client.get<ProfileResponse>('/profile')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 更新用户资料
|
||
*/
|
||
async updateProfile(data: ProfileUpdateRequest): Promise<ProfileResponse> {
|
||
const response = await this.client.put<ProfileResponse>('/profile', data)
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 修改密码
|
||
*/
|
||
async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> {
|
||
const response = await this.client.put<{ message: string }>('/profile/password', data)
|
||
return response.data
|
||
}
|
||
|
||
// ==================== 消息/通知 ====================
|
||
|
||
/**
|
||
* 获取消息列表
|
||
*/
|
||
async getMessages(params?: { page?: number; page_size?: number; is_read?: boolean; type?: string }): Promise<MessageListResponse> {
|
||
const response = await this.client.get<MessageListResponse>('/messages', { params })
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 获取未读消息数
|
||
*/
|
||
async getUnreadCount(): Promise<{ count: number }> {
|
||
const response = await this.client.get<{ count: number }>('/messages/unread-count')
|
||
return response.data
|
||
}
|
||
|
||
/**
|
||
* 标记单条消息已读
|
||
*/
|
||
async markMessageAsRead(messageId: string): Promise<void> {
|
||
await this.client.put(`/messages/${messageId}/read`)
|
||
}
|
||
|
||
/**
|
||
* 标记所有消息已读
|
||
*/
|
||
async markAllMessagesAsRead(): Promise<void> {
|
||
await this.client.put('/messages/read-all')
|
||
}
|
||
|
||
// ==================== 健康检查 ====================
|
||
|
||
/**
|
||
* 健康检查
|
||
*/
|
||
async healthCheck(): Promise<{ status: string; version: string }> {
|
||
const response = await this.client.get('/health')
|
||
return response.data
|
||
}
|
||
}
|
||
|
||
// 单例导出
|
||
export const api = new ApiClient()
|
||
|
||
// 导出 Token 管理函数供其他模块使用
|
||
export { getAccessToken, getRefreshToken, setTokens, clearTokens }
|
||
|
||
export default api
|