feat(video): 重构为视频工作区与右侧历史拼接栏
This commit is contained in:
1 parent
7aedee4e49
commit
ebda949077
3 files changed
+111
-55
No files matched your search
@@ -78,10 +78,53 @@ body {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.video-workbench-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.history-panel .panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.history-panel .video-stage {
|
||||
min-height: 260px;
|
||||
max-height: 980px;
|
||||
overflow: auto;
|
||||
background: #fafafa;
|
||||
border-color: var(--video-outline);
|
||||
}
|
||||
|
||||
.history-panel .video-item {
|
||||
background: #fff;
|
||||
border: 1px solid var(--video-outline);
|
||||
}
|
||||
|
||||
.history-panel .video-item-title,
|
||||
.history-panel .video-item-bar {
|
||||
color: var(--accents-7);
|
||||
}
|
||||
|
||||
.history-panel .video-item-actions .geist-button-outline {
|
||||
color: var(--accents-7);
|
||||
border-color: var(--video-outline);
|
||||
}
|
||||
|
||||
.history-panel .video-item-link {
|
||||
color: var(--accents-4);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.video-top-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.video-workbench-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.video-card {
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
const referencePreviewImg = document.getElementById('referencePreviewImg');
|
||||
const referencePreviewMeta = document.getElementById('referencePreviewMeta');
|
||||
const refDropZone = document.getElementById('refDropZone');
|
||||
const historyCount = document.getElementById('historyCount');
|
||||
|
||||
let taskStates = new Map();
|
||||
let activeTaskIds = [];
|
||||
@@ -119,6 +120,12 @@
|
||||
if (editFrameHash) editFrameHash.textContent = shortHash(lastFrameHash);
|
||||
}
|
||||
|
||||
function updateHistoryCount() {
|
||||
if (!historyCount || !videoStage) return;
|
||||
const count = videoStage.querySelectorAll('.video-item').length;
|
||||
historyCount.textContent = String(count);
|
||||
}
|
||||
|
||||
function updateMergeLabels() {
|
||||
if (mergeVideoA) {
|
||||
mergeVideoA.textContent = selectedVideoUrl ? shortHash(selectedVideoUrl) : '-';
|
||||
@@ -381,6 +388,10 @@
|
||||
previewCount = 0;
|
||||
selectedVideoItemId = '';
|
||||
selectedVideoUrl = '';
|
||||
if (editVideo) {
|
||||
editVideo.removeAttribute('src');
|
||||
editVideo.load();
|
||||
}
|
||||
mergeTargetVideoUrl = '';
|
||||
mergeTargetVideoName = '';
|
||||
mergeCutMsA = 0;
|
||||
@@ -388,6 +399,7 @@
|
||||
if (enterEditBtn) enterEditBtn.disabled = true;
|
||||
closeEditPanel();
|
||||
updateMergeLabels();
|
||||
updateHistoryCount();
|
||||
}
|
||||
if (durationValue) {
|
||||
durationValue.textContent = '耗时 -';
|
||||
@@ -456,6 +468,7 @@
|
||||
if (videoEmpty) {
|
||||
videoEmpty.classList.add('hidden');
|
||||
}
|
||||
updateHistoryCount();
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -1008,6 +1021,9 @@
|
||||
function bindEditVideoSource(url) {
|
||||
const safeUrl = String(url || '').trim();
|
||||
selectedVideoUrl = safeUrl;
|
||||
if (editHint) {
|
||||
editHint.classList.toggle('hidden', Boolean(safeUrl));
|
||||
}
|
||||
if (!editVideo) return;
|
||||
editVideo.src = safeUrl;
|
||||
editVideo.load();
|
||||
@@ -1040,29 +1056,23 @@
|
||||
}
|
||||
|
||||
function openEditPanel() {
|
||||
if (!editPanel || !editHint || !editBody) return;
|
||||
const item = getSelectedVideoItem();
|
||||
const url = item
|
||||
? String(item.dataset.url || '').trim()
|
||||
: String(selectedVideoUrl || '').trim();
|
||||
if (!url) {
|
||||
editPanel.classList.remove('hidden');
|
||||
editHint.classList.remove('hidden');
|
||||
editBody.classList.add('hidden');
|
||||
if (editHint) editHint.classList.remove('hidden');
|
||||
toast('请先选中一个已生成视频', 'warning');
|
||||
return;
|
||||
}
|
||||
editPanel.classList.remove('hidden');
|
||||
editHint.classList.add('hidden');
|
||||
editBody.classList.remove('hidden');
|
||||
if (editHint) editHint.classList.add('hidden');
|
||||
if (editBody) editBody.classList.remove('hidden');
|
||||
bindEditVideoSource(url);
|
||||
}
|
||||
|
||||
function closeEditPanel() {
|
||||
if (!editPanel || !editHint || !editBody) return;
|
||||
editHint.classList.remove('hidden');
|
||||
editBody.classList.add('hidden');
|
||||
editPanel.classList.add('hidden');
|
||||
if (editHint) editHint.classList.remove('hidden');
|
||||
if (editBody) editBody.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function openCacheVideoModal() {
|
||||
@@ -1220,6 +1230,13 @@
|
||||
} else if (info.url) {
|
||||
renderVideoFromUrl(taskState, info.url);
|
||||
}
|
||||
const item = taskState.previewItem || null;
|
||||
const itemUrl = item ? String(item.dataset.url || '').trim() : '';
|
||||
if (item && itemUrl) {
|
||||
selectedVideoItemId = String(item.dataset.index || '');
|
||||
refreshVideoSelectionUi();
|
||||
bindEditVideoSource(itemUrl);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2081,9 +2098,7 @@
|
||||
return;
|
||||
}
|
||||
if (!target.classList.contains('video-download')) {
|
||||
if (editPanel && !editPanel.classList.contains('hidden')) {
|
||||
openEditPanel();
|
||||
}
|
||||
bindEditVideoSource(selectedVideoUrl);
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/geist@1.0.0/dist/fonts/geist-mono/style.css" rel="stylesheet">
|
||||
<script src="/static/common/js/theme.js?v=0.1.0"></script>
|
||||
<link href="/static/common/css/common.css?v=0.3.7" rel="stylesheet">
|
||||
<link href="/static/public/css/video.css?v=0.3.7" rel="stylesheet">
|
||||
<link href="/static/public/css/video.css?v=0.3.8" rel="stylesheet">
|
||||
<link href="/static/common/css/toast.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div id="toast-container" class="toast-container"></div>
|
||||
<div id="app-header"></div>
|
||||
|
||||
<main class="flex-1 container mx-auto max-w-5xl px-4 sm:px-6 py-5 sm:py-8 fade-in">
|
||||
<main class="flex-1 container mx-auto max-w-6xl px-4 sm:px-6 py-5 sm:py-8 fade-in">
|
||||
<div class="space-y-4 sm:space-y-6">
|
||||
<div class="video-header-row">
|
||||
<div>
|
||||
@@ -173,31 +173,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-card video-preview-card">
|
||||
<div class="video-preview-header">
|
||||
<div class="card-title">视频预览</div>
|
||||
<div class="preview-actions">
|
||||
<button id="pickCachedVideoBtn" class="geist-button-outline text-xs px-3">选择缓存视频</button>
|
||||
<button id="enterEditBtn" class="geist-button-outline text-xs px-3">进入编辑模式</button>
|
||||
<button id="clearBtn" class="geist-button-outline text-xs px-3">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="videoEmpty" class="video-empty">等待生成视频</div>
|
||||
<div id="videoStage" class="video-stage hidden"></div>
|
||||
</div>
|
||||
|
||||
<div id="editPanel" class="video-card edit-panel hidden">
|
||||
<div class="video-preview-header">
|
||||
<div class="card-title">拼接编辑</div>
|
||||
<div class="preview-actions">
|
||||
<button id="cancelEditBtn" class="geist-button-outline text-xs px-3">退出编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editHint" class="video-empty">先在上方视频列表中选择一个视频,再进入编辑模式。</div>
|
||||
<div id="editBody" class="hidden">
|
||||
<div class="edit-preview-wrap">
|
||||
<video id="editVideo" controls preload="metadata"></video>
|
||||
<div class="video-workbench-grid">
|
||||
<div id="editPanel" class="video-card edit-panel">
|
||||
<div class="video-preview-header">
|
||||
<div class="card-title">视频工作区</div>
|
||||
<div class="preview-actions">
|
||||
<button id="pickCachedVideoBtn" class="geist-button-outline text-xs px-3">选择视频</button>
|
||||
<button id="clearBtn" class="geist-button-outline text-xs px-3">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editHint" class="video-empty">请先选择或生成一个视频,工作区会始终展示当前视频。</div>
|
||||
<div id="editBody">
|
||||
<div class="meta-grid edit-meta-grid">
|
||||
<div class="meta-item"><div class="meta-label">当前工作视频</div><div id="mergeVideoA" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">历史数量</div><div id="historyCount" class="meta-value">0</div></div>
|
||||
</div>
|
||||
<div class="edit-preview-wrap">
|
||||
<video id="editVideo" controls preload="metadata"></video>
|
||||
</div>
|
||||
<div class="edit-slider-wrap">
|
||||
<input id="editTimeline" type="range" min="0" max="100000" step="1" value="0">
|
||||
<div class="progress-meta">
|
||||
@@ -205,13 +198,14 @@
|
||||
<span id="editDurationText">总时长 -</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-grid edit-meta-grid">
|
||||
<div class="meta-item"><div class="meta-label">frame_index</div><div id="editFrameIndex" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">timestamp_ms</div><div id="editTimestampMs" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">frame_hash</div><div id="editFrameHash" class="meta-value">-</div></div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">拼接时长</div>
|
||||
<select id="editLengthSelect" class="geist-input">
|
||||
<div class="meta-grid edit-meta-grid">
|
||||
<div class="meta-item"><div class="meta-label">frame_index</div><div id="editFrameIndex" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">timestamp_ms</div><div id="editTimestampMs" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">frame_hash</div><div id="editFrameHash" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">视频2(B)</div><div id="mergeVideoB" class="meta-value">-</div></div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">拼接时长</div>
|
||||
<select id="editLengthSelect" class="geist-input">
|
||||
<option value="6" selected>6 秒</option>
|
||||
<option value="10">10 秒</option>
|
||||
<option value="15">15 秒</option>
|
||||
@@ -224,19 +218,15 @@
|
||||
</div>
|
||||
<div class="preview-actions">
|
||||
<button id="spliceBtn" class="geist-button text-xs px-3">拼接视频</button>
|
||||
</div>
|
||||
<div class="video-card manual-merge-card">
|
||||
<div class="video-preview-header">
|
||||
</div>
|
||||
<div class="video-card manual-merge-card">
|
||||
<div class="video-preview-header">
|
||||
<div class="card-title">手动两段拼接</div>
|
||||
<div class="preview-actions">
|
||||
<button id="pickMergeVideoBtn" class="geist-button-outline text-xs px-3" type="button">选择视频2</button>
|
||||
<button id="directMergeBtn" class="geist-button-outline text-xs px-3" type="button">手动拼接</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-grid edit-meta-grid">
|
||||
<div class="meta-item"><div class="meta-label">视频1(A)</div><div id="mergeVideoA" class="meta-value">-</div></div>
|
||||
<div class="meta-item"><div class="meta-label">视频2(B)</div><div id="mergeVideoB" class="meta-value">-</div></div>
|
||||
</div>
|
||||
<div class="manual-merge-grid">
|
||||
<div class="manual-merge-col">
|
||||
<div class="meta-label">视频1拼接帧(取左侧)</div>
|
||||
@@ -267,6 +257,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="video-card history-panel">
|
||||
<div class="panel-head">
|
||||
<div class="card-title">历史拼接视频</div>
|
||||
</div>
|
||||
<div id="videoEmpty" class="video-empty">拼接结果会在这里保存,点击可替换到工作区。</div>
|
||||
<div id="videoStage" class="video-stage hidden"></div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div id="cacheVideoModal" class="modal-overlay hidden">
|
||||
@@ -315,7 +313,7 @@
|
||||
<script src="/static/common/js/pwa.js?v=0.1.1"></script>
|
||||
<script src="/static/vendor/ffmpeg/ffmpeg.js?v=0.1.0"></script>
|
||||
<script src="/static/vendor/ffmpeg/ffmpeg-util.js?v=0.1.0"></script>
|
||||
<script src="/static/public/js/video.js?v=0.4.9"></script>
|
||||
<script src="/static/public/js/video.js?v=0.5.0"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user