fix(viral-video): 分析/生成期间不再锁定页面其他控件(P0)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 53s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m6s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m3s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m21s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m31s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m44s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m11s
PR Automation / Auto Approve on CI Green (pull_request) Failing after 4m34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m40s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 6m51s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 10m25s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped

- 移除 canEditAssets 限制,STEP1 上传/音色/参考视频/参考音频等控件在分析图片期间全程可操作
- 移除 canEditStep2 限制,STEP2 融合方式/输入框/参数 Select 在生成文案期间全程可操作
- STEP3 4个参数 Select(视频比例/模型/清晰度/超分)生成视频前后均可改
- 分镜脚本 storyboard inline 编辑在生成视频前后均可改(locked=false)
- 只保留三个主按钮自身的 loading + disabled 防重复提交:
  - 分析图片按钮:step1_analyzing 时 disabled+spinner
  - 生成文案按钮:step2_generating 时 disabled+spinner
  - 生成视频按钮:step3_generating 时 disabled+spinner
- 图片列表项保留单图上传中的 uploading 锁(防止正在上传的图被删/移动)
- 无 pointer-events:none、无容器 disabled、无全屏遮罩
This commit is contained in:
Coze Agent
2026-10-02 13:03:08 +08:00
parent b31205e965
commit 0b6cf2f39b
@@ -560,8 +560,6 @@ const ViralVideoPage: React.FC = () => {
)
/* ── 图片 ── */
const canEditAssets = task.uiStep === "step1_upload" || task.uiStep === "failed"
const appendImagesFromFiles = useCallback(
async (files: FileList | File[]) => {
const remaining = 9 - task.images.length
@@ -964,10 +962,6 @@ const ViralVideoPage: React.FC = () => {
const outputUrl = task.job?.output_url || task.job?.result_video_url
const errMsg = task.videoError || task.job?.error_message || task.job?.error_msg
const canEditStep2 =
task.uiStep !== "step2_generating" &&
task.uiStep !== "step3_generating" &&
task.uiStep !== "step3_done"
const step2Enabled =
task.uiStep === "step1_done" ||
task.uiStep === "step2_generating" ||
@@ -1158,7 +1152,7 @@ const ViralVideoPage: React.FC = () => {
) {
return null
}
const locked = task.uiStep === "step3_generating" || task.uiStep === "step3_done"
const locked = false
const isMockPreview = sb === MOCK_STORYBOARD
return (
@@ -1446,17 +1440,12 @@ const ViralVideoPage: React.FC = () => {
<div className="vv-section-body" style={{ paddingTop: 4 }}>
{/* 图片按钮 */}
<div className="vv-action-row">
<button
className="vv-action-btn"
onClick={() => imgInputRef.current?.click()}
disabled={!canEditAssets}
>
<button className="vv-action-btn" onClick={() => imgInputRef.current?.click()}>
<UploadOutlined /> 本地上传
</button>
<button
className="vv-action-btn"
onClick={() => setAssetPicker({ open: true, kind: "image", multiple: true })}
disabled={!canEditAssets}
>
<FolderOpenOutlined /> 从素材库选择图片
</button>
@@ -1478,7 +1467,7 @@ const ViralVideoPage: React.FC = () => {
已选 <strong style={{ color: "#111827" }}>{task.images.length}</strong>/9
</span>
{task.images.length > 0 && (
<button className="vv-link-btn" onClick={clearImages} disabled={!canEditAssets}>
<button className="vv-link-btn" onClick={clearImages}>
清空图片
</button>
)}
@@ -1511,7 +1500,7 @@ const ViralVideoPage: React.FC = () => {
<button
className="vv-icon-btn"
onClick={() => moveImg(i, -1)}
disabled={i === 0 || im.uploading || !canEditAssets}
disabled={i === 0 || im.uploading}
title="上移"
>
<ArrowUpOutlined />
@@ -1519,7 +1508,7 @@ const ViralVideoPage: React.FC = () => {
<button
className="vv-icon-btn"
onClick={() => moveImg(i, 1)}
disabled={i === task.images.length - 1 || im.uploading || !canEditAssets}
disabled={i === task.images.length - 1 || im.uploading}
title="下移"
>
<ArrowDownOutlined />
@@ -1527,7 +1516,7 @@ const ViralVideoPage: React.FC = () => {
<button
className="vv-icon-btn vv-icon-btn-danger"
onClick={() => removeImg(im.localId)}
disabled={im.uploading || !canEditAssets}
disabled={!!im.uploading}
title="删除"
>
<CloseOutlined />
@@ -1546,11 +1535,7 @@ const ViralVideoPage: React.FC = () => {
<span className="vv-subblock-hint">最大 200MB · 最长 120 秒</span>
</div>
<div className="vv-action-row">
<button
className="vv-action-btn"
onClick={() => videoInputRef.current?.click()}
disabled={!canEditAssets}
>
<button className="vv-action-btn" onClick={() => videoInputRef.current?.click()}>
<UploadOutlined /> 本地上传视频
</button>
<button
@@ -1558,7 +1543,6 @@ const ViralVideoPage: React.FC = () => {
onClick={() =>
setTask({ voicePanel: task.voicePanel === "douyin" ? null : "douyin" })
}
disabled={!canEditAssets}
>
<LinkOutlined /> 抖音链接识别
</button>
@@ -1609,11 +1593,7 @@ const ViralVideoPage: React.FC = () => {
</div>
)}
</div>
<button
className="vv-link-btn"
onClick={removeRefVideo}
disabled={!canEditAssets}
>
<button className="vv-link-btn" onClick={removeRefVideo}>
删除重传
</button>
{task.refVideo.ossUrl && (
@@ -1627,7 +1607,6 @@ const ViralVideoPage: React.FC = () => {
key={s.value}
className={`vv-pill ${task.styleStrength === s.value ? "active" : ""}`}
onClick={() => setTask({ styleStrength: s.value })}
disabled={!canEditAssets}
>
{s.label}
</button>
@@ -1650,7 +1629,6 @@ const ViralVideoPage: React.FC = () => {
<button
className={`vv-audio-cell ${task.refAudio?.source === "preset" ? "selected" : ""}`}
onClick={() => setVoicePickerOpen(true)}
disabled={!canEditAssets}
>
<FileTextOutlined className="vv-audio-ic" />
<span>选择内置音色</span>
@@ -1658,7 +1636,6 @@ const ViralVideoPage: React.FC = () => {
<button
className={`vv-audio-cell ${task.voicePanel === "upload" ? "active" : ""} ${task.refAudio?.source === "upload" ? "selected" : ""}`}
onClick={() => voiceInputRef.current?.click()}
disabled={!canEditAssets}
>
<UploadOutlined className="vv-audio-ic" />
<span>本地上传</span>
@@ -1666,7 +1643,6 @@ const ViralVideoPage: React.FC = () => {
<button
className={`vv-audio-cell ${task.voicePanel === "library" ? "active" : ""} ${task.refAudio?.source === "library" ? "selected" : ""}`}
onClick={() => setAssetPicker({ open: true, kind: "voice", multiple: false })}
disabled={!canEditAssets}
>
<FolderOpenOutlined className="vv-audio-ic" />
<span>从素材库选择</span>
@@ -1674,7 +1650,6 @@ const ViralVideoPage: React.FC = () => {
<button
className={`vv-audio-cell ${task.voicePanel === "record" ? "active" : ""} ${task.refAudio?.source === "clone" ? "selected" : ""}`}
onClick={() => setCloneModalOpen(true)}
disabled={!canEditAssets}
>
<AudioMutedOutlined className="vv-audio-ic" />
<span>直接录音</span>
@@ -1694,11 +1669,7 @@ const ViralVideoPage: React.FC = () => {
<div className="vv-selected-audio">
<SoundOutlined style={{ color: "#7c3aed", marginRight: 6 }} />
<span style={{ flex: 1 }}>已选:{task.refAudio.name}</span>
<button
className="vv-link-btn"
onClick={() => setTask({ refAudio: null })}
disabled={!canEditAssets}
>
<button className="vv-link-btn" onClick={() => setTask({ refAudio: null })}>
移除
</button>
</div>
@@ -1810,7 +1781,6 @@ const ViralVideoPage: React.FC = () => {
key={f.value}
className={`vv-fusion-btn ${task.fusionLevel === f.value ? "active" : ""}`}
onClick={() => setTask({ fusionLevel: f.value })}
disabled={!canEditStep2}
>
<strong>{f.label}</strong>
{f.desc}
@@ -1832,7 +1802,6 @@ const ViralVideoPage: React.FC = () => {
}
value={task.userCopy}
onChange={(e) => setTask({ userCopy: e.target.value })}
disabled={!canEditStep2}
/>
</div>
)}
@@ -1848,7 +1817,6 @@ const ViralVideoPage: React.FC = () => {
placeholder="如:家具店、宠物店、装修公司"
value={task.industry}
onChange={(e) => setTask({ industry: e.target.value })}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1861,7 +1829,6 @@ const ViralVideoPage: React.FC = () => {
onChange={(v) => setTask({ targetCustomer: v })}
options={TARGET_CUSTOMERS.map((i) => ({ value: i, label: i }))}
allowClear
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1872,7 +1839,6 @@ const ViralVideoPage: React.FC = () => {
value={task.language}
onChange={(v) => setTask({ language: v })}
options={LANGUAGES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1885,7 +1851,6 @@ const ViralVideoPage: React.FC = () => {
value={task.persona || undefined}
onChange={(v) => setTask({ persona: v })}
options={PERSONAS.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1896,7 +1861,6 @@ const ViralVideoPage: React.FC = () => {
value={task.viralStructure}
onChange={(v) => setTask({ viralStructure: v })}
options={STRUCTURES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1907,7 +1871,6 @@ const ViralVideoPage: React.FC = () => {
value={task.marketingPurpose}
onChange={(v) => setTask({ marketingPurpose: v })}
options={PURPOSES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row" style={{ gridColumn: "1 / -1" }}>
@@ -1918,7 +1881,6 @@ const ViralVideoPage: React.FC = () => {
value={task.duration}
onChange={(v) => setTask({ duration: v })}
options={DURATIONS.map((n) => ({ value: n, label: `${n}秒` }))}
disabled={!canEditStep2}
/>
</div>
</div>
@@ -2002,7 +1964,6 @@ const ViralVideoPage: React.FC = () => {
value={task.videoRatio}
onChange={(v) => setTask({ videoRatio: v })}
options={RATIOS.map((r) => ({ value: r.v, label: r.v }))}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
<div className="vv-form-row">
@@ -2013,7 +1974,6 @@ const ViralVideoPage: React.FC = () => {
value={task.videoModel}
onChange={(v) => setTask({ videoModel: v })}
options={MODELS.map((m) => ({ value: m.v, label: m.label }))}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
<div className="vv-form-row">
@@ -2024,7 +1984,6 @@ const ViralVideoPage: React.FC = () => {
value={task.quality}
onChange={(v) => setTask({ quality: v })}
options={QUALITY_OPTIONS.map((m) => ({ value: m.v, label: m.label }))}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
<div className="vv-form-row">
@@ -2035,7 +1994,6 @@ const ViralVideoPage: React.FC = () => {
value={task.upscale}
onChange={(v) => setTask({ upscale: v })}
options={UPSCALE_OPTIONS.map((m) => ({ value: m.v, label: m.label }))}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
</div>