diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.css b/apps/web/src/pages/editing-planner/EditingPlanner.css index 5f5b6b1ab..6891b53a6 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.css +++ b/apps/web/src/pages/editing-planner/EditingPlanner.css @@ -1,1183 +1,1092 @@ /** - * 剪辑计划编辑器 - V21 设计系统样式 - * 三栏布局:素材面板 / 时间线 / 片段属性 - * 统一使用 CSS 变量,支持深色/浅色主题 + * 剪辑计划编辑器 — V8 原型 1:1 还原样式 + * 颜色:背景 #0a0a14、主色 #6c5ce7、卡片 #13131f + * 布局:4行(顶栏42 + 模式栏48 + 三栏主体flex + 底栏40) + * 面板宽度:左220 / 右260 */ -@import "../../styles/global.css"; -/* ============================================================ - 页面容器 - ============================================================ */ -.ep-page { +/* ═══════════════════════════════════════ + 根容器 — 全屏四行布局 + ═══════════════════════════════════════ */ + +.ep-v8-root { display: flex; flex-direction: column; - height: 100%; - background: var(--bg-secondary); + height: 100vh; + background: #0a0a14; + color: #e0e0e0; + font-family: "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 12px; + overflow: hidden; } -/* ── 顶部工具栏 ─────────────────────────────────────────────── */ -.ep-toolbar { +/* ═══════════════════════════════════════ + 第1行:顶栏 42px + ═══════════════════════════════════════ */ + +.ep-top-bar { + height: 42px; + min-height: 42px; + background: #161625; + border-bottom: 1px solid #222; display: flex; align-items: center; justify-content: space-between; - padding: var(--space-md) var(--space-lg); - background: var(--bg-primary); - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; - gap: var(--space-md); - flex-wrap: wrap; + padding: 0 16px; } -.ep-toolbar-left { +.ep-top-bar-left { display: flex; align-items: center; - gap: var(--space-md); + gap: 8px; } -.ep-toolbar-center { - display: flex; - align-items: center; - gap: var(--space-sm); +.ep-logo { + font-size: 18px; } -.ep-toolbar-template-name { - font-size: var(--font-size-sm); +.ep-app-title { + font-size: 14px; font-weight: 600; - color: var(--text-primary); - padding: 4px 12px; - background: var(--bg-secondary); - border-radius: var(--radius-md); + color: #fff; } -.ep-toolbar-plan-badge { +.ep-divider { + color: #333; + margin: 0 4px; +} + +.ep-template-name { + color: #888; font-size: 12px; - font-weight: 600; - color: var(--success-color); - background: var(--success-soft); - border: 1px solid var(--success-border); - padding: 2px 8px; - border-radius: 999px; } -.ep-toolbar-right { +.ep-top-bar-right { display: flex; align-items: center; - gap: var(--space-sm); + gap: 8px; } -/* ── 模式切换 — 圆角胶囊样式 ────────────────────────────────── */ -.ep-mode-switch { - display: flex; - gap: 6px; - padding: 4px; - background: var(--bg-secondary); - border-radius: 999px; - border: 1px solid var(--border-color); -} - -.ep-mode-btn { - padding: 6px 16px; - border-radius: 999px; - border: 1px solid transparent; - background: transparent; - color: var(--text-secondary); - font-size: var(--font-size-sm); +/* 按钮 */ +.ep-btn { + padding: 6px 14px; + border-radius: 6px; + font-size: 12px; font-weight: 500; + border: none; cursor: pointer; - transition: var(--transition-all); - white-space: nowrap; - user-select: none; + transition: all 0.2s; display: flex; align-items: center; gap: 4px; - line-height: 1.4; +} + +.ep-btn-primary { + background: #6c5ce7; + color: #fff; +} + +.ep-btn-primary:hover { + background: #7c6cf7; +} + +.ep-btn-secondary { + background: #1e1e2e; + color: #ccc; + border: 1px solid #2a2a4a; +} + +.ep-btn-secondary:hover { + background: #2a2a3a; + border-color: #6c5ce7; +} + +.ep-btn-sm { + padding: 4px 10px; + font-size: 11px; +} + +.ep-btn-danger { + background: #ff4757; + color: #fff; +} + +.ep-btn-danger:hover { + background: #ff6b81; +} + +/* ═══════════════════════════════════════ + 第2行:模式栏 48px + ═══════════════════════════════════════ */ + +.ep-mode-bar { + height: 48px; + min-height: 48px; + background: #13131f; + border-bottom: 1px solid #222; + display: flex; + align-items: center; + padding: 0 16px; + gap: 4px; +} + +.ep-mode-btn { + position: relative; + height: 36px; + padding: 0 16px; + background: transparent; + border: none; + color: #888; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; + border-radius: 6px; + transition: all 0.2s; } .ep-mode-btn:hover { - color: var(--primary-color); - background: var(--primary-soft); + color: #ccc; + background: rgba(108, 92, 231, 0.1); } .ep-mode-btn.active { - background: var(--primary-soft); - border-color: var(--primary-color); - color: var(--primary-color); - font-weight: 600; + color: #6c5ce7; + background: rgba(108, 92, 231, 0.15); } -/* ============================================================ - 三栏主体布局 - ============================================================ */ -.ep-body { - display: flex; +.ep-mode-btn.active::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 60%; + height: 2px; + background: #6c5ce7; + border-radius: 1px; + transition: all 0.3s ease; +} + +.ep-mode-icon { + font-size: 14px; +} + +/* ═══════════════════════════════════════ + 第3行:三栏主体 + ═══════════════════════════════════════ */ + +.ep-main-body { flex: 1; + display: flex; overflow: hidden; min-height: 0; } -/* ── 左侧素材面板 ───────────────────────────────────────────── */ -.ep-left { - width: 280px; - flex-shrink: 0; +/* ─── 左栏 220px ─── */ + +.ep-left-panel { + width: 220px; + min-width: 220px; + background: #0e0e1a; + border-right: 1px solid #222; display: flex; flex-direction: column; - background: var(--bg-primary); - border-right: 1px solid var(--border-color); overflow: hidden; } -/* Tab 切换 */ -.ep-left-tabs { - display: flex; - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; +/* 搜索 */ +.ep-search-wrap { + padding: 8px; } -.ep-left-tab { - flex: 1; - padding: 10px 12px; - border: none; - background: transparent; - color: var(--text-secondary); - font-size: var(--font-size-sm); - font-weight: 500; - cursor: pointer; - transition: var(--transition-all); - text-align: center; - position: relative; +.ep-search-input { + width: 100%; + height: 30px; + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 6px; + padding: 0 10px 0 30px; + color: #e0e0e0; + font-size: 12px; + outline: none; + transition: border-color 0.2s; } -.ep-left-tab:hover { - color: var(--text-primary); - background: var(--bg-secondary); +.ep-search-input:focus { + border-color: #6c5ce7; } -.ep-left-tab.active { - color: var(--primary-color); - font-weight: 600; -} - -.ep-left-tab.active::after { - content: ""; +.ep-search-icon { position: absolute; - bottom: 0; - left: 16px; - right: 16px; - height: 2px; - background: var(--primary-color); - border-radius: 2px 2px 0 0; + left: 10px; + top: 50%; + transform: translateY(-50%); + color: #555; + font-size: 12px; + pointer-events: none; } -.ep-left-header { - padding: var(--space-md); - border-bottom: 1px solid var(--border-color); +/* Chip 筛选 */ +.ep-filter-chips { display: flex; - flex-direction: column; - gap: var(--space-sm); - flex-shrink: 0; + gap: 6px; + padding: 0 8px 8px; + flex-wrap: wrap; } -.ep-left-list { +.ep-filter-chip { + padding: 3px 10px; + border-radius: 12px; + font-size: 11px; + background: #13131f; + color: #888; + border: 1px solid #2a2a4a; + cursor: pointer; + transition: all 0.2s; +} + +.ep-filter-chip:hover { + border-color: #6c5ce7; + color: #ccc; +} + +.ep-filter-chip.active { + background: #6c5ce7; + color: #fff; + border-color: #6c5ce7; +} + +/* 模板列表 */ +.ep-template-list { flex: 1; overflow-y: auto; - padding: var(--space-sm); + padding: 0 8px 8px; } -.ep-left-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: var(--space-2xl) var(--space-md); - text-align: center; - color: var(--text-secondary); +.ep-template-list::-webkit-scrollbar { + width: 4px; } -.ep-left-empty-icon { - font-size: 40px; - margin-bottom: var(--space-sm); - opacity: 0.5; +.ep-template-list::-webkit-scrollbar-thumb { + background: #333; + border-radius: 2px; } -.ep-left-empty p { - margin: 0; - font-size: var(--font-size-sm); -} - -/* 模板卡片 */ .ep-template-card { - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 12px; - margin-bottom: var(--space-sm); + background: #13131f; + border: 1px solid #222; + border-radius: 8px; + padding: 10px; + margin-bottom: 8px; cursor: pointer; - transition: var(--transition-all); + transition: all 0.2s; } .ep-template-card:hover { - border-color: var(--primary-color); - box-shadow: var(--shadow-sm); + border-color: #6c5ce7; + background: #1a1a2a; } -.ep-template-card.selected { - border-color: var(--primary-color); - background: var(--primary-soft); +.ep-template-card.active { + border-color: #6c5ce7; + background: rgba(108, 92, 231, 0.1); +} + +.ep-template-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; } .ep-template-card-name { - font-size: 14px; + font-size: 12px; font-weight: 600; - color: var(--text-primary); - margin: 0 0 6px; + color: #fff; +} + +.ep-template-card-mode { + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + background: rgba(108, 92, 231, 0.2); + color: #6c5ce7; +} + +.ep-template-card-meta { + display: flex; + gap: 8px; + font-size: 10px; + color: #666; } .ep-template-card-tags { display: flex; gap: 4px; + margin-top: 6px; flex-wrap: wrap; } -.ep-template-card-meta { - font-size: 12px; - color: var(--text-secondary); - margin-top: 6px; +.ep-template-tag { + font-size: 10px; + padding: 1px 6px; + border-radius: 3px; + background: #1a1a2a; + color: #888; } -/* 素材卡片(可拖拽) */ -.ep-asset-card { +/* Loading / Empty */ +.ep-loading, +.ep-empty { display: flex; + flex-direction: column; align-items: center; - gap: var(--space-sm); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 10px 12px; - margin-bottom: var(--space-sm); - cursor: grab; - transition: var(--transition-all); - user-select: none; + justify-content: center; + padding: 32px 16px; + color: #555; + font-size: 12px; + gap: 8px; } -.ep-asset-card:hover { - border-color: var(--primary-color); - box-shadow: var(--shadow-sm); +/* ─── 中栏 flex-1 ─── */ + +.ep-center-col { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + overflow: hidden; } -.ep-asset-card:active { - cursor: grabbing; - opacity: 0.7; -} - -.ep-asset-card-icon { - font-size: 24px; - flex-shrink: 0; - width: 36px; - height: 36px; +/* 上半部:预览 + 封面 (320px) */ +.ep-preview-area { + height: 320px; + min-height: 280px; display: flex; align-items: center; justify-content: center; - background: var(--bg-secondary); - border-radius: var(--radius-sm); + gap: 16px; + padding: 16px; + background: #0a0a14; + border-bottom: 1px solid #222; } -.ep-asset-card-info { - flex: 1; - min-width: 0; -} - -.ep-asset-card-name { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; +/* 手机模型预览 */ +.ep-phone-preview { + width: 150px; + height: 267px; + background: #000; + border-radius: 16px; + border: 2px solid #333; + position: relative; overflow: hidden; - text-overflow: ellipsis; + flex-shrink: 0; } -.ep-asset-card-meta { - font-size: 11px; - color: var(--text-secondary); - margin-top: 2px; +.ep-phone-status-bar { + height: 20px; + background: #111; display: flex; align-items: center; - gap: 4px; - flex-wrap: wrap; + justify-content: space-between; + padding: 0 10px; + font-size: 9px; + color: #666; } -.ep-asset-card-tags { - display: inline-flex; - gap: 3px; -} - -.ep-asset-card-tag { - padding: 1px 4px; - background: var(--bg-secondary); - border-radius: 3px; - font-size: 10px; -} - -.ep-asset-card-drag-hint { - color: var(--text-secondary); - font-size: 16px; - opacity: 0.4; - flex-shrink: 0; - transition: var(--transition-all); -} - -.ep-asset-card:hover .ep-asset-card-drag-hint { - opacity: 0.8; -} - -.ep-left-footer { - padding: var(--space-sm) var(--space-md); - border-top: 1px solid var(--border-color); - flex-shrink: 0; -} - -.ep-new-template-btn { - width: 100%; - padding: 8px; - border: 1px dashed var(--border-color); - border-radius: var(--radius-md); - background: transparent; - color: var(--text-secondary); - font-size: var(--font-size-sm); - cursor: pointer; - transition: var(--transition-all); -} - -.ep-new-template-btn:hover { - border-color: var(--primary-color); - color: var(--primary-color); - background: var(--primary-soft); -} - -/* 素材 Tab — AssetSelector 容器 */ -.ep-left-media { +.ep-phone-content { flex: 1; - overflow-y: auto; - min-height: 0; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #1a1a2e, #0a0a14); + height: calc(100% - 20px); + position: relative; } -.ep-left-media-footer { - padding: var(--space-sm) var(--space-md); - border-top: 1px solid var(--border-color); +.ep-phone-play-btn { + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(108, 92, 231, 0.8); + border: none; + color: #fff; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.ep-phone-play-btn:hover { + background: #6c5ce7; + transform: scale(1.1); +} + +.ep-phone-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: #222; +} + +.ep-phone-progress-fill { + height: 100%; + background: #6c5ce7; + transition: width 0.3s; +} + +.ep-phone-clip-label { + position: absolute; + bottom: 8px; + left: 0; + right: 0; + text-align: center; + font-size: 9px; + color: #888; +} + +/* 封面预览 */ +.ep-cover-preview { + width: 150px; + height: 267px; + background: #13131f; + border-radius: 8px; + border: 1px solid #2a2a4a; + position: relative; + overflow: hidden; flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; } -/* ── 中间时间线 ──────────────────────────────────────────────── */ -.ep-center { +.ep-cover-image { + width: 100%; + height: 100%; + background: linear-gradient(135deg, #1a1a2e, #13131f); + display: flex; + align-items: center; + justify-content: center; + color: #555; + font-size: 11px; +} + +.ep-cover-label { + position: absolute; + bottom: 8px; + left: 0; + right: 0; + text-align: center; + font-size: 9px; + color: #888; +} + +/* 封面方案按钮(竖排4个) */ +.ep-cover-tags { + display: flex; + flex-direction: column; + gap: 6px; +} + +.ep-cover-tag { + padding: 6px 12px; + border-radius: 6px; + font-size: 11px; + background: #13131f; + color: #888; + border: 1px solid #2a2a4a; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; +} + +.ep-cover-tag:hover { + border-color: #6c5ce7; + color: #ccc; +} + +.ep-cover-tag.active { + background: #6c5ce7; + color: #fff; + border-color: #6c5ce7; +} + +/* ─── 下半部:水平时间线 ─── */ + +.ep-timeline-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; - min-width: 0; - background: var(--bg-primary); + background: #0e0e1a; } -/* 可视化时长条 */ -.ep-timeline-bar { - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; -} - -.ep-timeline-bar-label { - font-size: var(--font-size-sm); - font-weight: 600; - color: var(--text-primary); - margin-bottom: 8px; - display: flex; - align-items: center; - gap: var(--space-sm); -} - -.ep-timeline-bar-duration { - font-weight: 400; - color: var(--text-secondary); - font-size: 12px; -} - -.ep-timeline-bar-track { - display: flex; - height: 28px; - background: var(--bg-secondary); - border-radius: var(--radius-sm); - overflow: hidden; - gap: 2px; -} - -.ep-timeline-bar-segment { - height: 100%; - border-radius: 3px; - transition: var(--transition-all); - min-width: 8px; - position: relative; -} - -.ep-timeline-bar-segment:hover { - opacity: 0.85; - transform: scaleY(1.1); -} - -.ep-timeline-bar-empty { - width: 100%; - display: flex; - align-items: center; - justify-content: center; - color: var(--text-secondary); - font-size: 12px; - opacity: 0.6; -} - -/* 时间线头部 */ .ep-timeline-header { display: flex; align-items: center; justify-content: space-between; - padding: var(--space-md) var(--space-lg); - flex-shrink: 0; + padding: 6px 12px; + border-bottom: 1px solid #1a1a2a; } -.ep-timeline-header h3 { - margin: 0; - font-size: var(--font-size-base); - font-weight: var(--font-weight-semibold); - color: var(--text-primary); -} - -/* 片段列表 */ -.ep-timeline-list { - flex: 1; - overflow-y: auto; - padding: 0 var(--space-lg) var(--space-lg); - display: flex; - flex-direction: column; - gap: var(--space-sm); -} - -/* 空状态拖放区 */ -.ep-timeline-empty-drop { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: var(--space-2xl); - border: 2px dashed var(--border-color); - border-radius: var(--radius-lg); - text-align: center; - color: var(--text-secondary); - transition: var(--transition-all); - min-height: 120px; -} - -.ep-timeline-empty-drop.active { - border-color: var(--primary-color); - background: var(--primary-soft); - color: var(--primary-color); -} - -.ep-timeline-empty-drop .ep-timeline-empty-icon { - font-size: 40px; - margin-bottom: var(--space-sm); - opacity: 0.5; -} - -.ep-timeline-empty-drop p { - margin: 0 0 4px; - font-size: var(--font-size-sm); - font-weight: 500; -} - -.ep-timeline-empty-drop span { +.ep-timeline-title { font-size: 12px; - opacity: 0.7; + font-weight: 600; + color: #fff; } -/* 拖放指示器 */ -.ep-timeline-drop-indicator { - height: 3px; - background: var(--primary-color); - border-radius: 2px; - margin: -2px 0; - animation: ep-drop-pulse 0.6s ease infinite alternate; +.ep-timeline-actions { + display: flex; + gap: 4px; } -@keyframes ep-drop-pulse { - from { - opacity: 0.6; - } - to { - opacity: 1; - } +.ep-timeline-action-btn { + padding: 3px 8px; + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 4px; + color: #888; + font-size: 11px; + cursor: pointer; + transition: all 0.2s; } -/* 片段卡片 */ -.ep-clip-card { +.ep-timeline-action-btn:hover { + border-color: #6c5ce7; + color: #ccc; +} + +/* 时间标尺 */ +.ep-time-ruler { + height: 20px; + background: #13131f; + border-bottom: 1px solid #1a1a2a; display: flex; align-items: center; - gap: var(--space-sm); - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - padding: 10px 12px; + padding: 0 12px; + overflow: hidden; +} + +.ep-time-ruler-inner { + display: flex; + align-items: center; + height: 100%; + position: relative; +} + +.ep-time-mark { + position: absolute; + font-size: 9px; + color: #555; + top: 2px; +} + +.ep-time-mark::after { + content: ""; + position: absolute; + bottom: -4px; + left: 0; + width: 1px; + height: 6px; + background: #333; +} + +/* 水平片段轨道 */ +.ep-clip-track { + flex: 1; + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + overflow-x: auto; + overflow-y: hidden; +} + +.ep-clip-track::-webkit-scrollbar { + height: 4px; +} + +.ep-clip-track::-webkit-scrollbar-thumb { + background: #333; + border-radius: 2px; +} + +/* 水平片段卡片 */ +.ep-clip-card { + width: 100px; + min-width: 100px; + height: 100px; + background: #13131f; + border: 1px solid #222; + border-radius: 8px; cursor: pointer; - transition: var(--transition-all); - user-select: none; + transition: all 0.2s; + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; } .ep-clip-card:hover { - border-color: var(--primary-color); - box-shadow: var(--shadow-sm); + border-color: #444; + transform: translateY(-2px); } .ep-clip-card.selected { - border-color: var(--primary-color); - background: var(--primary-soft); - box-shadow: 0 0 0 1px var(--primary-color); + border-color: #6c5ce7; + box-shadow: 0 0 8px rgba(108, 92, 231, 0.3); } .ep-clip-card.drag-over { - border-color: var(--primary-color); - border-style: dashed; - background: var(--primary-soft); + border-color: #6c5ce7; + background: rgba(108, 92, 231, 0.1); } -.ep-clip-card[draggable="true"]:active { - opacity: 0.6; -} - -.ep-clip-drag { - cursor: grab; - color: var(--text-secondary); - font-size: 16px; - user-select: none; - flex-shrink: 0; +.ep-clip-card.dragging { opacity: 0.5; - transition: var(--transition-all); } -.ep-clip-card:hover .ep-clip-drag { - opacity: 1; -} - -.ep-clip-drag:active { - cursor: grabbing; -} - -.ep-clip-index { - width: 26px; - height: 26px; - border-radius: var(--radius-sm); - color: #fff; - font-size: 12px; - font-weight: 700; +.ep-clip-thumbnail { + height: 56px; + background: linear-gradient(135deg, #1a1a2e, #0e0e1a); display: flex; align-items: center; justify-content: center; + font-size: 20px; flex-shrink: 0; } .ep-clip-info { + padding: 4px 6px; flex: 1; - min-width: 0; + display: flex; + flex-direction: column; + justify-content: space-between; } -.ep-clip-info-top { +.ep-clip-name { + font-size: 10px; + color: #ccc; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ep-clip-duration { + font-size: 9px; + color: #666; +} + +.ep-clip-remove { + position: absolute; + top: 2px; + right: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + background: rgba(255, 71, 87, 0.8); + border: none; + color: #fff; + font-size: 10px; + cursor: pointer; + display: none; + align-items: center; + justify-content: center; + line-height: 1; +} + +.ep-clip-card:hover .ep-clip-remove { + display: flex; +} + +/* 空轨道 */ +.ep-track-empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #444; + font-size: 12px; + min-height: 100px; +} + +/* 一镜到底提示 */ +.ep-one-take-hint { + padding: 4px 12px; + background: rgba(255, 165, 2, 0.1); + border-bottom: 1px solid rgba(255, 165, 2, 0.2); + color: #ffa502; + font-size: 11px; + text-align: center; +} + +/* ─── 右栏 260px ─── */ + +.ep-right-panel { + width: 260px; + min-width: 260px; + background: #0e0e1a; + border-left: 1px solid #222; + overflow-y: auto; + padding: 12px; +} + +.ep-right-panel::-webkit-scrollbar { + width: 4px; +} + +.ep-right-panel::-webkit-scrollbar-thumb { + background: #333; + border-radius: 2px; +} + +/* 设置区块 */ +.ep-settings-section { + margin-bottom: 16px; +} + +.ep-section-title { + font-size: 12px; + font-weight: 600; + color: #fff; + margin-bottom: 10px; display: flex; align-items: center; gap: 6px; +} + +.ep-section-icon { + font-size: 14px; +} + +/* 表单字段 */ +.ep-field { + margin-bottom: 8px; +} + +.ep-field-label { + display: block; + font-size: 11px; + color: #888; margin-bottom: 4px; } -.ep-clip-type-icon { - font-size: 14px; - flex-shrink: 0; -} - -.ep-clip-script { - font-size: 13px; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; -} - -.ep-clip-script-empty { - color: var(--text-secondary); - font-style: italic; - opacity: 0.6; -} - -.ep-clip-info-bottom { - display: flex; - align-items: center; - gap: 6px; -} - -.ep-clip-duration-bar { - flex: 1; - height: 4px; - background: var(--bg-secondary); - border-radius: 2px; - overflow: hidden; - max-width: 100px; -} - -.ep-clip-duration-fill { - height: 100%; - border-radius: 2px; - transition: width 0.2s ease; -} - -.ep-clip-duration-text { +.ep-form-input, +.ep-form-select { + width: 100%; + height: 28px; + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 4px; + padding: 0 8px; + color: #e0e0e0; font-size: 11px; - color: var(--text-secondary); - font-weight: 600; - white-space: nowrap; + outline: none; + transition: border-color 0.2s; } -.ep-clip-asset-badge { - font-size: 12px; - opacity: 0.7; +.ep-form-input:focus, +.ep-form-select:focus { + border-color: #6c5ce7; } -.ep-clip-transition-badge { - font-size: 10px; - color: var(--primary-color); - background: var(--primary-soft); - padding: 1px 6px; - border-radius: 999px; - white-space: nowrap; -} - -.ep-clip-actions { - flex-shrink: 0; -} - -.ep-clip-action-btn { - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--text-secondary); - font-size: 14px; +.ep-form-select { cursor: pointer; - border-radius: var(--radius-sm); +} + +/* Toggle 开关 */ +.ep-toggle-row { display: flex; align-items: center; - justify-content: center; - transition: var(--transition-all); - opacity: 0; + justify-content: space-between; + margin-bottom: 8px; } -.ep-clip-card:hover .ep-clip-action-btn { - opacity: 1; +.ep-toggle-label { + font-size: 11px; + color: #888; } -.ep-clip-action-btn:hover { - background: var(--error-soft); - color: var(--error-color); +.ep-toggle { + width: 36px; + height: 20px; + background: #2a2a4a; + border-radius: 10px; + position: relative; + cursor: pointer; + transition: background 0.2s; } -/* ============================================================ - 右侧片段属性面板 - ============================================================ */ -.ep-right { - width: 300px; - flex-shrink: 0; +.ep-toggle.active { + background: #6c5ce7; +} + +.ep-toggle-knob { + width: 16px; + height: 16px; + background: #fff; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; + transition: left 0.2s; +} + +.ep-toggle.active .ep-toggle-knob { + left: 18px; +} + +/* 滑块 */ +.ep-slider-row { display: flex; - flex-direction: column; - background: var(--bg-primary); - border-left: 1px solid var(--border-color); - overflow-y: auto; -} - -.ep-right-section { - padding: var(--space-md); - border-bottom: 1px solid var(--border-color); -} - -.ep-right-section:last-child { - border-bottom: none; -} - -.ep-right-section h3 { - margin: 0 0 var(--space-md); - font-size: var(--font-size-base); - font-weight: var(--font-weight-semibold); - color: var(--text-primary); -} - -/* 空状态 */ -.ep-clip-props-empty { - display: flex; - flex-direction: column; align-items: center; - justify-content: center; - padding: var(--space-2xl); - text-align: center; - color: var(--text-secondary); + gap: 8px; +} + +.ep-slider { flex: 1; + -webkit-appearance: none; + height: 4px; + background: #2a2a4a; + border-radius: 2px; + outline: none; } -.ep-clip-props-empty-icon { - font-size: 48px; - margin-bottom: var(--space-md); - opacity: 0.4; +.ep-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 12px; + height: 12px; + background: #6c5ce7; + border-radius: 50%; + cursor: pointer; } -.ep-clip-props-empty h3 { - margin: 0 0 4px; - font-size: var(--font-size-base); - color: var(--text-primary); +.ep-slider-value { + font-size: 11px; + color: #888; + min-width: 30px; + text-align: right; } -.ep-clip-props-empty p { - margin: 0; - font-size: var(--font-size-sm); -} - -.ep-clip-props-summary { - margin-top: var(--space-lg); +/* 样式按钮组 */ +.ep-style-btns { display: flex; - gap: var(--space-lg); -} - -.ep-clip-props-summary-item { - display: flex; - flex-direction: column; - align-items: center; gap: 4px; } -.ep-clip-props-summary-label { - font-size: 12px; - color: var(--text-secondary); -} - -.ep-clip-props-summary-value { - font-size: 20px; - font-weight: 700; - color: var(--text-primary); -} - -/* 片段信息头 */ -.ep-clip-props-header { - display: flex; - align-items: center; - gap: var(--space-sm); -} - -.ep-clip-props-header-icon { - font-size: 28px; -} - -.ep-clip-props-header h3 { - margin: 0 0 2px; - font-size: var(--font-size-base); -} - -.ep-clip-props-header-type { - font-size: 12px; - color: var(--text-secondary); -} - -/* 字段 */ -.ep-clip-props-field { - margin-bottom: var(--space-sm); -} - -.ep-clip-props-field:last-child { - margin-bottom: 0; -} - -.ep-clip-props-field-hint { - font-size: 11px; - color: var(--text-secondary); - text-align: right; - margin-top: 4px; -} - -.ep-clip-props-label { - display: block; - font-size: var(--font-size-sm); - color: var(--text-secondary); - margin-bottom: 6px; -} - -/* 文案文本框 */ -.ep-clip-props-textarea { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-secondary); - color: var(--text-primary); - font-size: var(--font-size-sm); - font-family: inherit; - resize: vertical; - transition: var(--transition-all); - line-height: 1.5; -} - -.ep-clip-props-textarea:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 2px var(--primary-soft); -} - -.ep-clip-props-textarea::placeholder { - color: var(--text-secondary); - opacity: 0.6; -} - -/* 时长控制 */ -.ep-clip-props-duration-control { - display: flex; - align-items: center; - gap: var(--space-sm); -} - -.ep-clip-props-range { - flex: 1; - height: 4px; - -webkit-appearance: none; - appearance: none; - background: var(--bg-secondary); - border-radius: 2px; - outline: none; -} - -.ep-clip-props-range::-webkit-slider-thumb { - -webkit-appearance: none; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--primary-color); - cursor: pointer; - border: 2px solid var(--bg-primary); - box-shadow: var(--shadow-sm); -} - -.ep-clip-props-range::-moz-range-thumb { - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--primary-color); - cursor: pointer; - border: 2px solid var(--bg-primary); - box-shadow: var(--shadow-sm); -} - -.ep-clip-props-duration-value { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); - min-width: 36px; - text-align: right; -} - -/* 转场效果网格 */ -.ep-clip-props-transition-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 6px; -} - -.ep-clip-props-transition-btn { - padding: 6px 8px; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-primary); - color: var(--text-secondary); +.ep-style-btn { + width: 28px; + height: 28px; + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 4px; + color: #888; font-size: 12px; cursor: pointer; - transition: var(--transition-all); - text-align: center; -} - -.ep-clip-props-transition-btn:hover { - border-color: var(--primary-color); - color: var(--primary-color); -} - -.ep-clip-props-transition-btn.active { - background: var(--primary-soft); - border-color: var(--primary-color); - color: var(--primary-color); - font-weight: 600; -} - -/* 关联素材 */ -.ep-clip-props-asset-linked { - display: flex; - align-items: center; - gap: var(--space-sm); - padding: 8px 12px; - background: var(--bg-secondary); - border-radius: var(--radius-md); - border: 1px solid var(--border-color); -} - -.ep-clip-props-asset-icon { - font-size: 20px; -} - -.ep-clip-props-asset-name { - flex: 1; - font-size: 13px; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.ep-clip-props-asset-unlink { - width: 20px; - height: 20px; - border: none; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; + transition: all 0.2s; +} + +.ep-style-btn:hover { + border-color: #6c5ce7; + color: #ccc; +} + +.ep-style-btn.active { + background: rgba(108, 92, 231, 0.2); + border-color: #6c5ce7; + color: #6c5ce7; +} + +/* AI推荐按钮 */ +.ep-ai-recommend-btn { + width: 100%; + padding: 8px; + background: linear-gradient(135deg, #6c5ce7, #a29bfe); + border: none; + border-radius: 6px; + color: #fff; font-size: 12px; - transition: var(--transition-all); + cursor: pointer; + transition: all 0.2s; + margin-top: 8px; } -.ep-clip-props-asset-unlink:hover { - background: var(--error-soft); - color: var(--error-color); +.ep-ai-recommend-btn:hover { + opacity: 0.9; + transform: translateY(-1px); } -.ep-clip-props-asset-empty { +/* 片段详情卡片 */ +.ep-clip-detail { + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 8px; padding: 12px; +} + +.ep-clip-detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.ep-clip-detail-name { + font-size: 12px; + font-weight: 600; + color: #fff; +} + +.ep-clip-detail-type { + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + background: rgba(108, 92, 231, 0.2); + color: #6c5ce7; +} + +.ep-clip-detail-field { + margin-bottom: 8px; +} + +.ep-clip-detail-label { + font-size: 10px; + color: #666; + margin-bottom: 3px; +} + +.ep-clip-detail-value { + font-size: 11px; + color: #ccc; +} + +.ep-clip-detail-row { + display: flex; + gap: 8px; + align-items: center; +} + +.ep-duration-input { + width: 60px; + height: 24px; + background: #0a0a14; + border: 1px solid #2a2a4a; + border-radius: 4px; + padding: 0 6px; + color: #e0e0e0; + font-size: 11px; + outline: none; text-align: center; - color: var(--text-secondary); - font-size: var(--font-size-sm); - background: var(--bg-secondary); - border-radius: var(--radius-md); - border: 1px dashed var(--border-color); } -.ep-clip-props-asset-empty p { - margin: 0; +.ep-duration-input:focus { + border-color: #6c5ce7; } -/* ============================================================ - 保存 / 生成弹窗 - ============================================================ */ +/* ═══════════════════════════════════════ + 第4行:底栏 40px + ═══════════════════════════════════════ */ + +.ep-status-bar { + height: 40px; + min-height: 40px; + background: #161625; + border-top: 1px solid #222; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 16px; + font-size: 11px; + color: #888; +} + +.ep-status-left, +.ep-status-right { + display: flex; + align-items: center; + gap: 8px; +} + +.ep-status-sep { + color: #333; +} + +/* ═══════════════════════════════════════ + 弹窗补充样式 + ═══════════════════════════════════════ */ + .ep-modal-field { - margin-bottom: var(--space-md); -} - -.ep-modal-field:last-child { - margin-bottom: 0; + margin-bottom: 12px; } .ep-modal-field label { display: block; - margin-bottom: 6px; - font-size: var(--font-size-sm); - font-weight: 500; - color: var(--text-secondary); + font-size: 12px; + color: #888; + margin-bottom: 4px; } .ep-modal-info { - display: flex; - align-items: center; - gap: var(--space-sm); - padding: 10px 14px; - background: var(--bg-secondary); - border-radius: var(--radius-md); - font-size: var(--font-size-sm); - color: var(--text-secondary); - margin-bottom: var(--space-md); + padding: 8px 12px; + background: #13131f; + border-radius: 6px; + font-size: 12px; + color: #888; + margin-top: 8px; } -.ep-modal-info strong { - color: var(--text-primary); -} - -/* ============================================================ - 简易 Toast 提示 - ============================================================ */ -.ep-toast { - position: fixed; - top: 24px; - left: 50%; - transform: translateX(-50%); - padding: 10px 24px; - border-radius: var(--radius-md); - font-size: var(--font-size-sm); - font-weight: 500; - z-index: 1000; - animation: ep-toast-in 0.3s ease; - pointer-events: none; - box-shadow: var(--shadow-md); -} - -.ep-toast--success { - background: var(--success-soft); - color: var(--success-color); - border: 1px solid var(--success-border); -} - -.ep-toast--error { - background: var(--error-soft); - color: var(--error-color); - border: 1px solid var(--error-border); -} - -.ep-toast--warning { - background: var(--warning-soft); - color: var(--warning-color); - border: 1px solid var(--warning-border); -} - -@keyframes ep-toast-in { - from { - opacity: 0; - transform: translateX(-50%) translateY(-12px); - } - to { - opacity: 1; - transform: translateX(-50%) translateY(0); - } -} - -/* ============================================================ - 响应式 - ============================================================ */ -@media (max-width: 1200px) { - .ep-right { - width: 260px; - } - - .ep-left { - width: 240px; - } -} - -@media (max-width: 900px) { - .ep-body { - flex-direction: column; - } - - .ep-left, - .ep-right { - width: 100%; - max-height: 220px; - border-right: none; - border-left: none; - border-bottom: 1px solid var(--border-color); - } - - .ep-right { - border-bottom: none; - border-top: 1px solid var(--border-color); - } - - .ep-toolbar { - flex-wrap: wrap; - gap: var(--space-sm); - } - - .ep-mode-switch { - order: 3; - width: 100%; - justify-content: center; - } -} - -@media (max-width: 768px) { - .ep-left, - .ep-right { - max-height: 180px; - } - - .ep-timeline-bar { - padding: var(--space-sm) var(--space-md); - } - - .ep-timeline-header { - padding: var(--space-sm) var(--space-md); - } - - .ep-timeline-list { - padding: 0 var(--space-md) var(--space-md); - } - - .ep-clip-props-transition-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 480px) { - .ep-toolbar { - padding: var(--space-sm) var(--space-md); - } - - .ep-mode-btn { - padding: 4px 12px; - font-size: var(--font-size-xs); - } - - .ep-clip-card { - padding: 8px 10px; - gap: 6px; - } - - .ep-asset-card { - padding: 8px 10px; - } -} - -/* ============================================================ - 生成进度弹窗 — 任务 2.17 - ============================================================ */ - -/* ── setup 阶段 ─────────────────────────────────────────────── */ - +/* 生成弹窗 */ .ep-gen-setup { - display: flex; - flex-direction: column; - gap: var(--space-md); + padding: 8px 0; } .ep-gen-field-label { - font-size: var(--font-size-sm); - font-weight: 500; - color: var(--text-primary); - margin-bottom: -4px; + display: block; + font-size: 12px; + color: #888; + margin-bottom: 6px; +} + +.ep-gen-duration-input { + width: 100%; + height: 32px; + background: #13131f; + border: 1px solid #2a2a4a; + border-radius: 6px; + padding: 0 10px; + color: #e0e0e0; + font-size: 13px; + outline: none; + margin-bottom: 12px; +} + +.ep-gen-duration-input:focus { + border-color: #6c5ce7; } .ep-gen-estimate { - font-size: var(--font-size-sm); - color: var(--text-secondary); + font-size: 12px; + color: #888; padding: 8px 12px; - background: var(--bg-secondary); - border-radius: var(--radius-md); + background: #13131f; + border-radius: 6px; } -.ep-gen-estimate strong { - color: var(--primary-color); - font-weight: 600; -} - -/* ── progress 阶段 ──────────────────────────────────────────── */ - .ep-gen-progress { display: flex; flex-direction: column; align-items: center; - gap: var(--space-md); - padding: var(--space-lg) 0; + padding: 16px 0; + gap: 16px; } .ep-gen-progress-ring-wrap { @@ -1194,7 +1103,7 @@ .ep-gen-progress-ring-bg { fill: none; - stroke: var(--border-color); + stroke: #2a2a4a; stroke-width: 8; } @@ -1202,9 +1111,7 @@ fill: none; stroke-width: 8; stroke-linecap: round; - transition: - stroke-dashoffset 0.5s ease, - stroke 0.3s ease; + transition: stroke-dashoffset 0.5s ease, stroke 0.3s; } .ep-gen-progress-pct { @@ -1214,20 +1121,17 @@ transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; - font-family: var(--font-mono, monospace); } .ep-gen-step-text { - font-size: var(--font-size-base); - color: var(--text-primary); - font-weight: 500; - text-align: center; + font-size: 14px; + color: #ccc; } .ep-gen-progress-bar { width: 100%; height: 6px; - background: var(--bg-secondary); + background: #2a2a4a; border-radius: 3px; overflow: hidden; } @@ -1235,58 +1139,43 @@ .ep-gen-progress-bar-fill { height: 100%; border-radius: 3px; - transition: - width 0.5s ease, - background-color 0.3s ease; + transition: width 0.5s ease; } .ep-gen-task-id { - font-size: var(--font-size-xs); - color: var(--text-muted, #9ca3af); - font-family: var(--font-mono, monospace); + font-size: 11px; + color: #555; } -/* ── 结果阶段(completed / failed)──────────────────────────── */ - .ep-gen-result { display: flex; flex-direction: column; align-items: center; - gap: var(--space-md); - padding: var(--space-lg) 0; - text-align: center; + padding: 24px 0; + gap: 12px; } .ep-gen-result-icon { font-size: 48px; - line-height: 1; } .ep-gen-result-title { - font-size: var(--font-size-lg); + font-size: 18px; font-weight: 600; - color: var(--text-primary); + color: #fff; } .ep-gen-result-msg { - font-size: var(--font-size-sm); - color: var(--text-secondary); - max-width: 320px; - line-height: 1.5; + font-size: 12px; + color: #888; } .ep-gen-result-msg--error { - color: var(--error-color, #ef4444); -} - -.ep-gen-result-count { - font-size: var(--font-size-sm); - color: var(--success-color, #10b981); - font-weight: 500; + color: #ff4757; } .ep-gen-result-actions { display: flex; - gap: var(--space-sm); - margin-top: var(--space-sm); + gap: 8px; + margin-top: 8px; } diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index c9df24ec2..39e71dd14 100644 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -1,638 +1,565 @@ /** - * 剪辑计划编辑器 — V21 设计系统(完整版) - * 任务 2.14:三栏布局 — 素材面板 / 时间线 / 片段属性 - * 支持拖拽排序、素材关联、转场编辑、剪辑计划 CRUD + * 剪辑计划编辑器 — V8 原型 1:1 还原 + * 四行布局:顶栏(42px) → 模式栏(48px) → 三栏主体 → 底栏(40px) */ -import React, { useState, useEffect, useCallback } from "react"; -import "./EditingPlanner.css"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { Button } from "@/components/ui"; +import React, { useState, useCallback, useEffect } from "react"; import { useSearchParams } from "react-router-dom"; -import { - getEditingTemplates, - getTemplateCategories, - createEditingTemplate, - updateEditingTemplate, - MODE_LABELS, - type EditingTemplate, - type TemplateMode, - type SaveTemplatePayload, +// 简易消息工具(UI 库未导出 message) +const msg = { + success: (s: string) => console.log(`✅ ${s}`), + error: (s: string) => console.error(`❌ ${s}`), + warning: (s: string) => console.warn(`⚠️ ${s}`), + info: (s: string) => console.info(`ℹ️ ${s}`), +}; +import type { + EditingTemplate, + TemplateCategory, + TemplateMode, + SaveTemplatePayload, } from "@/api/editingPlanner"; import { - createEditPlan, - updateEditPlan, - type EditPlanClip, - type MediaAsset, -} from "@/api/editPlans"; -import { - createGenerationTask, - getTask, - retryTask, - type TaskItem, -} from "@/api/tasks"; + getEditingTemplates, + getEditingTemplate, + updateEditingTemplate, + getTemplateCategories, + generateFromTemplate, + MODE_LABELS, +} from "@/api/editingPlanner"; +import type { EditPlanClip, MediaAsset } from "@/api/editPlans"; +import { getMediaAssets, MATERIAL_TYPE_LABELS } from "@/api/editPlans"; +import type { TaskItem } from "@/api/tasks"; +import { createGenerationTask, getTask, retryTask } from "@/api/tasks"; -/* ── 子组件 ── */ import MediaPanel from "./components/MediaPanel"; +import PreviewPlayer from "./components/PreviewPlayer"; import TimelinePanel from "./components/TimelinePanel"; import ClipPropertiesPanel from "./components/ClipPropertiesPanel"; -import PreviewPlayer from "./components/PreviewPlayer"; import SaveModal from "./components/SaveModal"; -import GenerationProgressModal, { - type GenPhase, -} from "./components/GenerationProgressModal"; +import GenerationProgressModal from "./components/GenerationProgressModal"; +import type { GenPhase } from "./components/GenerationProgressModal"; +import "./EditingPlanner.css"; + +/* ──────────── 类型 ──────────── */ + +interface ClipData extends EditPlanClip { + name: string; + thumbnail?: string; + assetName?: string; +} /* ──────────── 常量 ──────────── */ -let _clipId = 0; -const newClipId = () => `clip-new-${++_clipId}`; +const MODE_LIST: { key: TemplateMode; label: string; icon: string }[] = [ + { key: "pip", label: "画中画", icon: "🖼️" }, + { key: "voice_over", label: "人物口播", icon: "🎙️" }, + { key: "one_take", label: "一镜到底", icon: "🎥" }, + { key: "voice_pip", label: "口播+画中画", icon: "🎭" }, +]; -let _segId = 0; -const newSegId = () => `seg-new-${++_segId}`; +const COVER_SCHEMES = [ + { key: "ai_frame", label: "AI选帧" }, + { key: "manual", label: "手动选" }, + { key: "upload", label: "上传" }, + { key: "ai_reselect", label: "AI重选" }, +]; -/* ──────────── 简易 Toast ──────────── */ -type ToastType = "success" | "error" | "warning"; - -const useToast = () => { - const [toast, setToast] = useState<{ - message: string; - type: ToastType; - } | null>(null); - - const show = useCallback((message: string, type: ToastType = "success") => { - setToast({ message, type }); - setTimeout(() => setToast(null), 2500); - }, []); - - const ToastNode = toast ? ( -
点击时间线上的片段来编辑属性
-