feat: 补全后端 API 并对齐前后端类型
- 后端新增: Project CRUD / Brief CRUD / 组织关系管理 / 工作台统计 / SSE 推送 / 认证依赖注入 - 后端完善: 任务 API 全流程(创建/审核/申诉) + Task Service + Task Schema - 前端修复: login 页面 localStorage key 错误 (miaosi_auth -> miaosi_user) - 前端对齐: types/task.ts 与后端 TaskStage/TaskResponse 完全对齐 - 前端新增: project/brief/organization/dashboard 类型定义 - 前端补全: api.ts 新增 30+ API 方法覆盖所有后端接口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
23835ee790
commit
a32102f583
@@ -67,7 +67,7 @@ function LoginForm() {
|
||||
const result = await login({ email, password })
|
||||
|
||||
if (result.success) {
|
||||
const stored = localStorage.getItem('miaosi_auth')
|
||||
const stored = localStorage.getItem('miaosi_user')
|
||||
if (stored) {
|
||||
const user = JSON.parse(stored)
|
||||
switch (user.role) {
|
||||
|
||||
+265
-1
@@ -14,7 +14,32 @@ import type {
|
||||
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'
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000'
|
||||
const STORAGE_KEY_ACCESS = 'miaosi_access_token'
|
||||
@@ -305,11 +330,29 @@ class ApiClient {
|
||||
|
||||
// ==================== 审核任务 ====================
|
||||
|
||||
/**
|
||||
* 创建任务(代理商操作)
|
||||
*/
|
||||
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): Promise<TaskListResponse> {
|
||||
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
|
||||
@@ -339,6 +382,227 @@ class ApiClient {
|
||||
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
|
||||
}
|
||||
|
||||
// ==================== 健康检查 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Brief 相关类型定义
|
||||
* 与后端 BriefResponse 对齐
|
||||
*/
|
||||
|
||||
export interface BriefAttachment {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
export interface SellingPoint {
|
||||
content: string
|
||||
required: boolean
|
||||
}
|
||||
|
||||
export interface BlacklistWord {
|
||||
word: string
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface BriefResponse {
|
||||
id: string
|
||||
project_id: string
|
||||
project_name?: string | null
|
||||
file_url?: string | null
|
||||
file_name?: string | null
|
||||
selling_points?: SellingPoint[] | null
|
||||
blacklist_words?: BlacklistWord[] | null
|
||||
competitors?: string[] | null
|
||||
brand_tone?: string | null
|
||||
min_duration?: number | null
|
||||
max_duration?: number | null
|
||||
other_requirements?: string | null
|
||||
attachments?: BriefAttachment[] | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface BriefCreateRequest {
|
||||
file_url?: string
|
||||
file_name?: string
|
||||
selling_points?: SellingPoint[]
|
||||
blacklist_words?: BlacklistWord[]
|
||||
competitors?: string[]
|
||||
brand_tone?: string
|
||||
min_duration?: number
|
||||
max_duration?: number
|
||||
other_requirements?: string
|
||||
attachments?: BriefAttachment[]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 工作台统计类型定义
|
||||
* 与后端 Dashboard schemas 对齐
|
||||
*/
|
||||
|
||||
export interface ReviewCount {
|
||||
script: number
|
||||
video: number
|
||||
}
|
||||
|
||||
export interface CreatorDashboard {
|
||||
total_tasks: number
|
||||
pending_script: number
|
||||
pending_video: number
|
||||
in_review: number
|
||||
completed: number
|
||||
rejected: number
|
||||
}
|
||||
|
||||
export interface AgencyDashboard {
|
||||
pending_review: ReviewCount
|
||||
pending_appeal: number
|
||||
today_passed: ReviewCount
|
||||
in_progress: ReviewCount
|
||||
total_creators: number
|
||||
total_tasks: number
|
||||
}
|
||||
|
||||
export interface BrandDashboard {
|
||||
total_projects: number
|
||||
active_projects: number
|
||||
pending_review: ReviewCount
|
||||
total_agencies: number
|
||||
total_tasks: number
|
||||
completed_tasks: number
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 组织关系类型定义
|
||||
* 与后端 Organization schemas 对齐
|
||||
*/
|
||||
|
||||
export interface BrandSummary {
|
||||
id: string
|
||||
name: string
|
||||
logo?: string | null
|
||||
contact_name?: string | null
|
||||
}
|
||||
|
||||
export interface AgencyDetail {
|
||||
id: string
|
||||
name: string
|
||||
logo?: string | null
|
||||
contact_name?: string | null
|
||||
force_pass_enabled: boolean
|
||||
}
|
||||
|
||||
export interface CreatorDetail {
|
||||
id: string
|
||||
name: string
|
||||
avatar?: string | null
|
||||
douyin_account?: string | null
|
||||
xiaohongshu_account?: string | null
|
||||
bilibili_account?: string | null
|
||||
}
|
||||
|
||||
export interface BrandListResponse {
|
||||
items: BrandSummary[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface AgencyListResponse {
|
||||
items: AgencyDetail[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface CreatorListResponse {
|
||||
items: CreatorDetail[]
|
||||
total: number
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 项目相关类型定义
|
||||
* 与后端 ProjectResponse 对齐
|
||||
*/
|
||||
|
||||
export interface AgencySummary {
|
||||
id: string
|
||||
name: string
|
||||
logo?: string | null
|
||||
}
|
||||
|
||||
export interface ProjectResponse {
|
||||
id: string
|
||||
name: string
|
||||
description?: string | null
|
||||
brand_id: string
|
||||
brand_name?: string | null
|
||||
status: string
|
||||
start_date?: string | null
|
||||
deadline?: string | null
|
||||
agencies: AgencySummary[]
|
||||
task_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ProjectListResponse {
|
||||
items: ProjectResponse[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface ProjectCreateRequest {
|
||||
name: string
|
||||
description?: string
|
||||
start_date?: string
|
||||
deadline?: string
|
||||
agency_ids?: string[]
|
||||
}
|
||||
|
||||
export interface ProjectUpdateRequest {
|
||||
name?: string
|
||||
description?: string
|
||||
start_date?: string
|
||||
deadline?: string
|
||||
status?: 'active' | 'completed' | 'archived'
|
||||
}
|
||||
+143
-16
@@ -1,22 +1,110 @@
|
||||
export type ApiTaskStatus =
|
||||
| 'pending'
|
||||
| 'processing'
|
||||
/**
|
||||
* 任务相关类型定义
|
||||
* 与后端 TaskStage/TaskStatus/TaskResponse 对齐
|
||||
*/
|
||||
|
||||
// 任务阶段(对应后端 TaskStage)
|
||||
export type TaskStage =
|
||||
| 'script_upload'
|
||||
| 'script_ai_review'
|
||||
| 'script_agency_review'
|
||||
| 'script_brand_review'
|
||||
| 'video_upload'
|
||||
| 'video_ai_review'
|
||||
| 'video_agency_review'
|
||||
| 'video_brand_review'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'approved'
|
||||
| 'rejected'
|
||||
|
||||
// 审核状态(对应后端 TaskStatus)
|
||||
export type TaskStatus =
|
||||
| 'pending'
|
||||
| 'processing'
|
||||
| 'passed'
|
||||
| 'rejected'
|
||||
| 'force_passed'
|
||||
|
||||
// 关联信息
|
||||
export interface ProjectInfo {
|
||||
id: string
|
||||
name: string
|
||||
brand_name?: string | null
|
||||
}
|
||||
|
||||
export interface AgencyInfo {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface CreatorInfo {
|
||||
id: string
|
||||
name: string
|
||||
avatar?: string | null
|
||||
}
|
||||
|
||||
// AI 审核结果
|
||||
export interface AIReviewResult {
|
||||
score: number
|
||||
violations: Array<{
|
||||
type: string
|
||||
content: string
|
||||
severity: string
|
||||
suggestion: string
|
||||
timestamp?: number
|
||||
source?: string
|
||||
}>
|
||||
soft_warnings: Array<{
|
||||
type: string
|
||||
content: string
|
||||
suggestion: string
|
||||
}>
|
||||
summary?: string
|
||||
}
|
||||
|
||||
// 任务响应(对应后端 TaskResponse)
|
||||
export interface TaskResponse {
|
||||
task_id: string
|
||||
video_url?: string | null
|
||||
script_content?: string | null
|
||||
id: string
|
||||
name: string
|
||||
sequence: number
|
||||
stage: TaskStage
|
||||
|
||||
// 关联
|
||||
project: ProjectInfo
|
||||
agency: AgencyInfo
|
||||
creator: CreatorInfo
|
||||
|
||||
// 脚本信息
|
||||
script_file_url?: string | null
|
||||
has_script: boolean
|
||||
has_video: boolean
|
||||
platform: string
|
||||
creator_id: string
|
||||
status: ApiTaskStatus
|
||||
script_file_name?: string | null
|
||||
script_uploaded_at?: string | null
|
||||
script_ai_score?: number | null
|
||||
script_ai_result?: AIReviewResult | null
|
||||
script_agency_status?: TaskStatus | null
|
||||
script_agency_comment?: string | null
|
||||
script_brand_status?: TaskStatus | null
|
||||
script_brand_comment?: string | null
|
||||
|
||||
// 视频信息
|
||||
video_file_url?: string | null
|
||||
video_file_name?: string | null
|
||||
video_duration?: number | null
|
||||
video_thumbnail_url?: string | null
|
||||
video_uploaded_at?: string | null
|
||||
video_ai_score?: number | null
|
||||
video_ai_result?: AIReviewResult | null
|
||||
video_agency_status?: TaskStatus | null
|
||||
video_agency_comment?: string | null
|
||||
video_brand_status?: TaskStatus | null
|
||||
video_brand_comment?: string | null
|
||||
|
||||
// 申诉
|
||||
appeal_count: number
|
||||
is_appeal: boolean
|
||||
appeal_reason?: string | null
|
||||
|
||||
// 时间
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface TaskListResponse {
|
||||
@@ -26,11 +114,50 @@ export interface TaskListResponse {
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface TaskSummary {
|
||||
id: string
|
||||
name: string
|
||||
stage: TaskStage
|
||||
creator_name: string
|
||||
creator_avatar?: string | null
|
||||
project_name: string
|
||||
is_appeal: boolean
|
||||
appeal_reason?: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ReviewTaskListResponse {
|
||||
items: TaskSummary[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
// 请求类型
|
||||
export interface TaskCreateRequest {
|
||||
project_id: string
|
||||
creator_id: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface TaskScriptUploadRequest {
|
||||
script_content?: string
|
||||
script_file_url?: string
|
||||
file_url: string
|
||||
file_name: string
|
||||
}
|
||||
|
||||
export interface TaskVideoUploadRequest {
|
||||
video_url: string
|
||||
file_url: string
|
||||
file_name: string
|
||||
duration?: number
|
||||
thumbnail_url?: string
|
||||
}
|
||||
|
||||
export interface TaskReviewRequest {
|
||||
action: 'pass' | 'reject' | 'force_pass'
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface AppealRequest {
|
||||
reason: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user