Initial commit: 添加项目代码
This commit is contained in:
20
demo/frontend/src/api/analytics.js
Normal file
20
demo/frontend/src/api/analytics.js
Normal 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 }
|
||||
})
|
||||
}
|
||||
83
demo/frontend/src/api/auth.js
Normal file
83
demo/frontend/src/api/auth.js
Normal 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 })
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
88
demo/frontend/src/api/cleanup.js
Normal file
88
demo/frontend/src/api/cleanup.js
Normal 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
|
||||
38
demo/frontend/src/api/dashboard.js
Normal file
38
demo/frontend/src/api/dashboard.js
Normal 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 }
|
||||
})
|
||||
}
|
||||
247
demo/frontend/src/api/imageToVideo.js
Normal file
247
demo/frontend/src/api/imageToVideo.js
Normal 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
|
||||
46
demo/frontend/src/api/members.js
Normal file
46
demo/frontend/src/api/members.js
Normal 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 })
|
||||
}
|
||||
58
demo/frontend/src/api/novelComic.js
Normal file
58
demo/frontend/src/api/novelComic.js
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
63
demo/frontend/src/api/orders.js
Normal file
63
demo/frontend/src/api/orders.js
Normal 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}`)
|
||||
}
|
||||
82
demo/frontend/src/api/payments.js
Normal file
82
demo/frontend/src/api/payments.js
Normal 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 })
|
||||
}
|
||||
23
demo/frontend/src/api/points.js
Normal file
23
demo/frontend/src/api/points.js
Normal 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')
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
31
demo/frontend/src/api/promptOptimizer.js
Normal file
31
demo/frontend/src/api/promptOptimizer.js
Normal 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
|
||||
}
|
||||
|
||||
206
demo/frontend/src/api/request.js
Normal file
206
demo/frontend/src/api/request.js
Normal 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('收到401,Token已过期:', 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
|
||||
|
||||
// 检查响应数据是否是HTML(302重定向的结果)
|
||||
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
|
||||
56
demo/frontend/src/api/storyboardVideo.js
Normal file
56
demo/frontend/src/api/storyboardVideo.js
Normal 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 - 列数,默认3(2×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`)
|
||||
}
|
||||
25
demo/frontend/src/api/taskStatus.js
Normal file
25
demo/frontend/src/api/taskStatus.js
Normal 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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
179
demo/frontend/src/api/textToVideo.js
Normal file
179
demo/frontend/src/api/textToVideo.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
87
demo/frontend/src/api/userWorks.js
Normal file
87
demo/frontend/src/api/userWorks.js
Normal 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
91
demo/frontend/src/api/workflowVideo.js
Normal file
91
demo/frontend/src/api/workflowVideo.js
Normal 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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user