Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
xuwx1
LightX2V
Commits
d8558a0c
Unverified
Commit
d8558a0c
authored
Nov 13, 2025
by
LiangLiu
Committed by
GitHub
Nov 13, 2025
Browse files
update froentend (#470)
Co-authored-by:
qinxinyi
<
qxy118045534@163.com
>
parent
2a31ba43
Changes
26
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
351 additions
and
246 deletions
+351
-246
lightx2v/deploy/server/frontend/src/style.css
lightx2v/deploy/server/frontend/src/style.css
+4
-10
lightx2v/deploy/server/frontend/src/utils/i18n.js
lightx2v/deploy/server/frontend/src/utils/i18n.js
+1
-1
lightx2v/deploy/server/frontend/src/utils/other.js
lightx2v/deploy/server/frontend/src/utils/other.js
+257
-148
lightx2v/deploy/server/frontend/src/views/Layout.vue
lightx2v/deploy/server/frontend/src/views/Layout.vue
+3
-0
lightx2v/deploy/server/frontend/src/views/Login.vue
lightx2v/deploy/server/frontend/src/views/Login.vue
+2
-0
lightx2v/deploy/server/frontend/src/views/Share.vue
lightx2v/deploy/server/frontend/src/views/Share.vue
+84
-87
No files found.
lightx2v/deploy/server/frontend/src/style.css
View file @
d8558a0c
...
...
@@ -28,10 +28,10 @@
color
:
white
;
/* Apple 极简黑白风格 - 品牌色 */
--brand-primary
:
#
a68ee7
;
--brand-primary-light
:
#
cdbbfe
;
--brand-primary-rgb
:
17
9
,
1
51
,
2
55
;
--brand-primary-light-rgb
:
210
,
193
,
255
;
--brand-primary
:
#
90dce1
;
--brand-primary-light
:
#
90dce1
;
--brand-primary-rgb
:
8
9
,
1
94
,
2
33
;
--brand-primary-light-rgb
:
142
,
220
,
255
;
/* Apple 极简黑白风格 - 基础颜色 */
--text-primary
:
#1d1d1f
;
/* 浅色模式主要文字 */
...
...
@@ -841,12 +841,6 @@ body {
padding
:
0.5rem
0.75rem
!important
;
}
/* 创建视频页面移动端适配 */
.max-w-4xl.mx-auto
{
max-width
:
100%
!important
;
padding
:
0
1rem
!important
;
}
/* 上传区域在移动端调整 */
.upload-section
{
grid-template-columns
:
1
fr
!important
;
...
...
lightx2v/deploy/server/frontend/src/utils/i18n.js
View file @
d8558a0c
...
...
@@ -55,7 +55,7 @@ async function switchLang() {
// };
const
languageOptions
=
ref
([
{
code
:
'
zh
'
,
name
:
'
中文
'
,
flag
:
'
ZH
'
},
{
code
:
'
zh
'
,
name
:
'
中文
'
,
flag
:
'
中
'
},
{
code
:
'
en
'
,
name
:
'
English
'
,
flag
:
'
EN
'
}
]);
...
...
lightx2v/deploy/server/frontend/src/utils/other.js
View file @
d8558a0c
...
...
@@ -10,7 +10,9 @@ export const locale = i18n.global.locale
const
loginLoading
=
ref
(
false
);
const
initLoading
=
ref
(
false
);
const
downloadLoading
=
ref
(
false
);
const
downloadLoadingMessage
=
ref
(
''
);
const
isLoading
=
ref
(
false
);
// 页面加载loading状态
const
isPageLoading
=
ref
(
false
);
// 分页加载loading状态
// 录音相关状态
const
isRecording
=
ref
(
false
);
...
...
@@ -45,7 +47,8 @@ export const locale = i18n.global.locale
confirm
:
()
=>
{
}
});
const
submitting
=
ref
(
false
);
const
templateLoading
=
ref
(
false
);
// 模板加载状态
const
templateLoading
=
ref
(
false
);
// 模板/任务复用加载状态
const
templateLoadingMessage
=
ref
(
''
);
const
taskSearchQuery
=
ref
(
''
);
const
sidebarCollapsed
=
ref
(
false
);
const
showExpandHint
=
ref
(
false
);
...
...
@@ -135,6 +138,7 @@ export const locale = i18n.global.locale
const
templatePaginationKey
=
ref
(
0
);
const
imageHistory
=
ref
([]);
const
audioHistory
=
ref
([]);
const
ttsHistory
=
ref
([]);
// 模板文件缓存,避免重复下载
const
currentUser
=
ref
({});
...
...
@@ -1194,7 +1198,7 @@ export const locale = i18n.global.locale
};
const
triggerAudioUpload
=
()
=>
{
const
audioInput
=
document
.
querySelector
(
'
input[type="file"][
accept
="audio
/*
"]
'
);
const
audioInput
=
document
.
querySelector
(
'
input[type="file"][
data-role
="audio
-input
"]
'
);
if
(
audioInput
)
{
audioInput
.
click
();
}
else
{
...
...
@@ -1223,7 +1227,7 @@ export const locale = i18n.global.locale
updateUploadedContentStatus
();
console
.
log
(
'
音频已移除
'
);
// 重置音频文件输入框,确保可以重新选择相同文件
const
audioInput
=
document
.
querySelector
(
'
input[type="file"][
accept
="audio
/*
"]
'
);
const
audioInput
=
document
.
querySelector
(
'
input[type="file"][
data-role
="audio
-input
"]
'
);
if
(
audioInput
)
{
audioInput
.
value
=
''
;
}
...
...
@@ -1239,7 +1243,14 @@ export const locale = i18n.global.locale
const
handleAudioUpload
=
(
event
)
=>
{
const
file
=
event
.
target
.
files
[
0
];
if
(
file
)
{
if
(
file
&&
(
file
.
type
?.
startsWith
(
'
audio/
'
)
||
file
.
type
?.
startsWith
(
'
video/
'
)))
{
const
allowedVideoTypes
=
[
'
video/mp4
'
,
'
video/x-m4v
'
,
'
video/mpeg
'
];
if
(
file
.
type
.
startsWith
(
'
video/
'
)
&&
!
allowedVideoTypes
.
includes
(
file
.
type
))
{
showAlert
(
t
(
'
unsupportedVideoFormat
'
),
'
warning
'
);
setCurrentAudioPreview
(
null
);
updateUploadedContentStatus
();
return
;
}
s2vForm
.
value
.
audioFile
=
file
;
const
reader
=
new
FileReader
();
reader
.
onload
=
(
e
)
=>
{
...
...
@@ -1251,6 +1262,9 @@ export const locale = i18n.global.locale
}
else
{
setCurrentAudioPreview
(
null
);
updateUploadedContentStatus
();
if
(
file
)
{
showAlert
(
t
(
'
unsupportedAudioOrVideo
'
),
'
warning
'
);
}
}
};
...
...
@@ -2134,15 +2148,15 @@ export const locale = i18n.global.locale
// 分页相关函数
const
goToPage
=
async
(
page
)
=>
{
isLoading
.
value
=
true
;
is
Page
Loading
.
value
=
true
;
if
(
page
<
1
||
page
>
pagination
.
value
?.
total_pages
||
page
===
currentTaskPage
.
value
)
{
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
return
;
}
currentTaskPage
.
value
=
page
;
taskPageInput
.
value
=
page
;
// 同步更新输入框
await
refreshTasks
();
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
};
const
jumpToPage
=
async
()
=>
{
...
...
@@ -2157,15 +2171,15 @@ export const locale = i18n.global.locale
// Template分页相关函数
const
goToTemplatePage
=
async
(
page
)
=>
{
isLoading
.
value
=
true
;
is
Page
Loading
.
value
=
true
;
if
(
page
<
1
||
page
>
templatePagination
.
value
?.
total_pages
||
page
===
templateCurrentPage
.
value
)
{
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
return
;
}
templateCurrentPage
.
value
=
page
;
templatePageInput
.
value
=
page
;
// 同步更新输入框
await
loadImageAudioTemplates
();
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
};
const
jumpToTemplatePage
=
async
()
=>
{
...
...
@@ -2270,15 +2284,15 @@ export const locale = i18n.global.locale
// 灵感广场分页相关函数
const
goToInspirationPage
=
async
(
page
)
=>
{
isLoading
.
value
=
true
;
is
Page
Loading
.
value
=
true
;
if
(
page
<
1
||
page
>
inspirationPagination
.
value
?.
total_pages
||
page
===
inspirationCurrentPage
.
value
)
{
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
return
;
}
inspirationCurrentPage
.
value
=
page
;
inspirationPageInput
.
value
=
page
;
// 同步更新输入框
await
loadInspirationData
();
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
};
const
jumpToInspirationPage
=
async
()
=>
{
...
...
@@ -2623,16 +2637,20 @@ export const locale = i18n.global.locale
if
(
!
confirmed
)
{
return
;
}
// 显示删除中的提示
showAlert
(
t
(
'
deletingTaskAlert
'
),
'
info
'
);
const
response
=
await
apiRequest
(
`/api/v1/task/delete?task_id=
${
taskId
}
`
,
{
method
:
'
DELETE
'
});
if
(
response
&&
response
.
ok
)
{
showAlert
(
t
(
'
taskDeletedSuccessAlert
'
),
'
success
'
);
const
deletedTaskIndex
=
tasks
.
value
.
findIndex
(
task
=>
task
.
task_id
===
taskId
);
if
(
deletedTaskIndex
!==
-
1
)
{
const
wasCurrent
=
currentTask
.
value
?.
task_id
===
taskId
;
tasks
.
value
.
splice
(
deletedTaskIndex
,
1
);
if
(
wasCurrent
)
{
currentTask
.
value
=
tasks
.
value
[
deletedTaskIndex
]
||
tasks
.
value
[
deletedTaskIndex
-
1
]
||
null
;
}
}
refreshTasks
(
true
);
// 强制刷新
// 如果是从任务详情页删除,删除成功后关闭详情弹窗
...
...
@@ -2727,9 +2745,16 @@ export const locale = i18n.global.locale
};
const
reuseTask
=
async
(
task
)
=>
{
if
(
!
task
)
{
showAlert
(
t
(
'
loadTaskDataFailedAlert
'
),
'
danger
'
);
return
;
}
try
{
templateLoading
.
value
=
true
;
templateLoadingMessage
.
value
=
t
(
'
prefillLoadingTask
'
);
// 跳转到任务创建界面
isCreationAreaExpanded
.
value
=
true
isCreationAreaExpanded
.
value
=
true
;
if
(
showTaskDetailModal
.
value
)
{
closeTaskDetailModal
();
}
...
...
@@ -2741,6 +2766,9 @@ export const locale = i18n.global.locale
// 获取当前表单
const
currentForm
=
getCurrentForm
();
// 立即切换到创建视图,后续资产异步加载
switchToCreateView
();
// 设置模型
if
(
task
.
params
&&
task
.
params
.
model_cls
)
{
currentForm
.
model_cls
=
task
.
params
.
model_cls
;
...
...
@@ -2779,6 +2807,9 @@ export const locale = i18n.global.locale
// 加载音频文件
if
(
audioUrl
)
{
try
{
currentForm
.
audioUrl
=
audioUrl
;
setCurrentAudioPreview
(
audioUrl
);
const
audioResponse
=
await
fetch
(
audioUrl
);
if
(
audioResponse
&&
audioResponse
.
ok
)
{
const
blob
=
await
audioResponse
.
blob
();
...
...
@@ -2810,13 +2841,6 @@ export const locale = i18n.global.locale
size
:
file
.
size
,
originalBlobType
:
blob
.
type
});
// 使用FileReader生成data URL,与正常上传保持一致
const
reader
=
new
FileReader
();
reader
.
onload
=
(
e
)
=>
{
setCurrentAudioPreview
(
e
.
target
.
result
);
console
.
log
(
'
复用任务 - 音频预览已设置:
'
,
e
.
target
.
result
.
substring
(
0
,
50
)
+
'
...
'
);
};
reader
.
readAsDataURL
(
file
);
}
}
catch
(
error
)
{
console
.
warn
(
'
Failed to load audio file:
'
,
error
);
...
...
@@ -2828,161 +2852,169 @@ export const locale = i18n.global.locale
showAlert
(
t
(
'
taskMaterialReuseSuccessAlert
'
),
'
success
'
);
// 检查当前路由,如果已经在 generate 页面,则滚动到生成区域
const
currentRoute
=
router
.
currentRoute
.
value
;
if
(
currentRoute
.
path
===
'
/generate
'
)
{
// 关闭任务详情弹窗
if
(
showTaskDetailModal
.
value
)
{
closeTaskDetailModal
();
}
// 等待 DOM 更新后滚动到生成区域
await
nextTick
();
// 如果之前有展开过创作区域,保持展开状态
const
creationArea
=
document
.
querySelector
(
'
.creation-area
'
);
if
(
isCreationAreaExpanded
.
value
)
{
// 延迟一点时间确保DOM更新完成
setTimeout
(()
=>
{
if
(
creationArea
)
{
creationArea
.
classList
.
add
(
'
show
'
);
}
},
50
);
}
// 滚动到顶部
const
mainScrollable
=
document
.
querySelector
(
'
.main-scrollbar
'
);
if
(
mainScrollable
)
{
mainScrollable
.
scrollTo
({
top
:
0
,
behavior
:
'
smooth
'
});
}
}
else
{
// 不在 generate 页面,跳转过去
switchToCreateView
();
}
}
catch
(
error
)
{
console
.
error
(
'
Failed to reuse task:
'
,
error
);
showAlert
(
t
(
'
loadTaskDataFailedAlert
'
),
'
danger
'
);
}
finally
{
templateLoading
.
value
=
false
;
templateLoadingMessage
.
value
=
''
;
}
};
const
downloadFile
=
(
fileInfo
)
=>
{
const
downloadFile
=
async
(
fileInfo
)
=>
{
if
(
!
fileInfo
||
!
fileInfo
.
blob
)
{
showAlert
(
t
(
'
fileUnavailableAlert
'
),
'
danger
'
);
return
;
return
false
;
}
const
blob
=
fileInfo
.
blob
;
const
fileName
=
fileInfo
.
name
||
'
download
'
;
const
mimeType
=
blob
.
type
||
fileInfo
.
mimeType
||
'
application/octet-stream
'
;
const
isMobile
=
typeof
navigator
!==
'
undefined
'
&&
/iPhone|iPad|iPod|Android/i
.
test
(
navigator
.
userAgent
);
if
(
isMobile
&&
typeof
navigator
?.
canShare
===
'
function
'
&&
typeof
navigator
?.
share
===
'
function
'
)
{
try
{
const
shareFile
=
new
File
([
blob
],
fileName
,
{
type
:
mimeType
});
if
(
navigator
.
canShare
({
files
:
[
shareFile
]
}))
{
await
navigator
.
share
({
files
:
[
shareFile
],
title
:
fileName
});
showAlert
(
t
(
'
downloadSuccessAlert
'
),
'
success
'
);
return
true
;
}
}
catch
(
error
)
{
if
(
error
?.
name
===
'
AbortError
'
)
{
console
.
info
(
'
User cancelled share dialog
'
);
showAlert
(
t
(
'
downloadCancelledAlert
'
),
'
info
'
);
return
false
;
}
console
.
warn
(
'
Native share failed, falling back to download link:
'
,
error
);
}
}
try
{
const
u
rl
=
URL
.
createObjectURL
(
fileInfo
.
blob
);
const
objectU
rl
=
URL
.
createObjectURL
(
blob
);
const
a
=
document
.
createElement
(
'
a
'
);
a
.
href
=
u
rl
;
a
.
download
=
file
Info
.
name
||
'
download
'
;
a
.
href
=
objectU
rl
;
a
.
download
=
file
Name
;
document
.
body
.
appendChild
(
a
);
a
.
click
();
document
.
body
.
removeChild
(
a
);
URL
.
revokeObjectURL
(
u
rl
);
URL
.
revokeObjectURL
(
objectU
rl
);
showAlert
(
t
(
'
downloadSuccessAlert
'
),
'
success
'
);
return
true
;
}
catch
(
error
)
{
console
.
error
(
'
Download failed:
'
,
error
);
showAlert
(
t
(
'
downloadFailedAlert
'
),
'
danger
'
);
return
false
;
}
};
// 处理文件下载
const
handleDownloadFile
=
async
(
taskId
,
fileKey
,
fileName
)
=>
{
if
(
downloadLoading
.
value
)
{
showAlert
(
t
(
'
downloadInProgressNotice
'
),
'
info
'
);
return
;
}
downloadLoading
.
value
=
true
;
downloadLoadingMessage
.
value
=
t
(
'
downloadPreparing
'
);
try
{
console
.
log
(
'
开始下载文件:
'
,
{
taskId
,
fileKey
,
fileName
})
console
.
log
(
'
开始下载文件:
'
,
{
taskId
,
fileKey
,
fileName
})
;
// 处理文件名,确保有正确的后缀名
let
finalFileName
=
fileName
let
finalFileName
=
fileName
;
if
(
fileName
&&
typeof
fileName
===
'
string
'
)
{
// 检查是否已有后缀名
const
hasExtension
=
/
\.[
a-zA-Z0-9
]
+$/
.
test
(
fileName
)
const
hasExtension
=
/
\.[
a-zA-Z0-9
]
+$/
.
test
(
fileName
);
if
(
!
hasExtension
)
{
// 没有后缀名,根据文件类型添加
const
extension
=
getFileExtension
(
fileKey
)
finalFileName
=
`
${
fileName
}
.
${
extension
}
`
console
.
log
(
'
添加后缀名:
'
,
finalFileName
)
const
extension
=
getFileExtension
(
fileKey
);
finalFileName
=
`
${
fileName
}
.
${
extension
}
`
;
console
.
log
(
'
添加后缀名:
'
,
finalFileName
);
}
}
else
{
// 没有文件名,使用默认名称
finalFileName
=
`
${
fileKey
}
.
${
getFileExtension
(
fileKey
)}
`
finalFileName
=
`
${
fileKey
}
.
${
getFileExtension
(
fileKey
)}
`
;
}
// 先尝试从缓存获取
let
fileData
=
getTaskFileFromCache
(
taskId
,
fileKey
)
console
.
log
(
'
缓存中的文件数据:
'
,
fileData
)
if
(
fileData
&&
fileData
.
blob
)
{
// 缓存中有blob数据,直接使用
console
.
log
(
'
使用缓存中的文件数据
'
)
downloadFile
({
...
fileData
,
name
:
finalFileName
})
return
}
downloadLoadingMessage
.
value
=
t
(
'
downloadFetching
'
);
if
(
fileData
&&
fileData
.
url
)
{
// 缓存中有URL,使用URL下载
console
.
log
(
'
使用缓存中的URL下载:
'
,
fileData
.
url
)
try
{
const
response
=
await
fetch
(
fileData
.
url
)
console
.
log
(
'
文件响应状态:
'
,
response
.
status
,
response
.
ok
)
let
downloadUrl
=
null
;
if
(
response
.
ok
)
{
const
blob
=
await
response
.
blob
()
console
.
log
(
'
文件blob大小:
'
,
blob
.
size
)
const
downloadData
=
{
blob
:
blob
,
name
:
finalFileName
}
console
.
log
(
'
构造的文件数据:
'
,
downloadData
)
downloadFile
(
downloadData
)
return
}
else
{
console
.
error
(
'
文件响应失败:
'
,
response
.
status
,
response
.
statusText
)
}
}
catch
(
error
)
{
console
.
error
(
'
使用缓存URL下载失败:
'
,
error
)
}
const
cachedData
=
getTaskFileFromCache
(
taskId
,
fileKey
);
if
(
cachedData
?.
url
)
{
downloadUrl
=
cachedData
.
url
;
}
if
(
!
fileData
)
{
console
.
log
(
'
缓存中没有文件,尝试异步获取...
'
)
// 缓存中没有,尝试异步获取
const
url
=
await
getTaskFileUrl
(
taskId
,
fileKey
)
console
.
log
(
'
获取到的文件URL:
'
,
url
)
if
(
!
downloadUrl
)
{
downloadUrl
=
await
getTaskFileUrl
(
taskId
,
fileKey
);
}
if
(
u
rl
)
{
const
response
=
await
fetch
(
url
)
console
.
log
(
'
文件响应状态:
'
,
response
.
status
,
response
.
ok
)
if
(
!
downloadU
rl
)
{
throw
new
Error
(
'
无法获取文件URL
'
);
}
if
(
response
.
ok
)
{
const
blob
=
await
response
.
blob
()
console
.
log
(
'
文件blob大小:
'
,
blob
.
size
)
const
response
=
await
fetch
(
downloadUrl
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
`文件响应失败:
${
response
.
status
}
`
);
}
fileData
=
{
blob
:
blob
,
name
:
finalFileName
}
console
.
log
(
'
构造的文件数据:
'
,
fileData
)
}
else
{
console
.
error
(
'
文件响应失败:
'
,
response
.
status
,
response
.
statusText
)
}
const
blob
=
await
response
.
blob
();
const
isMobileBrowser
=
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
.
test
(
navigator
.
userAgent
);
if
(
isMobileBrowser
)
{
downloadLoadingMessage
.
value
=
''
;
downloadLoading
.
value
=
false
;
showAlert
(
t
(
'
mobileSaveToAlbumTip
'
),
'
info
'
);
const
blobUrl
=
URL
.
createObjectURL
(
blob
);
const
previewWindow
=
window
.
open
(
''
,
'
_blank
'
,
'
noopener,noreferrer
'
);
if
(
previewWindow
)
{
previewWindow
.
document
.
write
(
`<!DOCTYPE html>
<html lang="
${
locale
.
value
||
'
en
'
}
">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
${
t
(
'
mobileSavePreviewTitle
'
)}
</title>
<style>
body { margin: 0; background: #000; color: #fff; font-family: system-ui, sans-serif; }
.wrapper { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 16px; gap: 16px; }
video { width: 100%; height: auto; border-radius: 16px; max-height: calc(100vh - 160px); }
p { text-align: center; line-height: 1.5; font-size: 15px; color: rgba(255,255,255,0.85); }
</style>
</head>
<body>
<div class="wrapper">
<video controls playsinline webkit-playsinline preload="auto" src="
${
blobUrl
}
"></video>
<p>
${
t
(
'
mobileSaveInstruction
'
)}
</p>
</div>
<script>
window.addEventListener('pagehide', () => URL.revokeObjectURL('
${
blobUrl
}
'));
window.addEventListener('beforeunload', () => URL.revokeObjectURL('
${
blobUrl
}
'));
</script>
</body>
</html>`
);
previewWindow
.
document
.
close
();
}
else
{
console
.
error
(
'
无法获取文件URL
'
)
URL
.
revokeObjectURL
(
blobUrl
);
window
.
location
.
href
=
downloadUrl
;
}
return
;
}
if
(
fileData
&&
fileData
.
blob
)
{
console
.
log
(
'
开始下载文件:
'
,
fileData
.
name
)
downloadFile
(
fileData
)
}
else
{
console
.
error
(
'
文件数据无效:
'
,
fileData
)
showAlert
(
t
(
'
fileUnavailableAlert
'
),
'
danger
'
)
}
downloadLoadingMessage
.
value
=
t
(
'
downloadSaving
'
);
await
downloadFile
({
blob
,
name
:
finalFileName
,
mimeType
:
blob
.
type
});
}
catch
(
error
)
{
console
.
error
(
'
下载失败:
'
,
error
)
showAlert
(
t
(
'
downloadFailedAlert
'
),
'
danger
'
)
console
.
error
(
'
下载失败:
'
,
error
);
showAlert
(
t
(
'
downloadFailedAlert
'
),
'
danger
'
);
}
finally
{
downloadLoading
.
value
=
false
;
downloadLoadingMessage
.
value
=
''
;
}
}
...
...
@@ -4750,10 +4782,10 @@ export const locale = i18n.global.locale
// 选择分类
const
selectInspirationCategory
=
async
(
category
)
=>
{
isLoading
.
value
=
true
;
is
Page
Loading
.
value
=
true
;
// 如果点击的是当前分类,不重复请求
if
(
selectedInspirationCategory
.
value
===
category
)
{
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
return
;
}
...
...
@@ -4770,7 +4802,7 @@ export const locale = i18n.global.locale
// 重新加载数据
await
loadInspirationData
();
// 强制刷新,不使用缓存
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
};
// 搜索防抖定时器
...
...
@@ -4796,7 +4828,7 @@ export const locale = i18n.global.locale
// 重新加载数据
await
loadInspirationData
();
// 强制刷新,不使用缓存
isLoading
.
value
=
false
;
is
Page
Loading
.
value
=
false
;
},
500
);
// 500ms 防抖延迟
};
...
...
@@ -5665,7 +5697,7 @@ export const locale = i18n.global.locale
try
{
// 开始模板加载
templateLoading
.
value
=
true
;
showAlert
(
'
模板加载中...
'
,
'
info
'
);
templateLoadingMessage
.
value
=
t
(
'
prefillLoadingTemplate
'
);
// 先设置任务类型
selectedTaskId
.
value
=
item
.
task_type
;
...
...
@@ -5680,6 +5712,12 @@ export const locale = i18n.global.locale
currentForm
.
model_cls
=
item
.
model_cls
||
''
;
currentForm
.
stage
=
item
.
stage
||
'
single_stage
'
;
// 立即关闭模板详情并切换到创建视图,后续资源异步加载
showTemplateDetailModal
.
value
=
false
;
selectedTemplate
.
value
=
null
;
isCreationAreaExpanded
.
value
=
true
;
switchToCreateView
();
// 创建加载Promise数组
const
loadingPromises
=
[];
...
...
@@ -5783,14 +5821,6 @@ export const locale = i18n.global.locale
await
Promise
.
all
(
loadingPromises
);
}
// 关闭模板详情弹窗(不跳转路由)
showTemplateDetailModal
.
value
=
false
;
selectedTemplate
.
value
=
null
;
// 切换到创建视图
isCreationAreaExpanded
.
value
=
true
;
switchToCreateView
();
showAlert
(
`模板加载完成`
,
'
success
'
);
}
catch
(
error
)
{
console
.
error
(
'
应用模板失败:
'
,
error
);
...
...
@@ -5798,6 +5828,7 @@ export const locale = i18n.global.locale
}
finally
{
// 结束模板加载
templateLoading
.
value
=
false
;
templateLoadingMessage
.
value
=
''
;
}
};
...
...
@@ -6212,6 +6243,75 @@ export const locale = i18n.global.locale
featuredTemplatesLoading
.
value
=
false
;
}
};
const
removeTtsHistoryEntry
=
(
entryId
)
=>
{
if
(
!
entryId
)
return
;
const
currentHistory
=
loadTtsHistory
().
filter
(
entry
=>
entry
.
id
!==
entryId
);
saveTtsHistory
(
currentHistory
);
};
const
loadTtsHistory
=
()
=>
{
try
{
const
stored
=
localStorage
.
getItem
(
'
ttsHistory
'
);
if
(
!
stored
)
return
[];
const
parsed
=
JSON
.
parse
(
stored
);
ttsHistory
.
value
=
Array
.
isArray
(
parsed
)
?
parsed
:
[];
return
ttsHistory
.
value
;
}
catch
(
error
)
{
console
.
error
(
'
加载TTS历史失败:
'
,
error
);
ttsHistory
.
value
=
[];
return
[];
}
};
const
saveTtsHistory
=
(
historyList
)
=>
{
try
{
localStorage
.
setItem
(
'
ttsHistory
'
,
JSON
.
stringify
(
historyList
));
ttsHistory
.
value
=
historyList
;
}
catch
(
error
)
{
console
.
error
(
'
保存TTS历史失败:
'
,
error
);
}
};
const
addTtsHistoryEntry
=
(
text
=
''
,
instruction
=
''
)
=>
{
const
trimmedText
=
(
text
||
''
).
trim
();
const
trimmedInstruction
=
(
instruction
||
''
).
trim
();
if
(
!
trimmedText
&&
!
trimmedInstruction
)
{
return
;
}
const
currentHistory
=
loadTtsHistory
();
const
existingIndex
=
currentHistory
.
findIndex
(
entry
=>
entry
.
text
===
trimmedText
&&
entry
.
instruction
===
trimmedInstruction
);
const
timestamp
=
new
Date
().
toISOString
();
if
(
existingIndex
!==
-
1
)
{
const
existingEntry
=
currentHistory
.
splice
(
existingIndex
,
1
)[
0
];
existingEntry
.
timestamp
=
timestamp
;
currentHistory
.
unshift
(
existingEntry
);
}
else
{
currentHistory
.
unshift
({
id
:
Date
.
now
(),
text
:
trimmedText
,
instruction
:
trimmedInstruction
,
timestamp
});
}
if
(
currentHistory
.
length
>
20
)
{
currentHistory
.
length
=
20
;
}
saveTtsHistory
(
currentHistory
);
};
const
clearTtsHistory
=
()
=>
{
ttsHistory
.
value
=
[];
localStorage
.
removeItem
(
'
ttsHistory
'
);
};
export
{
// 任务类型下拉菜单
...
...
@@ -6222,7 +6322,9 @@ export {
loginLoading
,
initLoading
,
downloadLoading
,
downloadLoadingMessage
,
isLoading
,
isPageLoading
,
// 录音相关
isRecording
,
...
...
@@ -6245,6 +6347,7 @@ export {
toggleSmsLogin
,
submitting
,
templateLoading
,
templateLoadingMessage
,
taskSearchQuery
,
currentUser
,
models
,
...
...
@@ -6531,4 +6634,10 @@ export {
initTheme
,
toggleTheme
,
getThemeIcon
,
loadTtsHistory
,
removeTtsHistoryEntry
,
ttsHistory
,
addTtsHistoryEntry
,
saveTtsHistory
,
clearTtsHistory
,
};
lightx2v/deploy/server/frontend/src/views/Layout.vue
View file @
d8558a0c
...
...
@@ -9,6 +9,7 @@ import PromptTemplate from '../components/PromptTemplate.vue'
import
Voice_tts
from
'
../components/Voice_tts.vue
'
import
MediaTemplate
from
'
../components/MediaTemplate.vue
'
import
Loading
from
'
../components/Loading.vue
'
import
SiteFooter
from
'
../components/SiteFooter.vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
isLoading
,
showVoiceTTSModal
,
handleAudioUpload
,
showAlert
}
from
'
../utils/other
'
...
...
@@ -59,6 +60,8 @@ const handleTTSComplete = (audioBlob) => {
<router-view></router-view>
</div>
</div>
<SiteFooter
/>
</div>
<!-- 全局组件 -->
...
...
lightx2v/deploy/server/frontend/src/views/Login.vue
View file @
d8558a0c
...
...
@@ -90,6 +90,7 @@ onMounted(async () => {
<span
class=
"text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
LightX2V
</span>
<i
class=
"fas fa-external-link-alt text-xs text-[#86868b] dark:text-[#98989d] transition-all duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
></i>
</a>
</div>
<Alert
/>
...
...
@@ -99,6 +100,7 @@ onMounted(async () => {
<Loading
/>
</div>
</div>
</
template
>
<
style
scoped
>
...
...
lightx2v/deploy/server/frontend/src/views/Share.vue
View file @
d8558a0c
...
...
@@ -276,35 +276,33 @@ onMounted(async () => {
<
template
>
<!-- Apple 极简风格分享页面 -->
<div
class=
"bg-[#f5f5f7] dark:bg-[#000000] transition-colors duration-300 w-full h-full"
>
<!-- 主内容区域 -->
<div
class=
"flex flex-col w-full h-full"
>
<!-- TopBar -->
<topMenu
/>
<!-- 滚动内容区域 - 带滚动条 -->
<div
class=
"flex-1 overflow-y-auto main-scrollbar"
>
<!-- 错误状态 - Apple 风格 - 响应式 -->
<div
v-if=
"error"
class=
"flex items-center justify-center min-h-[60vh] px-4 sm:px-6"
>
<div
class=
"min-h-screen w-full bg-[#f5f5f7] dark:bg-[#000000]"
>
<!-- TopBar -->
<topMenu
/>
<!-- 主要内容区域 -->
<div
class=
"w-full min-h-[calc(100vh-80px)] overflow-y-auto main-scrollbar"
>
<!-- 错误状态 - Apple 风格 -->
<div
v-if=
"error"
class=
"flex items-center justify-center min-h-[60vh] px-6"
>
<div
class=
"text-center max-w-md"
>
<div
class=
"inline-flex items-center justify-center w-
16 h-16 sm:w-20 sm:
h-20 bg-red-500/10 dark:bg-red-400/10 rounded-
2xl sm:rounded-3xl mb-4 sm:
mb-6"
>
<i
class=
"fas fa-exclamation-triangle
text-2xl sm:
text-3xl text-red-500 dark:text-red-400"
></i>
<div
class=
"inline-flex items-center justify-center w-
20
h-20 bg-red-500/10 dark:bg-red-400/10 rounded-
3xl
mb-6"
>
<i
class=
"fas fa-exclamation-triangle text-3xl text-red-500 dark:text-red-400"
></i>
</div>
<h2
class=
"text-
xl sm:text-
2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7]
mb-3 sm:
mb-4 tracking-tight"
>
{{
t
(
'
shareNotFound
'
)
}}
</h2>
<p
class=
"text-
sm sm:text-
base text-[#86868b] dark:text-[#98989d]
mb-6 sm:
mb-8 tracking-tight"
>
{{
error
}}
</p>
<h2
class=
"text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-4 tracking-tight"
>
{{
t
(
'
shareNotFound
'
)
}}
</h2>
<p
class=
"text-base text-[#86868b] dark:text-[#98989d] mb-8 tracking-tight"
>
{{
error
}}
</p>
<button
@
click=
"router.push('/')"
class=
"inline-flex items-center justify-center gap-2 px-
6 sm:px-8 py-2.5 sm:
py-3 bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white rounded-full
text-sm sm:
text-[15px] font-semibold tracking-tight transition-all duration-200 hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100"
>
<i
class=
"fas fa-home
text-xs sm:
text-sm"
></i>
class=
"inline-flex items-center justify-center gap-2 px-
8
py-3 bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white rounded-full text-[15px] font-semibold tracking-tight transition-all duration-200 hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100"
>
<i
class=
"fas fa-home text-sm"
></i>
<span>
{{
t
(
'
backToHome
'
)
}}
</span>
</button>
</div>
</div>
<!-- 分享内容 - Apple 风格
- 响应式布局
-->
<div
v-else-if=
"shareData"
class=
"
flex flex-col lg:grid
lg:grid-cols-2 gap-8 lg:gap-16 w-full max-w-7xl mx-auto px-
4
sm:px-
6
lg:px-12
py-8 sm:
py-12 lg:py-16 items-center"
>
<!-- 左侧视频区域
- 响应式尺寸
-->
<div
class=
"flex justify-center items-center
w-full order-1
"
>
<div
class=
"w-full
max-w-[300px] sm:max-w-[350px] lg:
max-w-[400px] aspect-[9/16] bg-black dark:bg-[#000000] rounded-2xl overflow-hidden shadow-[0_8px_24px_rgba(0,0,0,0.15)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.5)] relative"
>
<!-- 分享内容 - Apple 风格 -->
<div
v-else-if=
"shareData"
class=
"
grid grid-cols-1
lg:grid-cols-2 gap-8 lg:gap-16 w-full max-w-7xl mx-auto px-
6
sm:px-
8
lg:px-12 py-12 lg:py-16 items-center"
>
<!-- 左侧视频区域 -->
<div
class=
"flex justify-center items-center"
>
<div
class=
"w-full max-w-[400px] aspect-[9/16] bg-black dark:bg-[#000000] rounded-2xl overflow-hidden shadow-[0_8px_24px_rgba(0,0,0,0.15)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.5)] relative"
>
<!-- 视频加载占位符 - Apple 风格 -->
<div
v-if=
"!videoUrl"
class=
"w-full h-full flex flex-col items-center justify-center bg-[#f5f5f7] dark:bg-[#1c1c1e]"
>
<div
class=
"relative w-12 h-12 mb-6"
>
...
...
@@ -339,94 +337,94 @@ onMounted(async () => {
</div>
</div>
<!-- 右侧信息区域 - Apple 风格
- 响应式
-->
<div
class=
"flex items-center justify-center
w-full order-2
"
>
<div
class=
"w-full
max-w-[300px] sm:
max-w-[500px]"
>
<!-- 标题 - Apple 风格
- 响应式字体
-->
<h1
class=
"text-
2
xl sm:text-
3xl lg:text-4xl xl:text-
5xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7]
mb-3 sm:
mb-4 tracking-tight leading-tight
text-center lg:text-left
"
>
<!-- 右侧信息区域 - Apple 风格 -->
<div
class=
"flex items-center justify-center"
>
<div
class=
"w-full max-w-[500px]"
>
<!-- 标题 - Apple 风格 -->
<h1
class=
"text-
4
xl sm:text-5xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-4 tracking-tight leading-tight"
>
{{
getShareTitle
()
}}
</h1>
<!-- 描述 - Apple 风格
- 响应式字体
-->
<p
class=
"text-
base sm:text-
lg text-[#86868b] dark:text-[#98989d]
mb-6 sm:
mb-8 leading-relaxed tracking-tight
text-center lg:text-left
"
>
<!-- 描述 - Apple 风格 -->
<p
class=
"text-lg text-[#86868b] dark:text-[#98989d] mb-8 leading-relaxed tracking-tight"
>
{{
getShareDescription
()
}}
</p>
<!-- 特性列表 - Apple 风格
- 响应式
-->
<div
class=
"grid grid-cols-1 gap-
2 sm:gap-3 mb-6 sm:
mb-8"
>
<div
class=
"flex items-center gap-
2.5 sm:gap-3 p-2.5 sm:
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
9 h-9 sm:w-10 sm:
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-rocket
text-sm sm:
text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<!-- 特性列表 - Apple 风格 -->
<div
class=
"grid grid-cols-1 gap-
3
mb-8"
>
<div
class=
"flex items-center gap-
3
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
10
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-rocket text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
</div>
<span
class=
"text-
xs sm:text-
sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
latestAIModel
'
)
}}
</span>
<span
class=
"text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
latestAIModel
'
)
}}
</span>
</div>
<div
class=
"flex items-center gap-
2.5 sm:gap-3 p-2.5 sm:
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
9 h-9 sm:w-10 sm:
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-bolt
text-sm sm:
text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<div
class=
"flex items-center gap-
3
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
10
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-bolt text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
</div>
<span
class=
"text-
xs sm:text-
sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
oneClickReplication
'
)
}}
</span>
<span
class=
"text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
oneClickReplication
'
)
}}
</span>
</div>
<div
class=
"flex items-center gap-
2.5 sm:gap-3 p-2.5 sm:
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
9 h-9 sm:w-10 sm:
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-user-cog
text-sm sm:
text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<div
class=
"flex items-center gap-
3
p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)]"
>
<div
class=
"w-
10
h-10 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0"
>
<i
class=
"fas fa-user-cog text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
</div>
<span
class=
"text-
xs sm:text-
sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
customizableCharacter
'
)
}}
</span>
<span
class=
"text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
customizableCharacter
'
)
}}
</span>
</div>
</div>
<!-- 操作按钮 - Apple 风格
- 响应式
-->
<div
class=
"space-y-
2.5 sm:space-y-3 mb-6 sm:
mb-8"
>
<!-- 操作按钮 - Apple 风格 -->
<div
class=
"space-y-
3
mb-8"
>
<button
@
click=
"createSimilar"
class=
"w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-
6 sm:px-8 py-3 sm:py-3.5 text-sm sm:
text-[15px] font-semibold text-white hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100 transition-all duration-200 ease-out tracking-tight flex items-center justify-center gap-2"
>
class=
"w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-
8 py-3.5
text-[15px] font-semibold text-white hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100 transition-all duration-200 ease-out tracking-tight flex items-center justify-center gap-2"
>
<i
class=
"fas fa-magic text-sm"
></i>
<span>
{{
getShareButtonText
()
}}
</span>
</button>
<!-- 详细信息按钮 -->
<button
@
click=
"showDetails = !showDetails"
class=
"w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-
6 sm:px-8 py-2.5 sm:py-3 text-sm sm:
text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2"
>
class=
"w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-
8 py-3
text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2"
>
<i
:class=
"showDetails ? 'fas fa-chevron-up' : 'fas fa-info-circle'"
class=
"text-sm"
></i>
<span>
{{
showDetails
?
t
(
'
hideDetails
'
)
:
t
(
'
showDetails
'
)
}}
</span>
</button>
</div>
<!-- 技术信息 - Apple 风格 - 响应式 -->
<div
class=
"text-center
lg:text-left
pt-4 sm:pt-6 border-t border-black/8 dark:border-white/8"
>
<div
class=
"text-center pt-4 sm:pt-6 border-t border-black/8 dark:border-white/8"
>
<a
href=
"https://github.com/ModelTC/LightX2V"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"inline-flex items-center gap-2
text-xs sm:
text-sm text-[#86868b] dark:text-[#98989d] hover:text-[color:var(--brand-primary)] dark:hover:text-[color:var(--brand-primary-light)] transition-colors tracking-tight"
>
<i
class=
"fab fa-github
text-sm sm:
text-base"
></i>
class=
"inline-flex items-center gap-2 text-sm text-[#86868b] dark:text-[#98989d] hover:text-[color:var(--brand-primary)] dark:hover:text-[color:var(--brand-primary-light)] transition-colors tracking-tight"
>
<i
class=
"fab fa-github text-base"
></i>
<span>
{{
t
(
'
poweredByLightX2V
'
)
}}
</span>
<i
class=
"fas fa-external-link-alt
text-[10px] sm:
text-xs"
></i>
<i
class=
"fas fa-external-link-alt text-xs"
></i>
</a>
</div>
</div>
</div>
</div>
<!-- 详细信息面板 - Apple 风格
- 响应式
-->
<div
v-if=
"showDetails && shareData"
class=
"w-full bg-white dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8
py-8 sm:py-12 lg:
py-16"
>
<div
class=
"max-w-6xl mx-auto px-
4
sm:px-
6
lg:px-12"
>
<!-- 输入素材标题 - Apple 风格
- 响应式
-->
<h2
class=
"text-xl sm:text-
2xl lg:text-
3xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex
flex-col sm:flex-row
items-center justify-center gap-
2 sm:gap-3 mb-6 sm:mb-8 lg:
mb-10 tracking-tight"
>
<!-- 详细信息面板 - Apple 风格 -->
<div
v-if=
"showDetails && shareData"
class=
"w-full bg-white dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8 py-16"
>
<div
class=
"max-w-6xl mx-auto px-
6
sm:px-
8
lg:px-12"
>
<!-- 输入素材标题 - Apple 风格 -->
<h2
class=
"text-
2
xl sm:text-3xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center justify-center gap-
3
mb-10 tracking-tight"
>
<i
class=
"fas fa-upload text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<span>
{{
t
(
'
inputMaterials
'
)
}}
</span>
</h2>
<!-- 三个卡片 - Apple 风格
- 响应式竖向排列
-->
<div
class=
"
flex flex-col md:grid
md:grid-cols-3
gap-4 sm:
gap-6"
>
<!-- 三个
并列的分块
卡片 - Apple 风格 -->
<div
class=
"
grid grid-cols-1
md:grid-cols-3 gap-6"
>
<!-- 图片卡片 - Apple 风格 -->
<div
class=
"bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]"
>
<!-- 卡片头部
- 响应式
-->
<div
class=
"flex items-center justify-between px-
4 sm:px-5 py-3 sm:
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center
gap-2 sm:
gap-3"
>
<i
class=
"fas fa-image
text-base sm:
text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-
sm sm:text-
base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
image
'
)
}}
</h3>
<!-- 卡片头部 -->
<div
class=
"flex items-center justify-between px-
5
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center gap-3"
>
<i
class=
"fas fa-image text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
image
'
)
}}
</h3>
</div>
</div>
<!-- 卡片内容
- 响应式 - 带滚动条
-->
<div
class=
"p-
4 sm:p-
6 min-h-[
150px] sm:min-h-[200px] max-h-[300px] overflow-y-auto main-scrollbar
"
>
<!-- 卡片内容 -->
<div
class=
"p-6 min-h-[
200px]
"
>
<div
v-if=
"getImageMaterials().length > 0"
>
<div
v-for=
"[inputName, url] in getImageMaterials()"
:key=
"inputName"
class=
"rounded-xl overflow-hidden border border-black/8 dark:border-white/8"
>
<img
:src=
"url"
:alt=
"inputName"
...
...
@@ -442,17 +440,17 @@ onMounted(async () => {
</div>
</div>
<!-- 音频卡片 - Apple 风格
- 响应式
-->
<!-- 音频卡片 - Apple 风格 -->
<div
class=
"bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]"
>
<!-- 卡片头部
- 响应式
-->
<div
class=
"flex items-center justify-between px-
4 sm:px-5 py-3 sm:
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center
gap-2 sm:
gap-3"
>
<i
class=
"fas fa-music
text-base sm:
text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-
sm sm:text-
base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
audio
'
)
}}
</h3>
<!-- 卡片头部 -->
<div
class=
"flex items-center justify-between px-
5
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center gap-3"
>
<i
class=
"fas fa-music text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
audio
'
)
}}
</h3>
</div>
</div>
<!-- 卡片内容
- 响应式 - 带滚动条
-->
<div
class=
"p-
4 sm:p-
6 min-h-[
150px] sm:min-h-[200px] max-h-[300px] overflow-y-auto main-scrollbar
"
>
<!-- 卡片内容 -->
<div
class=
"p-6 min-h-[
200px]
"
>
<div
v-if=
"getAudioMaterials().length > 0"
class=
"space-y-4"
>
<div
v-for=
"[inputName, url] in getAudioMaterials()"
:key=
"inputName"
>
<audio
:src=
"url"
controls
class=
"w-full rounded-xl"
></audio>
...
...
@@ -465,23 +463,23 @@ onMounted(async () => {
</div>
</div>
<!-- 提示词卡片 - Apple 风格
- 响应式
-->
<!-- 提示词卡片 - Apple 风格 -->
<div
class=
"bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]"
>
<!-- 卡片头部
- 响应式
-->
<div
class=
"flex items-center justify-between px-
4 sm:px-5 py-3 sm:
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center
gap-2 sm:
gap-3"
>
<i
class=
"fas fa-file-alt
text-base sm:
text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-
sm sm:text-
base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
prompt
'
)
}}
</h3>
<!-- 卡片头部 -->
<div
class=
"flex items-center justify-between px-
5
py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8"
>
<div
class=
"flex items-center gap-3"
>
<i
class=
"fas fa-file-alt text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"
></i>
<h3
class=
"text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight"
>
{{
t
(
'
prompt
'
)
}}
</h3>
</div>
<button
v-if=
"shareData.prompt"
@
click=
"copyPrompt(shareData.prompt)"
class=
"w-
7 h-7 sm:w-8 sm:
h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
class=
"w-
8
h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
:title=
"t('copy')"
>
<i
class=
"fas fa-copy
text-[10px] sm:
text-xs"
></i>
<i
class=
"fas fa-copy text-xs"
></i>
</button>
</div>
<!-- 卡片内容
- 响应式
-->
<div
class=
"p-
4 sm:p-6 min-h-[150px] sm:
min-h-[200px]"
>
<!-- 卡片内容 -->
<div
class=
"p-
6
min-h-[200px]"
>
<div
v-if=
"shareData.prompt"
class=
"bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[10px] border border-black/6 dark:border-white/6 rounded-xl p-4"
>
<p
class=
"text-sm text-[#1d1d1f] dark:text-[#f5f5f7] leading-relaxed tracking-tight break-words"
>
{{
shareData
.
prompt
}}
</p>
</div>
...
...
@@ -494,13 +492,12 @@ onMounted(async () => {
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 全局路由跳转Loading覆盖层 - Apple 风格 -->
<div
v-show=
"isLoading"
class=
"fixed inset-0 bg-[#f5f5f7] dark:bg-[#000000] flex items-center justify-center z-[9999]"
>
<Loading
/>
</div>
<!-- 全局路由跳转Loading覆盖层 - Apple 风格 -->
<div
v-show=
"isLoading"
class=
"fixed inset-0 bg-[#f5f5f7] dark:bg-[#000000] flex items-center justify-center z-[9999]"
>
<Loading
/>
</div>
</
template
>
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment