Initial commit: 添加项目代码

This commit is contained in:
2026-02-13 18:24:52 +08:00
commit 05d3cc539d
303 changed files with 97922 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import api from './request'
// 获取日活用户趋势数据
export const getDailyActiveUsersTrend = (year = '2024', granularity = 'monthly') => {
return api.get('/analytics/daily-active-users', {
params: { year, granularity }
})
}
// 获取用户活跃度概览
export const getUserActivityOverview = () => {
return api.get('/analytics/user-activity-overview')
}
// 获取用户活跃度热力图数据
export const getUserActivityHeatmap = (year = '2024') => {
return api.get('/analytics/user-activity-heatmap', {
params: { year }
})
}

View File

@@ -0,0 +1,83 @@
import api from './request'
// 认证相关API
// 注意:用户名密码登录已禁用,仅支持邮箱验证码登录
// 邮箱验证码登录(唯一登录方式)
export const loginWithEmail = (credentials) => {
return api.post('/auth/login/email', credentials)
}
// 向后兼容(已禁用)
export const login = (credentials) => {
return api.post('/auth/login', credentials)
}
export const register = (userData) => {
return api.post('/auth/register', userData)
}
export const logout = () => {
return api.post('/auth/logout')
}
export const getCurrentUser = () => {
return api.get('/auth/me')
}
// 修改当前登录用户密码
export const changePassword = (data) => {
return api.post('/auth/change-password', data)
}
// 用户相关API
export const getUsers = (params) => {
return api.get('/users', { params })
}
export const getUserById = (id) => {
return api.get(`/users/${id}`)
}
export const createUser = (userData) => {
return api.post('/users', userData)
}
export const updateUser = (id, userData) => {
return api.put(`/users/${id}`, userData)
}
export const deleteUser = (id) => {
return api.delete(`/users/${id}`)
}
// 检查用户名是否存在
export const checkUsernameExists = (username) => {
return api.get(`/public/users/exists/username`, {
params: { value: username }
})
}
// 检查邮箱是否存在
export const checkEmailExists = (email) => {
return api.get(`/public/users/exists/email`, {
params: { value: email }
})
}
// 发送邮箱验证码
export const sendEmailCode = (email) => {
return api.post('/verification/email/send', { email })
}
// 开发环境:设置验证码(用于开发测试)
export const setDevEmailCode = (email, code) => {
return api.post('/verification/email/dev-set', { email, code })
}

View File

@@ -0,0 +1,88 @@
// 任务清理API服务
import request from './request'
import { getApiBaseURL } from '@/utils/apiHelper'
export const cleanupApi = {
// 获取清理统计信息
getCleanupStats() {
return request({
url: '/cleanup/cleanup-stats',
method: 'GET'
})
},
// 执行完整清理
performFullCleanup() {
return request({
url: '/cleanup/full-cleanup',
method: 'POST'
})
},
// 清理指定用户任务
cleanupUserTasks(username) {
return request({
url: `/cleanup/user-tasks/${username}`,
method: 'POST'
})
},
// 获取清理统计信息原始fetch方式用于测试
async getCleanupStatsRaw() {
try {
const response = await fetch(`${getApiBaseURL()}/cleanup/cleanup-stats`)
if (response.ok) {
return await response.json()
} else {
throw new Error('获取统计信息失败')
}
} catch (error) {
console.error('获取统计信息失败:', error)
throw error
}
},
// 执行完整清理原始fetch方式用于测试
async performFullCleanupRaw() {
try {
const response = await fetch(`${getApiBaseURL()}/cleanup/full-cleanup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
if (response.ok) {
return await response.json()
} else {
throw new Error('执行完整清理失败')
}
} catch (error) {
console.error('执行完整清理失败:', error)
throw error
}
},
// 清理指定用户任务原始fetch方式用于测试
async cleanupUserTasksRaw(username) {
try {
const response = await fetch(`/api/cleanup/user-tasks/${username}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
if (response.ok) {
return await response.json()
} else {
throw new Error('清理用户任务失败')
}
} catch (error) {
console.error('清理用户任务失败:', error)
throw error
}
}
}
export default cleanupApi

View File

@@ -0,0 +1,38 @@
import api from './request'
// 获取仪表盘概览数据
export const getDashboardOverview = () => {
return api.get('/dashboard/overview')
}
// 获取月度收入趋势数据
export const getMonthlyRevenue = (year = '2024') => {
return api.get('/dashboard/monthly-revenue', {
params: { year }
})
}
// 获取用户转化率数据
export const getConversionRate = (year = null) => {
const params = year ? { year } : {}
return api.get('/dashboard/conversion-rate', { params })
}
// 获取最近订单数据
export const getRecentOrders = (limit = 10) => {
return api.get('/dashboard/recent-orders', {
params: { limit }
})
}
// 获取系统状态
export const getSystemStatus = () => {
return api.get('/dashboard/system-status')
}
// 获取日活用户趋势数据
export const getDailyActiveUsersTrend = (year = '2024', granularity = 'monthly') => {
return api.get('/analytics/daily-active-users', {
params: { year, granularity }
})
}

View File

@@ -0,0 +1,247 @@
import request from './request'
/**
* 图生视频API服务
*/
export const imageToVideoApi = {
/**
* 创建图生视频任务
* @param {Object} params - 任务参数
* @param {File} params.firstFrame - 首帧图片
* @param {File} params.lastFrame - 尾帧图片(可选)
* @param {string} params.prompt - 描述文字
* @param {string} params.aspectRatio - 视频比例
* @param {number} params.duration - 视频时长
* @param {boolean} params.hdMode - 是否高清模式
* @returns {Promise} API响应
*/
createTask(params) {
// 参数验证
if (!params) {
throw new Error('参数不能为空')
}
if (!params.firstFrame) {
throw new Error('首帧图片不能为空')
}
if (!params.prompt || params.prompt.trim() === '') {
throw new Error('描述文字不能为空')
}
if (!params.aspectRatio) {
throw new Error('视频比例不能为空')
}
if (!params.duration || params.duration < 1 || params.duration > 60) {
throw new Error('视频时长必须在1-60秒之间')
}
const formData = new FormData()
// 添加必填参数
formData.append('firstFrame', params.firstFrame)
formData.append('prompt', params.prompt.trim())
formData.append('aspectRatio', params.aspectRatio)
formData.append('duration', params.duration.toString())
formData.append('hdMode', params.hdMode.toString())
// 添加可选参数
if (params.lastFrame) {
formData.append('lastFrame', params.lastFrame)
}
return request({
url: '/image-to-video/create',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
},
/**
* 通过图片URL创建图生视频任务用于"做同款"功能)
* @param {Object} params - 任务参数
* @param {string} params.imageUrl - 图片URL
* @param {string} params.prompt - 描述文字
* @param {string} params.aspectRatio - 视频比例
* @param {number} params.duration - 视频时长
* @param {boolean} params.hdMode - 是否高清模式
* @returns {Promise} API响应
*/
createTaskByUrl(params) {
if (!params) {
throw new Error('参数不能为空')
}
if (!params.imageUrl) {
throw new Error('图片URL不能为空')
}
if (!params.prompt || params.prompt.trim() === '') {
throw new Error('描述文字不能为空')
}
return request({
url: '/image-to-video/create-by-url',
method: 'POST',
data: {
imageUrl: params.imageUrl,
prompt: params.prompt.trim(),
aspectRatio: params.aspectRatio || '16:9',
duration: params.duration || 5,
hdMode: params.hdMode || false
}
})
},
/**
* 获取用户任务列表
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Promise} API响应
*/
getTasks(page = 0, size = 10) {
return request({
url: '/image-to-video/tasks',
method: 'GET',
params: { page, size }
})
},
/**
* 获取任务详情
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
getTaskDetail(taskId) {
return request({
url: `/image-to-video/tasks/${taskId}`,
method: 'GET'
})
},
/**
* 获取任务状态
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
getTaskStatus(taskId) {
return request({
url: `/image-to-video/tasks/${taskId}/status`,
method: 'GET'
})
},
/**
* 删除任务
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
deleteTask(taskId) {
return request({
url: `/image-to-video/tasks/${taskId}`,
method: 'DELETE'
})
},
/**
* 重试失败的任务
* 复用原task_id和已上传的图片重新提交至外部API
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
retryTask(taskId) {
return request({
url: `/image-to-video/tasks/${taskId}/retry`,
method: 'POST'
})
},
/**
* 轮询任务状态
* @param {string} taskId - 任务ID
* @param {Function} onProgress - 进度回调
* @param {Function} onComplete - 完成回调
* @param {Function} onError - 错误回调
* @returns {Function} 停止轮询的函数
*/
pollTaskStatus(taskId, onProgress, onComplete, onError) {
let isPolling = true
let pollCount = 0
const maxPolls = 30 // 最大轮询次数1小时每2分钟一次
const poll = async () => {
if (!isPolling || pollCount >= maxPolls) {
if (pollCount >= maxPolls) {
onError && onError(new Error('任务超时'))
}
return
}
try {
const response = await request({
url: `/image-to-video/tasks/${taskId}/status`,
method: 'GET'
})
// 检查响应是否有效
if (!response || !response.data || !response.data.success) {
onError && onError(new Error('获取任务状态失败'))
isPolling = false
return
}
const taskData = response.data.data
// 检查taskData是否有效
if (!taskData || !taskData.status) {
onError && onError(new Error('无效的任务数据'))
isPolling = false
return
}
if (taskData.status === 'COMPLETED') {
onComplete && onComplete(taskData)
isPolling = false
return
}
if (taskData.status === 'FAILED' || taskData.status === 'CANCELLED') {
console.error('任务失败:', {
taskId: taskId,
status: taskData.status,
errorMessage: taskData.errorMessage,
pollCount: pollCount
})
onError && onError(new Error(taskData.errorMessage || '任务失败'))
isPolling = false
return
}
// 调用进度回调
onProgress && onProgress({
status: taskData.status,
progress: taskData.progress || 0,
resultUrl: taskData.resultUrl
})
pollCount++
// 继续轮询
setTimeout(poll, 120000) // 每2分钟轮询一次
} catch (error) {
console.error('轮询任务状态失败:', error)
onError && onError(error)
isPolling = false
}
}
// 开始轮询
poll()
// 返回停止轮询的函数
return () => {
isPolling = false
}
}
}
export default imageToVideoApi

View File

@@ -0,0 +1,46 @@
import api from './request'
// 获取会员列表
export const getMembers = (params) => {
return api.get('/members', { params })
}
// 更新会员信息
export const updateMember = (id, data) => {
return api.put(`/members/${id}`, data)
}
// 删除会员
export const deleteMember = (id) => {
return api.delete(`/members/${id}`)
}
// 批量删除会员
export const deleteMembers = (ids) => {
return api.delete('/members/batch', { data: { ids } })
}
// 获取会员详情
export const getMemberDetail = (id) => {
return api.get(`/members/${id}`)
}
// 获取所有会员等级配置
export const getMembershipLevels = () => {
return api.get('/members/levels')
}
// 更新会员等级配置
export const updateMembershipLevel = (id, data) => {
return api.put(`/members/levels/${id}`, data)
}
// 封禁/解封会员
export const toggleBanMember = (id, isActive) => {
return api.put(`/members/${id}/ban`, { isActive })
}
// 设置用户角色(仅超级管理员可用)
export const setUserRole = (id, role) => {
return api.put(`/members/${id}/role`, { role })
}

View File

@@ -0,0 +1,58 @@
import request from './request'
/**
* 小说漫剧生成 API异步模式
* 1. 提交任务 → 立即返回 taskId
* 2. 轮询状态 → 直到 SUCCESS/FAIL
*/
// 创建小说漫剧生成任务(异步提交,秒级返回 taskId
export const createNovelComicTask = (params) => {
if (!params.storyBackground || params.storyBackground.trim() === '') {
throw new Error('故事背景不能为空')
}
if (!params.storyScript || params.storyScript.trim() === '') {
throw new Error('故事文案不能为空')
}
const formData = new FormData()
formData.append('theme', params.theme?.trim() || '')
formData.append('storyBackground', params.storyBackground.trim())
formData.append('storyScript', params.storyScript.trim())
// 背景音乐文件(可选)
if (params.musicFile) {
formData.append('backgroundMusic', params.musicFile)
}
return request({
url: '/novel-comic/create',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 60000 // 提交本身很快上传音乐文件可能较慢1 分钟足够
})
}
// 查询任务执行状态(轮询用)
export const getNovelComicTaskStatus = (executeId) => {
return request({
url: `/novel-comic/status/${executeId}`,
method: 'GET',
timeout: 15000
})
}
// 获取用户的小说漫剧历史列表
export const getNovelComicHistory = (params = {}) => {
return request({
url: '/novel-comic/history',
method: 'GET',
params: {
page: params.page || 0,
size: params.size || 20
}
})
}

View File

@@ -0,0 +1,63 @@
import api from './request'
// 订单相关API
export const getOrders = (params) => {
return api.get('/orders', { params })
}
export const getOrderById = (id) => {
return api.get(`/orders/${id}`)
}
export const createOrder = (orderData) => {
return api.post('/orders/create', orderData)
}
export const updateOrderStatus = (id, status, notes) => {
return api.post(`/orders/${id}/status`, {
status,
notes
})
}
export const cancelOrder = (id, reason) => {
return api.post(`/orders/${id}/cancel`, {
reason
})
}
export const shipOrder = (id, trackingNumber) => {
return api.post(`/orders/${id}/ship`, {
trackingNumber
})
}
export const completeOrder = (id) => {
return api.post(`/orders/${id}/complete`)
}
export const createOrderPayment = (id, paymentMethod) => {
return api.post(`/orders/${id}/pay`, {
paymentMethod
})
}
// 管理员订单API使用普通订单接口后端会根据用户角色返回相应数据
export const getAdminOrders = (params) => {
return api.get('/orders', { params })
}
// 订单统计API
export const getOrderStats = () => {
return api.get('/orders/stats')
}
// 批量删除订单
export const deleteOrders = (orderIds) => {
return api.delete('/orders/batch', { data: orderIds })
}
// 删除单个订单
export const deleteOrder = (id) => {
return api.delete(`/orders/${id}`)
}

View File

@@ -0,0 +1,82 @@
import api from './request'
// 支付相关API
export const getPayments = (params) => {
return api.get('/payments', { params })
}
export const getPaymentById = (id) => {
return api.get(`/payments/${id}`)
}
export const createPayment = (paymentData) => {
return api.post('/payments/create', paymentData)
}
export const createTestPayment = (paymentData) => {
return api.post('/payments/create-test', paymentData)
}
export const updatePaymentStatus = (id, status) => {
return api.put(`/payments/${id}/status`, { status })
}
export const confirmPaymentSuccess = (id, externalTransactionId) => {
return api.post(`/payments/${id}/success`, {
externalTransactionId
})
}
export const confirmPaymentFailure = (id, failureReason) => {
return api.post(`/payments/${id}/failure`, {
failureReason
})
}
// 测试支付完成API
export const testPaymentComplete = (id) => {
return api.post(`/payments/${id}/test-complete`)
}
// 支付宝支付API
export const createAlipayPayment = (paymentData) => {
return api.post(`/payments/alipay/create`, paymentData)
}
export const handleAlipayCallback = (params) => {
return api.post('/payments/alipay/callback', params)
}
// 噜噜支付彩虹易支付API
export const createLuluPayment = (paymentData) => {
return api.post('/payments/lulupay/create', paymentData)
}
// PayPal支付API
export const createPayPalPayment = (paymentData) => {
return api.post('/payment/paypal/create', paymentData)
}
export const getPayPalPaymentStatus = (paymentId) => {
return api.get(`/payment/paypal/status/${paymentId}`)
}
// 支付统计API
export const getPaymentStats = () => {
return api.get('/payments/stats')
}
// 获取用户订阅信息
export const getUserSubscriptionInfo = () => {
return api.get('/payments/subscription/info')
}
// 删除单个支付记录
export const deletePayment = (id) => {
return api.delete(`/payments/${id}`)
}
// 批量删除支付记录
export const deletePayments = (paymentIds) => {
return api.delete('/payments/batch', { data: paymentIds })
}

View File

@@ -0,0 +1,23 @@
import api from './request'
// 积分相关API
export const getPointsInfo = () => {
return api.get('/points/info')
}
export const getPointsHistory = (params = {}) => {
return api.get('/points/history', { params })
}
export const getPointsFreezeRecords = () => {
return api.get('/points/freeze-records')
}
export const processExpiredRecords = () => {
return api.post('/points/process-expired')
}

View File

@@ -0,0 +1,31 @@
import api from './request'
/**
* 优化提示词
* @param {string} prompt - 原始提示词
* @param {string} type - 优化类型: 'text-to-video' | 'image-to-video' | 'storyboard'
* @returns {Promise} API响应
*/
export const optimizePrompt = async (prompt, type = 'text-to-video') => {
// 参数验证
if (!prompt || !prompt.trim()) {
throw new Error('提示词不能为空')
}
if (prompt.length > 2000) {
throw new Error('提示词过长请控制在2000字符以内')
}
// 设置较长的超时时间30秒因为AI优化可能需要较长时间
return api.post('/prompt/optimize', {
prompt: prompt.trim(),
type
}, {
timeout: 30000
})
}
export default {
optimizePrompt
}

View File

@@ -0,0 +1,206 @@
import axios from 'axios'
import { message } from 'ant-design-vue'
import router from '@/router'
import { getApiBaseURL } from '@/utils/apiHelper'
// 创建axios实例
// 自动检测:如果通过 Nginx 访问(包含 ngrok使用相对路径否则使用完整 URL
const api = axios.create({
baseURL: getApiBaseURL(),
timeout: 900000, // 增加到15分钟适应视频生成时间
withCredentials: true,
maxRedirects: 0, // 不自动跟随重定向手动处理302
headers: {
'Content-Type': 'application/json'
},
validateStatus: function (status) {
// 允许所有状态码包括302让拦截器处理
return status >= 200 && status < 600
}
})
// 请求拦截器
api.interceptors.request.use(
(config) => {
// 登录相关的接口不需要添加token
const loginUrls = [
'/auth/login',
'/auth/login/email',
'/auth/register',
'/verification/email/send',
'/verification/email/verify',
'/verification/email/dev-set',
'/public/'
]
// 检查当前请求是否是登录相关接口
const isLoginRequest = loginUrls.some(url => config.url.includes(url))
if (!isLoginRequest) {
// 非登录请求才添加Authorization头
const token = localStorage.getItem('token')
if (token && token !== 'null' && token.trim() !== '') {
config.headers.Authorization = `Bearer ${token}`
// 打印token前30字符用于调试
console.log('请求拦截器添加Authorization头token前30字符:', token.substring(0, 30), '请求URL:', config.url)
} else {
console.warn('请求拦截器未找到有效的token请求URL:', config.url)
}
} else {
console.log('请求拦截器登录相关请求不添加token:', config.url)
}
return config
},
(error) => {
console.error('请求拦截器错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
(response) => {
// 检查是否是HTML响应可能是302重定向的结果
if (response.data && typeof response.data === 'string' && response.data.trim().startsWith('<!DOCTYPE')) {
console.error('收到HTML响应可能是认证失败:', response.config.url)
// 只有非登录请求才清除token并跳转
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
if (!isLoginRequest) {
// 清除无效的token并跳转到欢迎页
localStorage.removeItem('token')
localStorage.removeItem('user')
// 避免重复跳转
if (router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
message.warning('登录已过期,请重新登录')
router.push('/')
}
}
// 返回错误,让调用方知道这是认证失败
return Promise.reject(new Error('认证失败收到HTML响应'))
}
// 检查401未授权Token过期
if (response.status === 401) {
console.error('收到401Token已过期:', response.config.url)
// #region agent log
fetch('http://127.0.0.1:7243/ingest/7d01a34a-7181-4a5e-9962-62bb50420571',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'request.js:401interceptor',message:'401 response intercepted',data:{url:response.config.url,currentPath:router.currentRoute?.value?.path},timestamp:Date.now(),sessionId:'debug-session',hypothesisId:'H-C'})}).catch(()=>{});
// #endregion
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
// /auth/me 请求的401不跳转只静默清除token用于初始化时检查token有效性
const isAuthMeRequest = response.config.url.includes('/auth/me')
if (!isLoginRequest) {
localStorage.removeItem('token')
localStorage.removeItem('user')
// /auth/me 请求不显示提示也不跳转
if (!isAuthMeRequest) {
message.warning('登录已过期,请重新登录')
router.push('/')
}
}
return Promise.reject(new Error('认证失败Token已过期'))
}
// 检查302重定向
if (response.status === 302) {
console.error('收到302重定向可能是认证失败:', response.config.url)
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => response.config.url.includes(url))
if (!isLoginRequest) {
localStorage.removeItem('token')
localStorage.removeItem('user')
message.warning('登录已过期,请重新登录')
router.push('/')
}
return Promise.reject(new Error('认证失败302重定向'))
}
// 直接返回response让调用方处理data
return response
},
(error) => {
if (error.response) {
const { status, data } = error.response
// 检查响应数据是否是HTML302重定向的结果
if (data && typeof data === 'string' && data.trim().startsWith('<!DOCTYPE')) {
console.error('收到HTML响应可能是302重定向:', error.config.url)
// 只有非登录请求才清除token并跳转
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => error.config.url.includes(url))
if (!isLoginRequest) {
localStorage.removeItem('token')
localStorage.removeItem('user')
if (router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
message.warning('登录已过期,请重新登录')
router.push('/')
}
}
return Promise.reject(error)
}
switch (status) {
case 401:
case 302:
// 只有非登录请求才清除token并跳转
const loginUrls = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest = loginUrls.some(url => error.config.url.includes(url))
// /auth/me 请求的401不跳转只静默清除token
const isAuthMeRequest = error.config.url.includes('/auth/me')
if (!isLoginRequest) {
// 302也可能是认证失败导致的
localStorage.removeItem('token')
localStorage.removeItem('user')
// /auth/me 请求不显示提示也不跳转
if (!isAuthMeRequest && router.currentRoute.value.path !== '/' && router.currentRoute.value.path !== '/login') {
message.warning('登录已过期,请重新登录')
router.push('/')
}
}
break
case 403:
// 403可能是权限不足或CORS问题
// 如果是登录请求的403不要显示"权限不足",而是显示具体错误信息
const loginUrls403 = ['/auth/login', '/auth/login/email', '/auth/register', '/verification/', '/public/']
const isLoginRequest403 = loginUrls403.some(url => error.config.url.includes(url))
if (!isLoginRequest403) {
message.error('权限不足')
} else {
// 登录请求的403显示具体错误或网络问题
message.error(data?.message || '请求失败,请检查网络连接')
}
break
case 404:
message.error('请求的资源不存在')
break
case 500:
message.error('服务器内部错误')
break
default:
message.error(data?.message || '请求失败')
}
} else if (error.request) {
message.error('网络错误,请检查网络连接')
} else {
message.error('请求配置错误')
}
return Promise.reject(error)
}
)
export default api

View File

@@ -0,0 +1,56 @@
import api from './request'
/**
* 创建分镜视频任务
*/
export const createStoryboardTask = async (data) => {
return api.post('/storyboard-video/create', data)
}
/**
* 直接使用上传的分镜图创建视频任务(跳过分镜图生成)
* @param {object} data - 包含 storyboardImage, prompt, aspectRatio, hdMode, duration, referenceImages
*/
export const createVideoDirectTask = async (data) => {
return api.post('/storyboard-video/create-video-direct', data)
}
/**
* 获取任务详情
*/
export const getStoryboardTask = async (taskId) => {
return api.get(`/storyboard-video/task/${taskId}`)
}
/**
* 获取用户任务列表
*/
export const getUserStoryboardTasks = async (page = 0, size = 10) => {
return api.get('/storyboard-video/tasks', { params: { page, size } })
}
/**
* 开始生成视频(从分镜图生成视频)
* @param {string} taskId - 任务ID
* @param {object} params - 视频参数duration, aspectRatio, hdMode
*/
export const startVideoGeneration = async (taskId, params = {}) => {
return api.post(`/storyboard-video/task/${taskId}/start-video`, params)
}
/**
* 拼接多张图片为六宫格2×3
* @param {Array<string>} images - Base64图片数组
* @param {number} cols - 列数默认32×3布局
*/
export const mergeImagesToGrid = async (images, cols = 3) => {
return api.post('/image-grid/merge', { images, cols })
}
/**
* 重试失败的分镜视频任务
* @param {string} taskId - 任务ID
*/
export const retryStoryboardTask = async (taskId) => {
return api.post(`/storyboard-video/task/${taskId}/retry`)
}

View File

@@ -0,0 +1,25 @@
import api from './request'
export const taskStatusApi = {
// 获取任务状态
getTaskStatus(taskId) {
return api.get(`/task-status/${taskId}`)
},
// 获取用户的所有任务状态
getUserTaskStatuses(username) {
return api.get(`/task-status/user/${username}`)
},
// 手动触发轮询(管理员功能)
triggerPolling() {
return api.post('/task-status/poll')
},
// 获取所有任务记录(管理员功能)
getAllTaskRecords(params) {
return api.get('/task-status/admin/all', { params })
}
}

View File

@@ -0,0 +1,179 @@
import request from './request'
/**
* 文生视频API服务
*/
export const textToVideoApi = {
/**
* 创建文生视频任务
* @param {Object} params - 任务参数
* @param {string} params.prompt - 文本描述
* @param {string} params.aspectRatio - 视频比例
* @param {number} params.duration - 视频时长
* @param {boolean} params.hdMode - 是否高清模式
* @returns {Promise} API响应
*/
createTask(params) {
// 参数验证
if (!params) {
throw new Error('参数不能为空')
}
if (!params.prompt || params.prompt.trim() === '') {
throw new Error('文本描述不能为空')
}
if (!params.aspectRatio) {
throw new Error('视频比例不能为空')
}
if (!params.duration || params.duration < 1 || params.duration > 60) {
throw new Error('视频时长必须在1-60秒之间')
}
return request({
url: '/text-to-video/create',
method: 'POST',
data: {
prompt: params.prompt.trim(),
aspectRatio: params.aspectRatio,
duration: params.duration,
hdMode: params.hdMode
}
})
},
/**
* 获取用户的所有文生视频任务
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Promise} API响应
*/
getTasks(page = 0, size = 10) {
return request({
url: '/text-to-video/tasks',
method: 'GET',
params: {
page,
size
}
})
},
/**
* 获取单个文生视频任务详情
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
getTaskDetail(taskId) {
return request({
url: `/text-to-video/tasks/${taskId}`,
method: 'GET'
})
},
/**
* 获取文生视频任务状态
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
getTaskStatus(taskId) {
return request({
url: `/text-to-video/tasks/${taskId}/status`,
method: 'GET'
})
},
/**
* 重试失败的任务
* 复用原task_id重新提交至外部API
* @param {string} taskId - 任务ID
* @returns {Promise} API响应
*/
retryTask(taskId) {
return request({
url: `/text-to-video/tasks/${taskId}/retry`,
method: 'POST'
})
},
/**
* 轮询任务状态
* @param {string} taskId - 任务ID
* @param {Function} onProgress - 进度回调
* @param {Function} onComplete - 完成回调
* @param {Function} onError - 错误回调
* @returns {Function} 停止轮询的函数
*/
pollTaskStatus(taskId, onProgress, onComplete, onError) {
let isPolling = true
let pollCount = 0
const maxPolls = 30 // 最大轮询次数1小时每2分钟一次
const poll = async () => {
if (!isPolling || pollCount >= maxPolls) {
if (pollCount >= maxPolls) {
onError && onError(new Error('任务超时'))
}
return
}
try {
const response = await request({
url: `/text-to-video/tasks/${taskId}/status`,
method: 'GET'
})
// 检查响应是否有效
if (!response || !response.data || !response.data.success) {
onError && onError(new Error('获取任务状态失败'))
isPolling = false
return
}
const taskData = response.data.data
// 检查taskData是否有效
if (!taskData || !taskData.status) {
onError && onError(new Error('无效的任务数据'))
isPolling = false
return
}
if (taskData.status === 'COMPLETED') {
onComplete && onComplete(taskData)
isPolling = false
return
}
if (taskData.status === 'FAILED' || taskData.status === 'CANCELLED') {
onError && onError(new Error(taskData.errorMessage || '任务失败'))
isPolling = false
return
}
// 调用进度回调
onProgress && onProgress({
status: taskData.status,
progress: taskData.progress || 0,
resultUrl: taskData.resultUrl
})
pollCount++
// 继续轮询
setTimeout(poll, 120000) // 每2分钟轮询一次
} catch (error) {
console.error('轮询任务状态失败:', error)
onError && onError(error)
isPolling = false
}
}
// 开始轮询
poll()
// 返回停止轮询的函数
return () => {
isPolling = false
}
}
}

View File

@@ -0,0 +1,87 @@
import api from './request'
// 获取我的作品列表
export const getMyWorks = (params = {}) => {
return api.get('/works/my-works', {
params: {
page: params.page || 0,
size: params.size || 10,
includeProcessing: params.includeProcessing !== false, // 默认包含正在处理中的作品
workType: params.workType || null // 按作品类型筛选
}
})
}
// 按类型获取我的作品(用于历史记录)
export const getMyWorksByType = (workType, params = {}) => {
return api.get('/works/my-works', {
params: {
page: params.page || 0,
size: params.size || 1000,
includeProcessing: true,
workType: workType // TEXT_TO_VIDEO, IMAGE_TO_VIDEO, STORYBOARD_VIDEO, STORYBOARD_IMAGE
}
})
}
// 获取正在进行中的作品
export const getProcessingWorks = () => {
return api.get('/works/processing')
}
// 获取作品详情
export const getWorkDetail = (workId) => {
return api.get(`/works/${workId}`)
}
// 删除作品
export const deleteWork = (workId) => {
return api.delete(`/works/${workId}`)
}
// 批量删除作品
export const batchDeleteWorks = (workIds) => {
return api.post('/works/batch-delete', {
workIds: workIds
})
}
// 更新作品信息
export const updateWork = (workId, data) => {
return api.put(`/works/${workId}`, data)
}
// 获取作品统计信息
export const getWorkStats = () => {
return api.get('/works/stats')
}
// 记录下载(增加下载次数)
export const recordDownload = (workId) => {
return api.post(`/works/${workId}/download`)
}
// 获取作品文件下载URL
export const getWorkFileUrl = (workId, download = false) => {
// 构建URL直接返回完整路径用于浏览器打开
const baseUrl = import.meta.env.VITE_API_BASE_URL || '/api'
// 去掉 /api 前缀,因为 nginx 会自动转发
let url = `${baseUrl}/works/${workId}/file`
// 添加 download 参数(可选)
if (download) {
url += '?download=true'
}
return url
}

View File

@@ -0,0 +1,91 @@
import request from './request'
/**
* 优质工作流视频 API
*/
// ==================== 公开接口 ====================
/**
* 获取已启用的工作流视频列表(无需认证)
*/
export function getActiveWorkflowVideos() {
return request({
url: '/workflow-videos',
method: 'GET'
})
}
// ==================== 管理员接口 ====================
/**
* 管理后台 - 获取所有工作流视频(分页)
*/
export function getAdminWorkflowVideos(page = 0, size = 20) {
return request({
url: '/admin/workflow-videos',
method: 'GET',
params: { page, size }
})
}
/**
* 管理后台 - 创建工作流视频
*/
export function createWorkflowVideo(data) {
return request({
url: '/admin/workflow-videos',
method: 'POST',
data
})
}
/**
* 管理后台 - 更新工作流视频
*/
export function updateWorkflowVideo(id, data) {
return request({
url: `/admin/workflow-videos/${id}`,
method: 'PUT',
data
})
}
/**
* 管理后台 - 删除工作流视频
*/
export function deleteWorkflowVideo(id) {
return request({
url: `/admin/workflow-videos/${id}`,
method: 'DELETE'
})
}
/**
* 管理后台 - 上传视频文件
*/
export function uploadWorkflowVideoFile(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/admin/workflow-videos/upload-video',
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
data: formData,
timeout: 600000 // 10分钟超时视频文件可能较大
})
}
/**
* 管理后台 - 上传缩略图
*/
export function uploadWorkflowThumbnail(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/admin/workflow-videos/upload-thumbnail',
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
data: formData
})
}