feat: 编辑器深度整合 - 右侧栏片段Tab + 封面配置还原
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Failing after 35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m3s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 57s
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m32s
CI/CD Pipeline / Unit Tests (push) Successful in 3m0s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m51s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Failing after 35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m3s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 57s
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m32s
CI/CD Pipeline / Unit Tests (push) Successful in 3m0s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m51s
- 右侧栏增加属性/片段Tab切换,编辑器内直接管理片段 - 加载剪辑计划时还原封面配置(cover_config) - 新增 EditorClipList 紧凑片段列表组件 - 片段Tab支持选中、上下移动、删除、添加操作
This commit is contained in:
Regular → Executable
+172
@@ -6123,3 +6123,175 @@
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* ═══ 右侧栏 Tab ═══ */
|
||||
.ep-right-panel {
|
||||
width: 260px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid var(--border-color, #e5e7eb);
|
||||
background: var(--bg-secondary, #f9fafb);
|
||||
}
|
||||
|
||||
.ep-right-tabs {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid var(--border-color, #e5e7eb);
|
||||
background: var(--bg-primary, #fff);
|
||||
}
|
||||
|
||||
.ep-right-tab {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.ep-right-tab:hover {
|
||||
color: var(--text-primary, #111827);
|
||||
}
|
||||
|
||||
.ep-right-tab.active {
|
||||
color: var(--primary-color, #3b82f6);
|
||||
border-bottom-color: var(--primary-color, #3b82f6);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ep-right-tab-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ═══ 编辑器内片段列表 ═══ */
|
||||
.ep-clip-list {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ep-clip-list-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border-color, #e5e7eb);
|
||||
background: var(--bg-primary, #fff);
|
||||
}
|
||||
|
||||
.ep-clip-list-count {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.ep-clip-list-count b {
|
||||
color: var(--text-primary, #111827);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ep-clip-list-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.ep-clip-list-item {
|
||||
background: var(--bg-primary, #fff);
|
||||
border: 1px solid var(--border-color, #e5e7eb);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.ep-clip-list-item:hover {
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
box-shadow: 0 1px 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.ep-clip-list-item.selected {
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
background: rgba(59, 130, 246, 0.04);
|
||||
}
|
||||
|
||||
.ep-clip-item-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.ep-clip-item-index {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: var(--bg-tertiary, #f3f4f6);
|
||||
color: var(--text-secondary, #6b7280);
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ep-clip-item-type {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ep-clip-item-type-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.ep-clip-item-duration {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary, #111827);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ep-clip-item-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
line-height: 1.4;
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.ep-clip-item-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: flex-end;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.ep-clip-list-item:hover .ep-clip-item-actions,
|
||||
.ep-clip-list-item.selected .ep-clip-item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ep-clip-item-btn {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
padding: 0 !important;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.ep-clip-list-empty {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ import MediaPanel from "./components/MediaPanel";
|
||||
import PreviewPlayer from "./components/PreviewPlayer";
|
||||
import TimelinePanel from "./components/TimelinePanel";
|
||||
import ClipPropertiesPanel from "./components/ClipPropertiesPanel";
|
||||
import EditorClipList from "./components/EditorClipList";
|
||||
import BgmSelector from "./components/BgmSelector";
|
||||
import SubtitleStylePanel from "./components/SubtitleStylePanel";
|
||||
import type { SubtitleStyleConfig } from "./components/SubtitleStylePanel";
|
||||
@@ -242,6 +243,8 @@ const EditingPlanner: React.FC = () => {
|
||||
...DEFAULT_COVER_CONFIG,
|
||||
});
|
||||
const [coverDrawerOpen, setCoverDrawerOpen] = useState(false);
|
||||
/* ── 右侧栏 Tab ── */
|
||||
const [rightTab, setRightTab] = useState<"properties" | "clips">("properties");
|
||||
|
||||
/* ── 保存弹窗 ── */
|
||||
const [saveModalOpen, setSaveModalOpen] = useState(false);
|
||||
@@ -465,6 +468,20 @@ const EditingPlanner: React.FC = () => {
|
||||
music_id: cfg.bgm_config!.music_id || "",
|
||||
}));
|
||||
}
|
||||
// 还原封面配置
|
||||
if (cfg.cover_config) {
|
||||
setCoverSettings((prev) => ({
|
||||
...prev,
|
||||
enabled: cfg.cover_config!.enabled ?? prev.enabled,
|
||||
mode: (cfg.cover_config!.mode as CoverConfig["mode"]) || prev.mode,
|
||||
frame_time: cfg.cover_config!.frame_time ?? prev.frame_time,
|
||||
upload_url: cfg.cover_config!.upload_url || prev.upload_url,
|
||||
thumbnail_url:
|
||||
cfg.cover_config!.thumbnail_url || prev.thumbnail_url,
|
||||
ai_suggested_time:
|
||||
cfg.cover_config!.ai_suggested_time ?? prev.ai_suggested_time,
|
||||
}));
|
||||
}
|
||||
|
||||
// 还原片段:优先从后端 clips 表,其次从 config.segments 兜底
|
||||
const backendClips = clipsRes?.items || [];
|
||||
@@ -1349,43 +1366,87 @@ const EditingPlanner: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 右栏 260px:设置面板 */}
|
||||
<ClipPropertiesPanel
|
||||
selectedClip={selectedClip}
|
||||
titleSettings={titleSettings}
|
||||
subtitleSettings={subtitleSettings}
|
||||
bgmSettings={bgmSettings}
|
||||
clipsCount={clips.length}
|
||||
totalDuration={totalDuration}
|
||||
currentMode={currentMode}
|
||||
onTitleSettingsChange={(partial) =>
|
||||
setTitleSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onSubtitleSettingsChange={(partial) =>
|
||||
setSubtitleSettings(
|
||||
(prev) => ({ ...prev, ...partial }) as SubtitleStyleConfig,
|
||||
)
|
||||
}
|
||||
onBgmSettingsChange={(partial) =>
|
||||
setBgmSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onClipUpdate={handleClipUpdate}
|
||||
onOpenBgmDrawer={() => setBgmDrawerOpen(true)}
|
||||
onOpenSubtitleDrawer={() => setSubtitleDrawerOpen(true)}
|
||||
voiceMaterials={voiceMaterials}
|
||||
voiceMaterialsLoading={voiceMaterialsQuery.isLoading}
|
||||
onRefreshVoiceMaterials={() => voiceMaterialsQuery.refetch()}
|
||||
onClipVoiceSelect={handleClipVoiceSelect}
|
||||
onOpenTransitionDrawer={handleOpenTransitionDrawer}
|
||||
onOpenSpeedDrawer={handleOpenSpeedDrawer}
|
||||
onOpenTtsDrawer={handleOpenTtsDrawer}
|
||||
onOpenWatermarkDrawer={() => setWatermarkDrawerOpen(true)}
|
||||
onOpenIntroOutroDrawer={() => setIntroOutroDrawerOpen(true)}
|
||||
onOpenPipDrawer={() => setPipDrawerOpen(true)}
|
||||
onOpenFilterDrawer={() => setFilterDrawerOpen(true)}
|
||||
onOpenGreenScreenDrawer={() => setChromaKeyDrawerOpen(true)}
|
||||
onOpenStickerDrawer={() => setStickerDrawerOpen(true)}
|
||||
onOpenCoverDrawer={() => setCoverDrawerOpen(true)}
|
||||
/>
|
||||
<div className="ep-right-panel">
|
||||
{/* Tab 切换 */}
|
||||
<div className="ep-right-tabs">
|
||||
<button
|
||||
className={`ep-right-tab${rightTab === "properties" ? " active" : ""}`}
|
||||
onClick={() => setRightTab("properties")}
|
||||
>
|
||||
属性
|
||||
</button>
|
||||
<button
|
||||
className={`ep-right-tab${rightTab === "clips" ? " active" : ""}`}
|
||||
onClick={() => setRightTab("clips")}
|
||||
>
|
||||
片段
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 属性 Tab */}
|
||||
{rightTab === "properties" && (
|
||||
<div className="ep-right-tab-content">
|
||||
<ClipPropertiesPanel
|
||||
selectedClip={selectedClip}
|
||||
titleSettings={titleSettings}
|
||||
subtitleSettings={subtitleSettings}
|
||||
bgmSettings={bgmSettings}
|
||||
clipsCount={clips.length}
|
||||
totalDuration={totalDuration}
|
||||
currentMode={currentMode}
|
||||
onTitleSettingsChange={(partial) =>
|
||||
setTitleSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onSubtitleSettingsChange={(partial) =>
|
||||
setSubtitleSettings(
|
||||
(prev) => ({ ...prev, ...partial }) as SubtitleStyleConfig,
|
||||
)
|
||||
}
|
||||
onBgmSettingsChange={(partial) =>
|
||||
setBgmSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onClipUpdate={handleClipUpdate}
|
||||
onOpenBgmDrawer={() => setBgmDrawerOpen(true)}
|
||||
onOpenSubtitleDrawer={() => setSubtitleDrawerOpen(true)}
|
||||
voiceMaterials={voiceMaterials}
|
||||
voiceMaterialsLoading={voiceMaterialsQuery.isLoading}
|
||||
onRefreshVoiceMaterials={() => voiceMaterialsQuery.refetch()}
|
||||
onClipVoiceSelect={handleClipVoiceSelect}
|
||||
onOpenTransitionDrawer={handleOpenTransitionDrawer}
|
||||
onOpenSpeedDrawer={handleOpenSpeedDrawer}
|
||||
onOpenTtsDrawer={handleOpenTtsDrawer}
|
||||
onOpenWatermarkDrawer={() => setWatermarkDrawerOpen(true)}
|
||||
onOpenIntroOutroDrawer={() => setIntroOutroDrawerOpen(true)}
|
||||
onOpenPipDrawer={() => setPipDrawerOpen(true)}
|
||||
onOpenFilterDrawer={() => setFilterDrawerOpen(true)}
|
||||
onOpenGreenScreenDrawer={() => setChromaKeyDrawerOpen(true)}
|
||||
onOpenStickerDrawer={() => setStickerDrawerOpen(true)}
|
||||
onOpenCoverDrawer={() => setCoverDrawerOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 片段 Tab */}
|
||||
{rightTab === "clips" && (
|
||||
<div className="ep-right-tab-content">
|
||||
<EditorClipList
|
||||
clips={clips}
|
||||
selectedClipId={selectedClipId}
|
||||
onSelect={handleClipSelect}
|
||||
onMoveUp={(clipId) => {
|
||||
const idx = clips.findIndex((c) => c.id === clipId);
|
||||
if (idx > 0) handleClipReorder(clipId, idx - 1);
|
||||
}}
|
||||
onMoveDown={(clipId) => {
|
||||
const idx = clips.findIndex((c) => c.id === clipId);
|
||||
if (idx < clips.length - 1) handleClipReorder(clipId, idx + 1);
|
||||
}}
|
||||
onRemove={handleClipRemove}
|
||||
onAdd={handleAddClip}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ═══ 第4行:底栏 40px ═══ */}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 编辑器右侧栏 — 片段列表 Tab
|
||||
* 紧凑版片段管理:选中、上下移动、删除、添加
|
||||
*/
|
||||
import React from "react";
|
||||
import { Button, Tooltip, Empty } from "antd";
|
||||
import {
|
||||
UpOutlined,
|
||||
DownOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
ScissorOutlined,
|
||||
SoundOutlined,
|
||||
PictureOutlined,
|
||||
VideoCameraOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { ClipData, ClipType } from "../types";
|
||||
|
||||
interface EditorClipListProps {
|
||||
clips: ClipData[];
|
||||
selectedClipId: string | null;
|
||||
onSelect: (clipId: string) => void;
|
||||
onMoveUp: (clipId: string) => void;
|
||||
onMoveDown: (clipId: string) => void;
|
||||
onRemove: (clipId: string) => void;
|
||||
onAdd: () => void;
|
||||
onSplit?: (clipId: string) => void;
|
||||
}
|
||||
|
||||
const clipTypeIcon: Record<ClipType | string, React.ReactNode> = {
|
||||
video: <VideoCameraOutlined />,
|
||||
image: <PictureOutlined />,
|
||||
voice: <SoundOutlined />,
|
||||
pip: <ScissorOutlined />,
|
||||
};
|
||||
|
||||
const clipTypeLabel: Record<ClipType | string, string> = {
|
||||
video: "视频",
|
||||
image: "图片",
|
||||
voice: "配音",
|
||||
pip: "画中画",
|
||||
};
|
||||
|
||||
const formatDuration = (sec: number) => {
|
||||
if (sec < 60) return `${sec.toFixed(1)}s`;
|
||||
const m = Math.floor(sec / 60);
|
||||
const s = (sec % 60).toFixed(0);
|
||||
return `${m}m${s.padStart(2, "0")}s`;
|
||||
};
|
||||
|
||||
const EditorClipList: React.FC<EditorClipListProps> = ({
|
||||
clips,
|
||||
selectedClipId,
|
||||
onSelect,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
onRemove,
|
||||
onAdd,
|
||||
}) => {
|
||||
if (clips.length === 0) {
|
||||
return (
|
||||
<div className="ep-clip-list-empty">
|
||||
<Empty
|
||||
description="暂无片段"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
style={{ margin: "40px 0" }}
|
||||
/>
|
||||
<Button type="primary" icon={<PlusOutlined />} block onClick={onAdd}>
|
||||
添加片段
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ep-clip-list">
|
||||
{/* 顶部工具栏 */}
|
||||
<div className="ep-clip-list-toolbar">
|
||||
<span className="ep-clip-list-count">
|
||||
共 <b>{clips.length}</b> 个片段
|
||||
</span>
|
||||
<Tooltip title="添加片段">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={onAdd}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* 片段列表 */}
|
||||
<div className="ep-clip-list-scroll">
|
||||
{clips.map((clip, index) => (
|
||||
<div
|
||||
key={clip.id}
|
||||
className={`ep-clip-list-item${
|
||||
selectedClipId === clip.id ? " selected" : ""
|
||||
}`}
|
||||
onClick={() => onSelect(clip.id)}
|
||||
>
|
||||
{/* 序号 + 类型图标 */}
|
||||
<div className="ep-clip-item-head">
|
||||
<span className="ep-clip-item-index">{index + 1}</span>
|
||||
<span className="ep-clip-item-type">
|
||||
{clipTypeIcon[clip.type] || <ScissorOutlined />}
|
||||
<span className="ep-clip-item-type-label">
|
||||
{clipTypeLabel[clip.type] || "片段"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="ep-clip-item-duration">
|
||||
{formatDuration(clip.duration)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 文案预览 */}
|
||||
{clip.script_text && (
|
||||
<div className="ep-clip-item-text">
|
||||
{clip.script_text.slice(0, 40)}
|
||||
{clip.script_text.length > 40 ? "..." : ""}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="ep-clip-item-actions" onClick={(e) => e.stopPropagation()}>
|
||||
<Tooltip title="上移">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<UpOutlined />}
|
||||
disabled={index === 0}
|
||||
onClick={() => onMoveUp(clip.id)}
|
||||
className="ep-clip-item-btn"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="下移">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<DownOutlined />}
|
||||
disabled={index === clips.length - 1}
|
||||
onClick={() => onMoveDown(clip.id)}
|
||||
className="ep-clip-item-btn"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="删除">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onRemove(clip.id)}
|
||||
className="ep-clip-item-btn"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorClipList;
|
||||
Reference in New Issue
Block a user