From 11a85bfee73acc33fccc99250c7e6821f7215c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=B1=BC=E5=BC=80=E5=8F=91?= Date: Wed, 27 May 2026 18:37:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20BGM=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E4=B8=8A=E4=BC=A0=E3=80=81=E5=B0=81=E9=9D=A2=E5=BD=A2?= =?UTF-8?q?=E8=B1=A1=E6=A0=B7=E5=BC=8F=E3=80=81ESLint=20=E6=B8=85=E9=9B=B6?= =?UTF-8?q?=E3=80=81access=20log=20=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BGM 本地上传改用 Tauri open 对话框,修复 path 为空导致混音失效 - Rust 端放宽 BGM 路径验证(系统文件选择器选取的文件),加路径遍历防护 - BGM 混音失败时 toast 提示,不再静默忽略 - 我的作品页增加导出功能 - 封面形象卡片样式统一为 works-card 体系 - 关闭 uvicorn access log(Dockerfile + 3 个 compose) - ESLint 全绿:关掉 prop-types/incompatible-library,修复 curly/exhaustive-deps/any/unused-vars - .gitignore 排除 *.exe 构建产物 --- .gitignore | 2 + python-api/Dockerfile | 2 +- python-api/docker-compose.dev.yml | 2 +- python-api/docker-compose.prod.yml | 2 +- python-api/docker-compose.test.yml | 2 +- scripts/admin-ops.sql | 2 +- tauri-app/eslint.config.js | 2 + tauri-app/src-tauri/src/ffmpeg_cmd.rs | 8 +++- tauri-app/src/api/modules/videoCompose.ts | 3 ++ tauri-app/src/components/Layout/Sidebar.tsx | 2 +- .../components/PricingModal/PricingModal.tsx | 3 +- .../components/UpdateDialog/UpdateDialog.tsx | 2 +- tauri-app/src/hooks/useUpdater.ts | 4 +- .../ContentManagement/ContentManagement.css | 31 ++++++++++++++-- .../ContentManagement/CoverAvatarLibrary.tsx | 21 +++++------ .../src/pages/ContentManagement/MyWorks.tsx | 34 +++++++++++++++-- tauri-app/src/pages/Settings/Settings.tsx | 4 +- .../src/pages/VideoCreation/CoverDesign.tsx | 18 ++++++--- .../src/pages/VideoCreation/VideoCompose.tsx | 37 ++++++++----------- .../pages/VideoCreation/VoiceSynthesis.tsx | 2 +- .../hooks/useVideoGeneration.ts | 2 +- tauri-app/src/pages/VideoGeneration/index.tsx | 4 +- 22 files changed, 127 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index a9f97df..da48297 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ tauri-app/src-tauri/binaries/* *test*.key* .atomcode/ mixkit_bgm/ +*.exe +*.exe.sig diff --git a/python-api/Dockerfile b/python-api/Dockerfile index a2be4c7..25600c8 100644 --- a/python-api/Dockerfile +++ b/python-api/Dockerfile @@ -46,4 +46,4 @@ COPY pyproject.toml . EXPOSE 8000 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"] diff --git a/python-api/docker-compose.dev.yml b/python-api/docker-compose.dev.yml index 93efbb0..be65391 100644 --- a/python-api/docker-compose.dev.yml +++ b/python-api/docker-compose.dev.yml @@ -31,7 +31,7 @@ services: - ~/Documents/Meijiaka-zy:/root/Documents/Meijiaka-zy ports: - "8081:8000" - command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --no-access-log networks: - meijiaka-network diff --git a/python-api/docker-compose.prod.yml b/python-api/docker-compose.prod.yml index 7e4aa91..50a0ffd 100644 --- a/python-api/docker-compose.prod.yml +++ b/python-api/docker-compose.prod.yml @@ -34,7 +34,7 @@ services: volumes: # 仅持久化日志到宿主机,其他数据走对象存储 - /opt/meijiaka-zy/logs:/root/Documents/Meijiaka-zy/logs - command: alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 + command: alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --no-access-log ports: - "8000:8000" restart: unless-stopped diff --git a/python-api/docker-compose.test.yml b/python-api/docker-compose.test.yml index fae6f75..079ad5c 100644 --- a/python-api/docker-compose.test.yml +++ b/python-api/docker-compose.test.yml @@ -68,7 +68,7 @@ services: redis: condition: service_healthy command: > - sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000" + sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --no-access-log" networks: - meijiaka-zy restart: unless-stopped diff --git a/scripts/admin-ops.sql b/scripts/admin-ops.sql index e20ef42..bd6fcb7 100644 --- a/scripts/admin-ops.sql +++ b/scripts/admin-ops.sql @@ -108,7 +108,7 @@ END $$; DO $$ DECLARE - v_mobile TEXT := '13860199646'; -- ← 修改:目标用户手机号 + v_mobile TEXT := '18750556093'; -- ← 修改:目标用户手机号 v_gift_points INT := 5000; -- ← 修改:赠送积分数量 v_gift_days INT := 180; -- ← 修改:有效期(天) v_reason TEXT := '运营活动赠送'; -- ← 修改:赠送原因(写入流水描述) diff --git a/tauri-app/eslint.config.js b/tauri-app/eslint.config.js index c8d5a52..731ad2d 100644 --- a/tauri-app/eslint.config.js +++ b/tauri-app/eslint.config.js @@ -36,7 +36,9 @@ export default tseslint.config( ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, + 'react/prop-types': 'off', // TypeScript 已有类型检查,无需 PropTypes 'react-hooks/set-state-in-effect': 'warn', + 'react-hooks/incompatible-library': 'off', // TanStack Virtual 等常见库误报 'react-refresh/only-export-components': 'warn', 'react/no-unescaped-entities': 'warn', diff --git a/tauri-app/src-tauri/src/ffmpeg_cmd.rs b/tauri-app/src-tauri/src/ffmpeg_cmd.rs index 0379897..d1e8c89 100644 --- a/tauri-app/src-tauri/src/ffmpeg_cmd.rs +++ b/tauri-app/src-tauri/src/ffmpeg_cmd.rs @@ -560,7 +560,13 @@ pub async fn mix_bgm_to_video( bgm_volume: f64, ) -> Result<(), String> { let safe_video = validate_safe_path(video_path)?; - let safe_bgm = validate_safe_path(bgm_path)?; + // BGM 可以是用户通过系统文件选择器选取的任意本地文件, + // 验证文件存在且不含路径遍历字符 + let safe_bgm = if !bgm_path.contains("..") && std::path::Path::new(bgm_path).exists() { + bgm_path.to_string() + } else { + return Err(format!("BGM 文件不存在或路径非法: {}", bgm_path)); + }; let safe_output = sanitize_output_path(output_path)?; // 构建 filter_complex: diff --git a/tauri-app/src/api/modules/videoCompose.ts b/tauri-app/src/api/modules/videoCompose.ts index c10c50f..bce5186 100644 --- a/tauri-app/src/api/modules/videoCompose.ts +++ b/tauri-app/src/api/modules/videoCompose.ts @@ -72,6 +72,7 @@ export async function generateEmptyShotClip(args: { outputPath: string; }): Promise { const res = await invoke>('generate_empty_shot_clip', { args }); + // eslint-disable-next-line eqeqeq if (res.code !== 200 || res.data == null) { throw new Error(res.message || '生成空镜片段失败'); } @@ -93,6 +94,7 @@ export async function concatVideoClips( projectId, clipPaths, }); + // eslint-disable-next-line eqeqeq if (res.code !== 200 || res.data == null) { throw new Error(res.message || '视频拼接失败'); } @@ -108,6 +110,7 @@ export async function concatVideoClips( */ export async function downloadFile(url: string, outputPath: string): Promise { const res = await invoke>('download_file', { url, outputPath }); + // eslint-disable-next-line eqeqeq if (res.code !== 200 || res.data == null) { throw new Error(res.message || '下载文件失败'); } diff --git a/tauri-app/src/components/Layout/Sidebar.tsx b/tauri-app/src/components/Layout/Sidebar.tsx index 7db7d43..5c591aa 100644 --- a/tauri-app/src/components/Layout/Sidebar.tsx +++ b/tauri-app/src/components/Layout/Sidebar.tsx @@ -57,7 +57,7 @@ export default function Sidebar({ currentPath, onNavigate }: SidebarProps) { // 点击外部关闭用户菜单 useEffect(() => { - if (!showUserMenu) return; + if (!showUserMenu) {return;} const handleClick = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { setShowUserMenu(false); diff --git a/tauri-app/src/components/PricingModal/PricingModal.tsx b/tauri-app/src/components/PricingModal/PricingModal.tsx index bed40da..3eb745b 100644 --- a/tauri-app/src/components/PricingModal/PricingModal.tsx +++ b/tauri-app/src/components/PricingModal/PricingModal.tsx @@ -32,7 +32,8 @@ export default function PricingModal({ open, onClose }: PricingModalProps) { const [loading, setLoading] = useState(false); useEffect(() => { - if (!open || rules.length > 0) return; + if (!open || rules.length > 0) {return;} + // eslint-disable-next-line react-hooks/set-state-in-effect setLoading(true); pointsApi.getRules() .then(setRules) diff --git a/tauri-app/src/components/UpdateDialog/UpdateDialog.tsx b/tauri-app/src/components/UpdateDialog/UpdateDialog.tsx index 7871724..71ebb5b 100644 --- a/tauri-app/src/components/UpdateDialog/UpdateDialog.tsx +++ b/tauri-app/src/components/UpdateDialog/UpdateDialog.tsx @@ -46,7 +46,7 @@ export default function UpdateDialog() { } const formatBytes = (bytes: number) => { - if (bytes === 0) return '0 B'; + if (bytes === 0) {return '0 B';} const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); diff --git a/tauri-app/src/hooks/useUpdater.ts b/tauri-app/src/hooks/useUpdater.ts index 2de5383..885bcec 100644 --- a/tauri-app/src/hooks/useUpdater.ts +++ b/tauri-app/src/hooks/useUpdater.ts @@ -48,7 +48,7 @@ export interface UpdaterActions { * 如果 notes 中包含 [强制更新] 或 [mandatory],视为强制更新 */ function parseMandatory(notes: string | undefined): boolean { - if (!notes) return false; + if (!notes) {return false;} return notes.includes('[强制更新]') || notes.includes('[mandatory]'); } @@ -107,7 +107,7 @@ export function useUpdater(): UpdaterState & UpdaterActions { const downloadAndInstall = useCallback(async () => { const update = updateRef.current; - if (!update) return; + if (!update) {return;} setState(s => ({ ...s, diff --git a/tauri-app/src/pages/ContentManagement/ContentManagement.css b/tauri-app/src/pages/ContentManagement/ContentManagement.css index d914c23..9c028f0 100644 --- a/tauri-app/src/pages/ContentManagement/ContentManagement.css +++ b/tauri-app/src/pages/ContentManagement/ContentManagement.css @@ -1127,11 +1127,11 @@ flex: 1; } -/* 封面形象网格 — 复用 avatar-card 风格 */ +/* 封面形象网格 — 复用 works-card 风格 */ .cover-avatar-grid { display: grid; - grid-template-columns: repeat(4, 1fr); - gap: var(--spacing-xl); + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: var(--spacing-lg); padding: var(--spacing-sm) var(--spacing-xs); flex: 1; align-content: start; @@ -1139,6 +1139,21 @@ overflow-y: auto; } +/* 封面形象:封面区域使用 3:4 比例(照片尺寸) */ +.cover-avatar-grid .works-card-cover { + aspect-ratio: 3 / 4; +} + +.cover-avatar-grid .works-card-cover::before { + padding-top: 133.33%; /* 4/3 * 100 = 133.33% for 3:4 ratio */ +} + +/* 封面形象:图片使用 contain 以适应透明背景 */ +.cover-avatar-grid .works-card-poster { + object-fit: contain; + background: var(--bg-input); +} + /* Content Page Compact variant */ .content-page-compact { gap: var(--spacing-md); @@ -1659,11 +1674,12 @@ /* 网格布局 - 一行4个 */ .works-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--spacing-lg); flex: 1; align-content: start; align-items: start; + grid-auto-rows: min-content; } /* 成品卡片 */ @@ -1702,6 +1718,13 @@ flex-shrink: 0; } +/* 确保封面高度始终正确(兼容 aspect-ratio 支持不佳的环境) */ +.works-card-cover::before { + content: ''; + display: block; + padding-top: 177.78%; /* 16/9 * 100 = 177.78% for 9:16 ratio */ +} + .works-card-poster { position: absolute; inset: 0; diff --git a/tauri-app/src/pages/ContentManagement/CoverAvatarLibrary.tsx b/tauri-app/src/pages/ContentManagement/CoverAvatarLibrary.tsx index 60bdfec..9e0c691 100644 --- a/tauri-app/src/pages/ContentManagement/CoverAvatarLibrary.tsx +++ b/tauri-app/src/pages/ContentManagement/CoverAvatarLibrary.tsx @@ -309,40 +309,39 @@ export default function CoverAvatarLibrary() { ) : (
{coverAvatars.map(a => ( -
+
{/* 图片预览 */} -
+
{a.name} {/* 悬停操作按钮 */} -
+
e.stopPropagation()}>
{/* 名称 */} -
+
{editingId === a.id ? ( setEditingName(e.target.value)} onKeyDown={e => { @@ -353,7 +352,7 @@ export default function CoverAvatarLibrary() { autoFocus /> ) : ( -
{a.name}
+
{a.name}
)}
diff --git a/tauri-app/src/pages/ContentManagement/MyWorks.tsx b/tauri-app/src/pages/ContentManagement/MyWorks.tsx index 982a2a6..fe123b1 100644 --- a/tauri-app/src/pages/ContentManagement/MyWorks.tsx +++ b/tauri-app/src/pages/ContentManagement/MyWorks.tsx @@ -2,7 +2,9 @@ import { useState, useEffect, useRef } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { invoke } from '@tauri-apps/api/core'; import { openPath } from '@tauri-apps/plugin-opener'; +import { save } from '@tauri-apps/plugin-dialog'; import { getLocalFileUrl } from '../../utils/fileUrl'; +import { toast } from '../../store/uiStore'; import { useNavigation } from '../../contexts/NavigationContext'; import { switchProject } from '../../store/projectStore'; import { localProjectApi } from '../../api/modules/localStorage'; @@ -106,7 +108,7 @@ function ProductCard({ product, onDelete, onRename }: { let canceled = false; getLocalFileUrl(product.path) .then(url => { - if (!canceled) setVideoUrl(url); + if (!canceled) {setVideoUrl(url);} }) .catch(err => { console.error('[ProductCard] 加载视频失败:', err); @@ -165,6 +167,29 @@ function ProductCard({ product, onDelete, onRename }: { const handleOpen = () => openPath(product.path); + const handleExport = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + const filename = product.path.split(/[\\/]/).pop() || 'video.mp4'; + const targetPath = await save({ + defaultPath: filename, + filters: [{ name: '视频', extensions: ['mp4'] }], + }); + if (!targetPath) {return;} + const res = await invoke<{ code: number; data?: string; message: string }>('export_product', { + sourcePath: product.path, + targetPath, + }); + if (res.code === 200) { + toast.success('导出成功'); + } else { + toast.error(res.message || '导出失败'); + } + } catch { + toast.error('导出失败'); + } + }; + const startRename = (e: React.MouseEvent) => { e.stopPropagation(); setIsRenaming(true); @@ -210,6 +235,9 @@ function ProductCard({ product, onDelete, onRename }: {
)}
e.stopPropagation()}> + @@ -308,7 +336,7 @@ export default function MyWorks() { useEffect(() => { const el = productsGridRef.current; - if (!el) return; + if (!el) {return;} const ro = new ResizeObserver(entries => { const width = entries[0].contentRect.width; // 卡片最小 200px + gap 16px (var(--spacing-lg)) @@ -449,7 +477,7 @@ export default function MyWorks() { {activeTab === 'products' && ( <> {products.length > 0 ? ( -
+
{enableProductVirtualization ? (
{productsVirtualizer.getVirtualItems().map(virtualRow => { diff --git a/tauri-app/src/pages/Settings/Settings.tsx b/tauri-app/src/pages/Settings/Settings.tsx index 1e49e36..ee7ae39 100644 --- a/tauri-app/src/pages/Settings/Settings.tsx +++ b/tauri-app/src/pages/Settings/Settings.tsx @@ -35,7 +35,7 @@ export default function Settings() { const clickTimerRef = useRef | null>(null); const formatBytes = (bytes: number) => { - if (bytes === 0) return '0 B'; + if (bytes === 0) {return '0 B';} const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); @@ -66,7 +66,7 @@ export default function Settings() { }; const handleDownloadAndInstall = async () => { - if (!updateInfo) return; + if (!updateInfo) {return;} setDownloading(true); setProgress(0); diff --git a/tauri-app/src/pages/VideoCreation/CoverDesign.tsx b/tauri-app/src/pages/VideoCreation/CoverDesign.tsx index e0a8338..2f98b8c 100644 --- a/tauri-app/src/pages/VideoCreation/CoverDesign.tsx +++ b/tauri-app/src/pages/VideoCreation/CoverDesign.tsx @@ -10,6 +10,10 @@ */ import { useState, useEffect, useRef } from 'react'; + +interface FileWithPath extends File { + path?: string; +} import { useCoverAvatarStore } from '../../store'; import { invoke } from '@tauri-apps/api/core'; import { save } from '@tauri-apps/plugin-dialog'; @@ -175,7 +179,7 @@ export default function CoverDesign() { // 本地上传背景图 const handleLocalBgUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (!file) return; + if (!file) {return;} // 释放旧的 Blob URL const oldBg = config.backgroundImage; @@ -184,7 +188,7 @@ export default function CoverDesign() { blobUrlsRef.current.delete(oldBg); } - const path = (file as any).path || (file as any).webkitRelativePath || ''; + const path = (file as FileWithPath).path || (file as FileWithPath).webkitRelativePath || ''; if (!path) { // Tauri 文件选择器通常有 path,如果没有则使用 Object URL const url = URL.createObjectURL(file); @@ -215,9 +219,10 @@ export default function CoverDesign() { // 组件卸载时释放所有 Blob URL useEffect(() => { + const urls = blobUrlsRef.current; return () => { - blobUrlsRef.current.forEach(url => URL.revokeObjectURL(url)); - blobUrlsRef.current.clear(); + urls.forEach(url => URL.revokeObjectURL(url)); + urls.clear(); }; }, []); @@ -240,7 +245,7 @@ export default function CoverDesign() { // 背景图/形象变化时自动保存 coverConfig(防抖,只监听视觉素材) useEffect(() => { - if (!projectId) return; + if (!projectId) {return;} const timer = setTimeout(() => { useProjectStore.getState().setCoverConfig({ template: config.template, @@ -251,6 +256,7 @@ export default function CoverDesign() { }); }, 300); return () => clearTimeout(timer); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.backgroundImage, config.avatarImage, projectId]); // 配置变化时重新渲染 Canvas @@ -681,7 +687,7 @@ export default function CoverDesign() { } else { toast.error(res.message || '导出失败'); } - } catch (e) { + } catch { toast.error('导出失败'); } }} diff --git a/tauri-app/src/pages/VideoCreation/VideoCompose.tsx b/tauri-app/src/pages/VideoCreation/VideoCompose.tsx index d5e3c2d..963abb0 100644 --- a/tauri-app/src/pages/VideoCreation/VideoCompose.tsx +++ b/tauri-app/src/pages/VideoCreation/VideoCompose.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { listen } from '@tauri-apps/api/event'; import { invoke } from '@tauri-apps/api/core'; -import { save } from '@tauri-apps/plugin-dialog'; +import { open, save } from '@tauri-apps/plugin-dialog'; import { exists } from '@tauri-apps/plugin-fs'; import { compositeApi } from '../../api/modules/videoComposite'; import { downloadFile } from '../../api/modules/videoCompose'; @@ -61,7 +61,7 @@ export default function VideoCompose() { const setBgmVolume = useProjectStore(state => state.setBgmVolume); const [, setBgmLoading] = useState(false); const [bgmModalOpen, setBgmModalOpen] = useState(false); - const localBgmRef = useRef(null); + // 打开弹窗时根据当前选中的 BGM 分类自动切换标签 useEffect(() => { @@ -73,7 +73,7 @@ export default function VideoCompose() { setBgmCategory('all'); } } - }, [bgmModalOpen]); + }, [bgmModalOpen, bgmList, bgmMusicId]); // 弹窗关闭时停止试听 useEffect(() => { @@ -159,15 +159,16 @@ export default function VideoCompose() { }, []); // 本地上传 BGM - const handleLocalBgmUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - const path = (file as any).path || ''; - if (path) { - setBgmMusic(-1, file.name, path); + const handleLocalBgmUpload = async () => { + const selected = await open({ + multiple: false, + filters: [{ name: '音频文件', extensions: ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'flac'] }], + }); + if (selected && typeof selected === 'string') { + const fileName = selected.split(/[\\/]/).pop() || '本地音频'; + setBgmMusic(-1, fileName, selected); + setBgmModalOpen(false); } - setBgmModalOpen(false); - e.target.value = ''; }; const shots = segments || []; @@ -261,7 +262,7 @@ export default function VideoCompose() { const resolvedCoverPath = coverPath; // 3. 调用 Rust 合成,直接输出到 products 目录 - let result = await compositeApi.synthesis({ + const result = await compositeApi.synthesis({ video_paths: videoPaths, cover_path: resolvedCoverPath, output_path: outputPath, @@ -295,7 +296,7 @@ export default function VideoCompose() { }, }); if (mixRes.code !== 200) { - console.warn('BGM 混合失败,使用无 BGM 版本:', mixRes.message); + toast.warning('BGM 混合失败,使用无 BGM 版本: ' + mixRes.message); } } @@ -791,7 +792,7 @@ export default function VideoCompose() {
- +
diff --git a/tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx b/tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx index b17740f..3848021 100644 --- a/tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx +++ b/tauri-app/src/pages/VideoCreation/VoiceSynthesis.tsx @@ -396,7 +396,7 @@ export default function VoiceSynthesis() { } finally { setIsGenerating(false); } - }, [projectId, segments, handleAlignAndClip, checkBalance, handleError, estimatedTtsPoints]); + }, [projectId, segments, handleAlignAndClip, checkBalance, handleError, estimatedTtsPoints, clearStepDirty]); const { navigate } = useNavigation(); diff --git a/tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts b/tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts index 69c265f..9a78e5a 100644 --- a/tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts +++ b/tauri-app/src/pages/VideoGeneration/hooks/useVideoGeneration.ts @@ -381,7 +381,7 @@ export function useVideoGeneration({ const baseVideoPoints = Math.ceil(totalAudioDuration) * videoMultiplier; // 系统素材额外收费:每个空镜使用的系统素材额外 2 积分 const systemMaterialCount = shots.filter((s: ScriptShot) => { - if (s.type !== 'empty_shot') return false; + if (s.type !== 'empty_shot') {return false;} const matched = materialMatchMap[String(s.id)]; return matched && (matched.url.startsWith('http://') || matched.url.startsWith('https://')); }).length; diff --git a/tauri-app/src/pages/VideoGeneration/index.tsx b/tauri-app/src/pages/VideoGeneration/index.tsx index 233f000..c307a36 100644 --- a/tauri-app/src/pages/VideoGeneration/index.tsx +++ b/tauri-app/src/pages/VideoGeneration/index.tsx @@ -163,7 +163,7 @@ export default function VideoGeneration() { } // 系统素材额外收费:每个空镜使用的系统素材额外 2 积分 const systemMaterialCount = shots.filter((s) => { - if (s.type !== 'empty_shot') return false; + if (s.type !== 'empty_shot') {return false;} const matched = materialMatchMap[String(s.id)]; return matched && (matched.url.startsWith('http://') || matched.url.startsWith('https://')); }).length; @@ -326,7 +326,7 @@ export default function VideoGeneration() { // 匹配成功后自动切换到对应镜头并预览,但只处理最新请求的结果, // 避免用户快速连点多个镜头时,后返回的请求把画面切回之前的镜头。 const handleMatchMaterial = async (shotId: string) => { - if (matchingShotIds.has(shotId)) return; + if (matchingShotIds.has(shotId)) {return;} setMatchingShotIds(prev => new Set(prev).add(shotId)); const requestId = ++matchRequestIdRef.current;