feat: 优化
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import createClientAxios from '@gold/api/axios/client'
|
||||
import { refreshToken } from '@/api/auth'
|
||||
import { clearUserInfoCache } from '@/api/userinfo'
|
||||
import router from '@/router'
|
||||
import tokenManager from '@gold/utils/token-manager'
|
||||
|
||||
/**
|
||||
* 处理401/403错误 - 清理凭证并跳转登录页
|
||||
*/
|
||||
const handleAuthError = (error) => {
|
||||
tokenManager.clearTokens()
|
||||
clearUserInfoCache()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建HTTP客户端实例
|
||||
@@ -16,8 +27,8 @@ export function createHttpClient(options = {}) {
|
||||
baseURL: '/',
|
||||
timeout: 180000,
|
||||
refreshTokenFn: refreshToken,
|
||||
on401: on401 || ((error) => router.push('/login')),
|
||||
on403: on403 || ((error) => router.push('/login')),
|
||||
on401: on401 || handleAuthError,
|
||||
on403: on403 || handleAuthError,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ export function getLipSyncTask(taskId) {
|
||||
* 识别已上传的视频
|
||||
*/
|
||||
export async function identifyUploadedVideo(videoFile) {
|
||||
try {
|
||||
const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId)
|
||||
if (urlRes.code !== 0 || !urlRes.data) {
|
||||
throw new Error(urlRes.msg || '获取播放链接失败')
|
||||
@@ -98,9 +97,6 @@ export async function identifyUploadedVideo(videoFile) {
|
||||
|
||||
const identifyData = await performFaceIdentification(urlRes.data)
|
||||
return buildIdentifyResponse(videoFile.id, urlRes.data, identifyData, false)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,8 +105,7 @@ export async function identifyUploadedVideo(videoFile) {
|
||||
export async function uploadAndIdentifyVideo(file) {
|
||||
const coverBase64 = await extractVideoCoverOptional(file)
|
||||
|
||||
try {
|
||||
const uploadRes = await MaterialService.uploadFile(file, 'video', coverBase64, null, null)
|
||||
const uploadRes = await MaterialService.uploadFile(file, 'digital_human', coverBase64, null, null)
|
||||
if (uploadRes.code !== 0) {
|
||||
throw new Error(uploadRes.msg || '上传失败')
|
||||
}
|
||||
@@ -124,8 +119,5 @@ export async function uploadAndIdentifyVideo(file) {
|
||||
|
||||
const identifyData = await performFaceIdentification(urlRes.data)
|
||||
return buildIdentifyResponse(fileId, urlRes.data, identifyData, true)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ const fetchVideoList = async () => {
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
fileCategory: 'video',
|
||||
fileCategory: 'digital_human',
|
||||
fileName: searchKeyword.value.trim() || undefined
|
||||
}
|
||||
|
||||
@@ -239,32 +239,18 @@ const formatDuration = (seconds) => {
|
||||
const formatFileSize = (bytes) => {
|
||||
if (!bytes) return '0 B'
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let size = bytes
|
||||
let unitIndex = 0
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
return `${size.toFixed(1)} ${units[unitIndex]}`
|
||||
const index = Math.floor(Math.log2(bytes) / 10)
|
||||
const size = bytes / Math.pow(1024, index)
|
||||
return `${size.toFixed(1)} ${units[index]}`
|
||||
}
|
||||
|
||||
const getVideoPreviewUrl = (video) => {
|
||||
if (video.coverBase64) {
|
||||
if (!video.coverBase64.startsWith('data:')) {
|
||||
return `data:image/jpeg;base64,${video.coverBase64}`
|
||||
return video.coverBase64.startsWith('data:')
|
||||
? video.coverBase64
|
||||
: `data:image/jpeg;base64,${video.coverBase64}`
|
||||
}
|
||||
return video.coverBase64
|
||||
}
|
||||
|
||||
if (video.previewUrl) {
|
||||
return video.previewUrl
|
||||
}
|
||||
|
||||
if (video.coverUrl) {
|
||||
return video.coverUrl
|
||||
}
|
||||
|
||||
return defaultCover
|
||||
return video.previewUrl || video.coverUrl || defaultCover
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
|
||||
@@ -7,11 +7,43 @@ import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { MaterialService } from '@/api/material'
|
||||
|
||||
/**
|
||||
* 获取视频时长(秒)
|
||||
* @param {File} file - 视频文件对象
|
||||
* @returns {Promise<number|null>} 时长(秒)
|
||||
*/
|
||||
function getVideoDuration(file) {
|
||||
return new Promise((resolve) => {
|
||||
if (!file.type.startsWith('video/')) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
|
||||
const video = document.createElement('video')
|
||||
video.preload = 'metadata'
|
||||
video.muted = true
|
||||
|
||||
video.onloadedmetadata = function() {
|
||||
const duration = Math.round(video.duration)
|
||||
URL.revokeObjectURL(video.src)
|
||||
resolve(duration)
|
||||
}
|
||||
|
||||
video.onerror = function() {
|
||||
URL.revokeObjectURL(video.src)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
video.src = URL.createObjectURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} UploadOptions
|
||||
* @property {string} fileCategory - 文件分类(video/voice/audio/image)
|
||||
* @property {string} fileCategory - 文件分类(video/voice/audio/image/digital_human)
|
||||
* @property {number|null} groupId - 分组编号(可选,仅素材库模块使用)
|
||||
* @property {string|null} coverBase64 - 封面base64(可选)
|
||||
* @property {number|null} duration - 视频时长(可选,视频文件自动获取)
|
||||
* @property {Function} onProgress - 进度回调(可选)
|
||||
* @property {Function} onStart - 开始回调(可选)
|
||||
* @property {Function} onSuccess - 成功回调(可选)
|
||||
@@ -94,6 +126,7 @@ export function useUpload() {
|
||||
fileCategory,
|
||||
groupId = null,
|
||||
coverBase64 = null,
|
||||
duration: inputDuration,
|
||||
onProgress,
|
||||
onStart,
|
||||
onSuccess,
|
||||
@@ -108,7 +141,13 @@ export function useUpload() {
|
||||
state.progress = 0
|
||||
|
||||
// 通知开始
|
||||
onStart && onStart()
|
||||
onStart?.()
|
||||
|
||||
// 获取视频时长(如果是视频文件且未提供时长)
|
||||
let duration = inputDuration
|
||||
if (duration === undefined && file.type.startsWith('video/')) {
|
||||
duration = await getVideoDuration(file)
|
||||
}
|
||||
|
||||
// 第一步:获取预签名URL
|
||||
const presignedData = await MaterialService.getPresignedUrl({
|
||||
@@ -121,7 +160,7 @@ export function useUpload() {
|
||||
// 第二步:直传文件到OSS
|
||||
await uploadToOSS(file, presignedData.data, (progress) => {
|
||||
state.progress = progress
|
||||
onProgress && onProgress(progress)
|
||||
onProgress?.(progress)
|
||||
})
|
||||
|
||||
// 第三步:确认上传并保存记录
|
||||
@@ -133,7 +172,7 @@ export function useUpload() {
|
||||
fileType: file.type,
|
||||
groupId,
|
||||
coverBase64,
|
||||
duration: file.type.startsWith('video/') ? null : undefined
|
||||
duration
|
||||
})
|
||||
|
||||
state.uploading = false
|
||||
@@ -142,14 +181,14 @@ export function useUpload() {
|
||||
|
||||
const fileId = completeData.data?.infraFileId || completeData.data?.userFileId
|
||||
const fileUrl = presignedData.data.presignedUrl
|
||||
onSuccess && onSuccess(fileId, fileUrl)
|
||||
onSuccess?.(fileId, fileUrl)
|
||||
|
||||
return fileId
|
||||
} catch (error) {
|
||||
state.uploading = false
|
||||
state.status = 'error'
|
||||
state.error = error.message || '上传失败'
|
||||
onError && onError(error)
|
||||
onError?.(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { CloudUploadOutlined, CrownFilled } from '@ant-design/icons-vue'
|
||||
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -242,7 +242,6 @@ const {
|
||||
canGenerate,
|
||||
maxTextLength,
|
||||
textareaPlaceholder,
|
||||
audioDurationSec,
|
||||
|
||||
// Pipeline 状态(单一状态源)
|
||||
pipelineState,
|
||||
@@ -284,9 +283,6 @@ const handleFileSelectWrapper = (e: Event) => {
|
||||
controller.handleFileSelect(e)
|
||||
}
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
import { onMounted } from 'vue'
|
||||
onMounted(async () => {
|
||||
await voiceStore.refresh()
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@ public enum TikFileCategoryEnum implements ArrayValuable<Integer> {
|
||||
GENERATE(2, "generate", "生成集"),
|
||||
AUDIO(3, "audio", "配音集"),
|
||||
MIX(4, "mix", "混剪集"),
|
||||
VOICE(5, "voice", "声音集");
|
||||
VOICE(5, "voice", "声音集"),
|
||||
DIGITAL_HUMAN(6, "digital_human", "数字人素材");
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TikFileCategoryEnum::getCode).toArray(Integer[]::new);
|
||||
|
||||
|
||||
@@ -77,24 +77,14 @@ public class TikOssInitServiceImpl implements TikOssInitService {
|
||||
/**
|
||||
* OSS路径信息
|
||||
*/
|
||||
private static class OssPathInfo {
|
||||
final String ossRootPath;
|
||||
final String videoPath;
|
||||
final String generatePath;
|
||||
final String audioPath;
|
||||
final String mixPath;
|
||||
final String voicePath;
|
||||
|
||||
OssPathInfo(String ossRootPath, String videoPath, String generatePath,
|
||||
String audioPath, String mixPath, String voicePath) {
|
||||
this.ossRootPath = ossRootPath;
|
||||
this.videoPath = videoPath;
|
||||
this.generatePath = generatePath;
|
||||
this.audioPath = audioPath;
|
||||
this.mixPath = mixPath;
|
||||
this.voicePath = voicePath;
|
||||
}
|
||||
}
|
||||
private record OssPathInfo(
|
||||
String ossRootPath,
|
||||
String videoPath,
|
||||
String generatePath,
|
||||
String audioPath,
|
||||
String mixPath,
|
||||
String voicePath
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 构建OSS路径信息
|
||||
@@ -176,6 +166,7 @@ public class TikOssInitServiceImpl implements TikOssInitService {
|
||||
case "audio" -> ossInit.getAudioPath();
|
||||
case "mix" -> ossInit.getMixPath();
|
||||
case "voice" -> ossInit.getVoicePath();
|
||||
case "digital_human" -> ossInit.getOssRootPath() + "/digital_human";
|
||||
default -> throw exception(OSS_INIT_FAILED, "不支持的文件分类:" + fileCategory);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user