From 9388f7d75b57978decd2858cf30da5c37c76331b Mon Sep 17 00:00:00 2001
From: shenaowei <450702724@qq.com>
Date: Tue, 24 Feb 2026 21:41:05 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../app/web-gold/src/components/TopNav.vue | 105 +-----------------
.../web-gold/src/components/VoiceSelector.vue | 3 +-
.../app/web-gold/src/composables/useTTS.js | 32 ++++--
frontend/app/web-gold/src/router/index.js | 47 +++++---
.../kling/hooks/pipeline/useSimplePipeline.ts | 60 ++++++----
.../kling/hooks/useDigitalHumanGeneration.ts | 38 ++-----
.../views/kling/hooks/useVoiceGeneration.ts | 1 -
7 files changed, 108 insertions(+), 178 deletions(-)
diff --git a/frontend/app/web-gold/src/components/TopNav.vue b/frontend/app/web-gold/src/components/TopNav.vue
index 66166ec059..48d4dc17f8 100644
--- a/frontend/app/web-gold/src/components/TopNav.vue
+++ b/frontend/app/web-gold/src/components/TopNav.vue
@@ -1,60 +1,37 @@
-
diff --git a/frontend/app/web-gold/src/components/VoiceSelector.vue b/frontend/app/web-gold/src/components/VoiceSelector.vue
index 4cd9f64ee9..cc3474729b 100644
--- a/frontend/app/web-gold/src/components/VoiceSelector.vue
+++ b/frontend/app/web-gold/src/components/VoiceSelector.vue
@@ -188,7 +188,8 @@ const handlePlayVoiceSample = (voice) => {
},
(error) => {
console.error('音频播放失败', error)
- }
+ },
+ { autoPlay: false } // 禁用自动播放,由 APlayer 控制
)
}
diff --git a/frontend/app/web-gold/src/composables/useTTS.js b/frontend/app/web-gold/src/composables/useTTS.js
index 4a01745d00..cf383a62f6 100644
--- a/frontend/app/web-gold/src/composables/useTTS.js
+++ b/frontend/app/web-gold/src/composables/useTTS.js
@@ -215,8 +215,10 @@ export function useTTS(options = {}) {
* @param {Object} voice - 音色对象
* @param {Function} onSuccess - 成功回调
* @param {Function} onError - 错误回调
+ * @param {Object} options - 选项
+ * @param {boolean} options.autoPlay - 是否自动播放(默认 true)
*/
- async function playVoiceSample(voice, onSuccess, onError) {
+ async function playVoiceSample(voice, onSuccess, onError, options = { autoPlay: true }) {
if (!voice) return
if (previewLoadingVoiceId.value === voice.id || playingPreviewVoiceId.value === voice.id) {
return
@@ -237,7 +239,11 @@ export function useTTS(options = {}) {
const cachedAudio = previewAudioCache.get(cacheKey)
if (cachedAudio) {
- playCachedAudio(cachedAudio, resetPreviewState)
+ if (options.autoPlay !== false) {
+ playCachedAudio(cachedAudio, resetPreviewState)
+ } else {
+ resetPreviewState()
+ }
onSuccess && onSuccess(cachedAudio)
return
}
@@ -260,19 +266,23 @@ export function useTTS(options = {}) {
if (res.data?.audioUrl) {
resetPreviewState()
- playAudioPreview(res.data.audioUrl, {
- revokeOnEnd: true,
- onEnded: function() {
- URL.revokeObjectURL(res.data.audioUrl)
- }
- })
+ if (options.autoPlay !== false) {
+ playAudioPreview(res.data.audioUrl, {
+ revokeOnEnd: true,
+ onEnded: function() {
+ URL.revokeObjectURL(res.data.audioUrl)
+ }
+ })
+ }
onSuccess?.(res.data)
} else if (res.data?.audioBase64) {
const audioData = await decodeAndCacheBase64(res.data.audioBase64, res.data.format, cacheKey)
resetPreviewState()
- playCachedAudio(audioData, function() {
- URL.revokeObjectURL(audioData.objectUrl)
- })
+ if (options.autoPlay !== false) {
+ playCachedAudio(audioData, function() {
+ URL.revokeObjectURL(audioData.objectUrl)
+ })
+ }
onSuccess?.(audioData)
} else {
message.error('试听失败')
diff --git a/frontend/app/web-gold/src/router/index.js b/frontend/app/web-gold/src/router/index.js
index cc2e6b5ab1..983d9bafb0 100644
--- a/frontend/app/web-gold/src/router/index.js
+++ b/frontend/app/web-gold/src/router/index.js
@@ -10,7 +10,7 @@ const navConfig = [
order: 1,
items: [
{ name: '对标分析', path: 'content-style/benchmark', icon: 'grid', component: () => import('../views/content-style/Benchmark.vue') },
- { name: '文案创作', path: 'content-style/copywriting', icon: 'text', component: () => import('../views/content-style/Copywriting.vue') },
+ // { name: '文案创作', path: 'content-style/copywriting', icon: 'text', component: () => import('../views/content-style/Copywriting.vue') },
{ name: '热点趋势', path: 'trends/forecast', icon: 'text', component: () => import('../views/trends/Forecast.vue') },
{ name: '智能体', path: 'agents', icon: 'robot', component: () => import('../views/agents/Agents.vue') },
]
@@ -32,14 +32,14 @@ const navConfig = [
{ name: '任务中心', path: 'system/task-management/:type', icon: 'video', component: () => import('../views/system/task-management/layout/TaskLayout.vue'), requiresAuth: true, params: { type: 'mix-task' } },
]
},
- {
- group: '系统',
- order: 4,
- requiresAuth: true,
- items: [
- { name: '风格设置', path: 'system/style-settings', icon: 'text', component: () => import('../views/system/style-settings/index.vue') },
- ]
- }
+ // {
+ // group: '系统',
+ // order: 4,
+ // requiresAuth: true,
+ // items: [
+ // { name: '风格设置', path: 'system/style-settings', icon: 'text', component: () => import('../views/system/style-settings/index.vue') },
+ // ]
+ // }
]
// 导航图标定义
@@ -144,23 +144,34 @@ const router = createRouter({
routes,
})
+// 白名单路由(无需登录)
+const WHITE_LIST = ['/login']
+
// 路由守卫
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
- const authenToken = tokenManager.getAccessToken()
+ const token = tokenManager.getAccessToken()
- if (to.meta.requiresAuth && !authenToken) {
+ // 1. 白名单路由直接放行
+ if (WHITE_LIST.includes(to.path)) {
+ // 已登录访问登录页 → 跳转首页
+ if (token) {
+ next({ path: '/', replace: true })
+ return
+ }
+ next()
+ return
+ }
+
+ // 2. 非白名单路由,必须有 token
+ if (!token) {
next({ path: '/login', query: { redirect: to.fullPath } })
return
}
- if (to.path === '/login' && authenToken) {
- next({ path: '/content-style/benchmark', replace: true })
- return
- }
-
- if (authenToken && !userStore.isLoggedIn) {
- userStore.fetchUserInfo()
+ // 3. 有 token 但未加载用户信息 → 加载
+ if (!userStore.isLoggedIn) {
+ await userStore.fetchUserInfo()
}
next()
diff --git a/frontend/app/web-gold/src/views/kling/hooks/pipeline/useSimplePipeline.ts b/frontend/app/web-gold/src/views/kling/hooks/pipeline/useSimplePipeline.ts
index 48e50fd0d8..547f9914f5 100644
--- a/frontend/app/web-gold/src/views/kling/hooks/pipeline/useSimplePipeline.ts
+++ b/frontend/app/web-gold/src/views/kling/hooks/pipeline/useSimplePipeline.ts
@@ -113,8 +113,17 @@ export function useSimplePipeline(options: PipelineOptions) {
* 运行完整流程(到 ready 状态)
*/
async function run(params: PipelineParams): Promise {
- // 重置状态
- reset()
+ // 重置上下文数据,但保持状态在即将开始工作的状态
+ context.value = { ...INITIAL_CONTEXT }
+ error.value = null
+ history.value = ['idle']
+
+ // 立即设置忙碌状态,让 UI 显示 loading
+ // 根据是否有上传文件决定初始状态
+ const initialState: PipelineState = params.videoFile && !params.selectedVideo
+ ? 'uploading'
+ : 'recognizing'
+ setState(initialState)
try {
// 保存参数到上下文
@@ -126,20 +135,28 @@ export function useSimplePipeline(options: PipelineOptions) {
// 步骤1: 上传视频(如果是上传模式)
if (params.videoFile && !params.selectedVideo) {
- const fileId = await executeStep('uploading', () =>
- options.uploadVideo(params.videoFile!)
- )
- context.value.videoFileId = fileId
+ try {
+ const fileId = await options.uploadVideo(params.videoFile!)
+ context.value.videoFileId = fileId
+ } catch (err) {
+ setError(err as Error)
+ throw err
+ }
} else if (params.selectedVideo) {
context.value.videoFileId = params.selectedVideo.fileId
}
// 步骤2: 识别人脸
- const recognizeData = params.selectedVideo
- ? await options.recognizeFromLibrary(params.selectedVideo)
- : await options.recognizeUploaded(context.value.videoFileId!)
-
- await executeStep('recognizing', async () => recognizeData)
+ setState('recognizing')
+ let recognizeData
+ try {
+ recognizeData = params.selectedVideo
+ ? await options.recognizeFromLibrary(params.selectedVideo)
+ : await options.recognizeUploaded(context.value.videoFileId!)
+ } catch (err) {
+ setError(err as Error)
+ throw err
+ }
context.value.sessionId = recognizeData.sessionId
context.value.faceId = recognizeData.faceId
@@ -148,9 +165,14 @@ export function useSimplePipeline(options: PipelineOptions) {
context.value.videoDurationMs = recognizeData.duration || 0
// 步骤3: 生成音频
- const audioData = await executeStep('generating', () =>
- options.generateAudio(params.text, params.voice, params.speechRate)
- )
+ setState('generating')
+ let audioData
+ try {
+ audioData = await options.generateAudio(params.text, params.voice, params.speechRate)
+ } catch (err) {
+ setError(err as Error)
+ throw err
+ }
context.value.audioBase64 = audioData.audioBase64
context.value.audioFormat = audioData.format || 'mp3'
@@ -160,17 +182,17 @@ export function useSimplePipeline(options: PipelineOptions) {
setState('validating')
const videoDurationMs = context.value.videoDurationMs ?? 0
if (context.value.audioDurationMs > videoDurationMs) {
- throw new Error(
- `校验失败:音频时长(${(context.value.audioDurationMs / 1000).toFixed(1)}秒) 超过人脸时长(${(videoDurationMs / 1000).toFixed(1)}秒)`
- )
+ const errorMsg = `校验失败:音频时长(${(context.value.audioDurationMs / 1000).toFixed(1)}秒) 超过人脸时长(${(videoDurationMs / 1000).toFixed(1)}秒)`
+ setError(new Error(errorMsg))
+ return
}
context.value.validationPassed = true
// 到达 ready 状态
setState('ready')
- } catch (err) {
- // 错误已在 executeStep 中处理
+ } catch {
+ // 错误已在各步骤中处理
}
}
diff --git a/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts b/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
index dd87b331ed..e9e2c107e6 100644
--- a/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
+++ b/frontend/app/web-gold/src/views/kling/hooks/useDigitalHumanGeneration.ts
@@ -12,7 +12,7 @@ import type {
IdentifyResult,
Video,
} from '../types/identify-face'
-import { identifyUploadedVideo } from '@/api/kling'
+import { identifyUploadedVideo, uploadAndIdentifyVideo } from '@/api/kling'
import { useUpload } from '@/composables/useUpload'
export function useDigitalHumanGeneration() {
@@ -90,6 +90,7 @@ export function useDigitalHumanGeneration() {
}
if (hasSelectedVideo) {
+ // 从素材库选择:调用识别接口
const res = await identifyUploadedVideo(hasSelectedVideo) as {
success: boolean;
data: { sessionId: string; faceId: string | null; startTime: number; endTime: number }
@@ -100,35 +101,18 @@ export function useDigitalHumanGeneration() {
identifyResult.value.faceStartTime = res.data.startTime || 0
identifyResult.value.faceEndTime = res.data.endTime || 0
} else {
+ // 上传新视频:使用 uploadAndIdentifyVideo 完成上传+识别
const file = hasUploadFile!
- let coverBase64 = null
- try {
- const { extractVideoCover } = await import('@/utils/video-cover')
- const cover = await extractVideoCover(file, { maxWidth: 800, quality: 0.8 })
- coverBase64 = cover.base64
- } catch {
- // 封面提取失败不影响主流程
+ const res = await uploadAndIdentifyVideo(file) as {
+ success: boolean;
+ data: { fileId: string; sessionId: string; faceId: string | null; startTime: number; endTime: number }
}
- const fileId = await upload(file, {
- fileCategory: 'video',
- groupId: null,
- coverBase64,
- onStart: function() {},
- onProgress: function() {},
- onSuccess: function() {},
- onError: function(err: Error) {
- message.error(err.message || '上传失败')
- }
- })
-
- identifyResult.value.videoFileId = fileId
- // 上传后需要再调用识别接口获取人脸信息
- // 暂时清空,等待后续识别
- identifyResult.value.sessionId = ''
- identifyResult.value.faceId = ''
- identifyResult.value.faceStartTime = 0
- identifyResult.value.faceEndTime = 0
+ identifyResult.value.videoFileId = res.data.fileId
+ identifyResult.value.sessionId = res.data.sessionId
+ identifyResult.value.faceId = res.data.faceId || ''
+ identifyResult.value.faceStartTime = res.data.startTime || 0
+ identifyResult.value.faceEndTime = res.data.endTime || 0
}
return { ...identifyResult.value }
diff --git a/frontend/app/web-gold/src/views/kling/hooks/useVoiceGeneration.ts b/frontend/app/web-gold/src/views/kling/hooks/useVoiceGeneration.ts
index 51cdf3b67e..656979f9fa 100644
--- a/frontend/app/web-gold/src/views/kling/hooks/useVoiceGeneration.ts
+++ b/frontend/app/web-gold/src/views/kling/hooks/useVoiceGeneration.ts
@@ -71,7 +71,6 @@ export function useVoiceGeneration(): UseVoiceGeneration {
audioState.value.generated = audioData
audioState.value.durationMs = await parseAudioDuration(audioData.audioBase64)
- message.success('配音生成成功!')
} else {
throw new Error(res.msg || '配音生成失败')
}