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