更新新需求-采集
This commit is contained in:
@@ -0,0 +1,921 @@
|
||||
<template>
|
||||
<section class="delivery-workspace">
|
||||
<div class="delivery-workspace__tabs">
|
||||
<el-tabs v-model="activeTab" class="delivery-workspace__tab-panel">
|
||||
<el-tab-pane label="视频复刻" name="remake" />
|
||||
<el-tab-pane label="图生视频" name="imageToVideo" />
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div class="delivery-grid">
|
||||
<AiSectionCard
|
||||
step="01"
|
||||
title="源素材"
|
||||
:description="currentTabCopy.sourceDescription"
|
||||
class="delivery-card delivery-card--step-01"
|
||||
>
|
||||
<AiAssetDropzone
|
||||
:title="currentTabCopy.primaryAssetTitle"
|
||||
:description="currentTabCopy.primaryAssetDescription"
|
||||
:placeholder="currentTabCopy.primaryAssetPlaceholder"
|
||||
:helper="currentTabCopy.primaryAssetHelper"
|
||||
:accept="currentTabCopy.primaryAssetAccept"
|
||||
:file-name="currentWorkspace.primaryAsset.fileName"
|
||||
:preview-url="currentWorkspace.primaryAsset.previewUrl"
|
||||
:preview-kind="currentWorkspace.primaryAsset.previewKind"
|
||||
@select="(file) => setAsset(activeTab, 'primaryAsset', file)"
|
||||
@clear="clearAsset(activeTab, 'primaryAsset')"
|
||||
/>
|
||||
|
||||
<AiAssetDropzone
|
||||
:title="currentTabCopy.secondaryAssetTitle"
|
||||
:description="currentTabCopy.secondaryAssetDescription"
|
||||
:placeholder="currentTabCopy.secondaryAssetPlaceholder"
|
||||
:helper="currentTabCopy.secondaryAssetHelper"
|
||||
:accept="currentTabCopy.secondaryAssetAccept"
|
||||
:file-name="currentWorkspace.secondaryAsset.fileName"
|
||||
:preview-url="currentWorkspace.secondaryAsset.previewUrl"
|
||||
:preview-kind="currentWorkspace.secondaryAsset.previewKind"
|
||||
@select="(file) => setAsset(activeTab, 'secondaryAsset', file)"
|
||||
@clear="clearAsset(activeTab, 'secondaryAsset')"
|
||||
/>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">素材推进方式</span>
|
||||
<AiChoicePills
|
||||
v-model="currentWorkspace.sourceStrategy"
|
||||
:options="currentTabCopy.sourceStrategies"
|
||||
/>
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="02"
|
||||
title="音视频生成"
|
||||
description="先锁定投放规格,再决定整体风格、时长和画面清晰度。"
|
||||
class="delivery-card delivery-card--step-02"
|
||||
>
|
||||
<div class="delivery-form-grid delivery-form-grid--two">
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">画面比例</span>
|
||||
<AiChoicePills v-model="currentWorkspace.ratio" :options="ratioOptions" />
|
||||
</div>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">输出分辨率</span>
|
||||
<AiChoicePills v-model="currentWorkspace.resolution" :options="resolutionOptions" />
|
||||
</div>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">视频时长</span>
|
||||
<AiChoicePills v-model="currentWorkspace.duration" :options="durationOptions" />
|
||||
</div>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">整体风格</span>
|
||||
<AiChoicePills v-model="currentWorkspace.style" :options="styleOptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">语言</span>
|
||||
<el-select v-model="currentWorkspace.language" class="delivery-select">
|
||||
<el-option
|
||||
v-for="item in languageOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="03"
|
||||
title="任务和预览"
|
||||
description="先保存草稿,再开始组装带货视频。"
|
||||
class="delivery-card delivery-card--preview delivery-card--step-03"
|
||||
>
|
||||
<div class="task-actions">
|
||||
<el-button class="delivery-primary-btn" type="primary" @click="startAssembly">
|
||||
开始组装
|
||||
</el-button>
|
||||
<div class="task-actions__row">
|
||||
<button type="button" class="task-text-btn" @click="saveDraft">保存草稿</button>
|
||||
<button type="button" class="task-text-btn" @click="exportConfig">导出配置</button>
|
||||
</div>
|
||||
<el-button class="delivery-secondary-btn" @click="buildStoryboard">
|
||||
生成镜头方案
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="preview-panel__meta">
|
||||
<div class="preview-row">
|
||||
<span class="preview-row__label">模式</span>
|
||||
<span class="preview-row__value">{{ currentTabCopy.summaryLabel }}</span>
|
||||
</div>
|
||||
<div class="preview-row">
|
||||
<span class="preview-row__label">规格</span>
|
||||
<span class="preview-row__value">
|
||||
{{ currentWorkspace.ratio }} / {{ currentWorkspace.resolution }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-row">
|
||||
<span class="preview-row__label">口播</span>
|
||||
<span class="preview-row__value">
|
||||
{{ currentWorkspace.voiceTone }} / {{ currentWorkspace.language }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-row">
|
||||
<span class="preview-row__label">产品类目</span>
|
||||
<span class="preview-row__value">
|
||||
{{ currentWorkspace.categories.join('、') || '未设置' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-panel__surface">
|
||||
<img
|
||||
v-if="currentWorkspace.primaryAsset.previewKind === 'image'"
|
||||
:src="currentWorkspace.primaryAsset.previewUrl"
|
||||
class="preview-panel__media"
|
||||
alt=""
|
||||
/>
|
||||
<video
|
||||
v-else-if="currentWorkspace.primaryAsset.previewKind === 'video'"
|
||||
:src="currentWorkspace.primaryAsset.previewUrl"
|
||||
class="preview-panel__media"
|
||||
controls
|
||||
/>
|
||||
<div v-else class="preview-panel__placeholder">
|
||||
上传主素材后,这里显示视频预览和最终交付摘要
|
||||
</div>
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="04"
|
||||
title="场景和镜头"
|
||||
description="支持用提示词微调,也支持上传场景图直接替换原场景。"
|
||||
class="delivery-card delivery-card--step-04"
|
||||
>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">场景处理方式</span>
|
||||
<AiChoicePills v-model="currentWorkspace.sceneMode" :options="sceneModeOptions" />
|
||||
</div>
|
||||
|
||||
<div class="delivery-form-grid delivery-form-grid--two">
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">场景提示词</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.scenePrompt"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
resize="none"
|
||||
placeholder="例如:保留产品特写,换成室内直播间,镜头推进更快,背景偏暖光。"
|
||||
/>
|
||||
</div>
|
||||
<AiAssetDropzone
|
||||
title="场景图替换"
|
||||
description="有明确参考图时直接上传,优先保证场景结构和光线。"
|
||||
placeholder="上传场景参考图"
|
||||
helper="支持 JPG / PNG"
|
||||
accept="image/*"
|
||||
:file-name="currentWorkspace.sceneAsset.fileName"
|
||||
:preview-url="currentWorkspace.sceneAsset.previewUrl"
|
||||
:preview-kind="currentWorkspace.sceneAsset.previewKind"
|
||||
@select="(file) => setAsset(activeTab, 'sceneAsset', file)"
|
||||
@clear="clearAsset(activeTab, 'sceneAsset')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">镜头节奏</span>
|
||||
<el-slider v-model="currentWorkspace.cameraRhythm" :min="1" :max="10" />
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="05"
|
||||
title="人脸和口播"
|
||||
description="控制性别、人脸图、语气和语音来源。"
|
||||
class="delivery-card delivery-card--step-05"
|
||||
>
|
||||
<div class="delivery-form-grid delivery-form-grid--two">
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">性别</span>
|
||||
<AiChoicePills v-model="currentWorkspace.gender" :options="genderOptions" />
|
||||
</div>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">语气</span>
|
||||
<AiChoicePills v-model="currentWorkspace.voiceTone" :options="voiceToneOptions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">语音来源</span>
|
||||
<AiChoicePills
|
||||
v-model="currentWorkspace.speechSource"
|
||||
:options="speechSourceOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AiAssetDropzone
|
||||
title="模特图 / 人脸图"
|
||||
description="上传清晰正脸图,适合用于数字人口播。"
|
||||
placeholder="上传模特图"
|
||||
helper="建议 1080px 以上,单人正面"
|
||||
accept="image/*"
|
||||
:file-name="currentWorkspace.avatarAsset.fileName"
|
||||
:preview-url="currentWorkspace.avatarAsset.previewUrl"
|
||||
:preview-kind="currentWorkspace.avatarAsset.previewKind"
|
||||
@select="(file) => setAsset(activeTab, 'avatarAsset', file)"
|
||||
@clear="clearAsset(activeTab, 'avatarAsset')"
|
||||
/>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="06"
|
||||
title="话术和音乐"
|
||||
description="支持直接口播、原话术微调,或者转成纯音乐版本。"
|
||||
class="delivery-card delivery-card--step-06"
|
||||
>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">话术模式</span>
|
||||
<AiChoicePills v-model="currentWorkspace.scriptMode" :options="scriptModeOptions" />
|
||||
</div>
|
||||
|
||||
<div class="delivery-form-grid delivery-form-grid--two">
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">话术内容</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.scriptText"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
resize="none"
|
||||
placeholder="输入要口播的话术,或粘贴需要整体微调的原始话术。"
|
||||
/>
|
||||
</div>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">微调约束</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.scriptRules"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
resize="none"
|
||||
placeholder="例如:保留原来的成交语气,不改卖点顺序,强调防滑、透气、现货。"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AiAssetDropzone
|
||||
title="纯音乐素材"
|
||||
description="需要做纯音乐版本时可上传,也可以后续从原视频中抽取。"
|
||||
placeholder="上传背景音乐"
|
||||
helper="支持 MP3 / WAV"
|
||||
accept="audio/*"
|
||||
:file-name="currentWorkspace.musicAsset.fileName"
|
||||
:preview-url="currentWorkspace.musicAsset.previewUrl"
|
||||
:preview-kind="currentWorkspace.musicAsset.previewKind"
|
||||
@select="(file) => setAsset(activeTab, 'musicAsset', file)"
|
||||
@clear="clearAsset(activeTab, 'musicAsset')"
|
||||
/>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="07"
|
||||
title="标题标签关键词"
|
||||
description="类目、平台和卖点一起配置,方便后续拆投放版本。"
|
||||
class="delivery-card delivery-card--step-07"
|
||||
>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">细分类目</span>
|
||||
<AiChoicePills
|
||||
v-model="currentWorkspace.categories"
|
||||
:options="categoryOptions"
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">投放平台</span>
|
||||
<AiChoicePills
|
||||
v-model="currentWorkspace.platforms"
|
||||
:options="platformOptions"
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">核心卖点</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.productFocus"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
resize="none"
|
||||
placeholder="例如:上脚显瘦、脚感轻、百搭、直播间主推款。"
|
||||
/>
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
|
||||
<AiSectionCard
|
||||
step="08"
|
||||
title="执行说明"
|
||||
description="补充收尾逻辑,例如保留人物动作,只替换场景和话术重点。"
|
||||
class="delivery-card delivery-card--step-08"
|
||||
>
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">收口动作</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.cta"
|
||||
placeholder="例如:结尾引导领券下单,强调今晚库存。"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="delivery-field">
|
||||
<span class="delivery-field__label">执行说明</span>
|
||||
<el-input
|
||||
v-model="currentWorkspace.notes"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
resize="none"
|
||||
placeholder="补充执行说明,例如:第二版转成纯音乐短视频。"
|
||||
/>
|
||||
</div>
|
||||
</AiSectionCard>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import AiAssetDropzone from '@/shared/components/ai-workflow/AiAssetDropzone.vue'
|
||||
import AiChoicePills from '@/shared/components/ai-workflow/AiChoicePills.vue'
|
||||
import AiSectionCard from '@/shared/components/ai-workflow/AiSectionCard.vue'
|
||||
|
||||
type WorkspaceTab = 'remake' | 'imageToVideo'
|
||||
type PreviewKind = 'image' | 'video' | 'file'
|
||||
|
||||
type AssetState = {
|
||||
fileName: string
|
||||
previewUrl: string
|
||||
previewKind: PreviewKind
|
||||
}
|
||||
|
||||
type AssetKey =
|
||||
| 'primaryAsset'
|
||||
| 'secondaryAsset'
|
||||
| 'sceneAsset'
|
||||
| 'avatarAsset'
|
||||
| 'musicAsset'
|
||||
|
||||
type WorkspaceState = {
|
||||
primaryAsset: AssetState
|
||||
secondaryAsset: AssetState
|
||||
sceneAsset: AssetState
|
||||
avatarAsset: AssetState
|
||||
musicAsset: AssetState
|
||||
sourceStrategy: string
|
||||
ratio: string
|
||||
resolution: string
|
||||
duration: string
|
||||
style: string
|
||||
sceneMode: string
|
||||
scenePrompt: string
|
||||
cameraRhythm: number
|
||||
gender: string
|
||||
language: string
|
||||
voiceTone: string
|
||||
speechSource: string
|
||||
scriptMode: string
|
||||
scriptText: string
|
||||
scriptRules: string
|
||||
categories: string[]
|
||||
platforms: string[]
|
||||
productFocus: string
|
||||
cta: string
|
||||
notes: string
|
||||
}
|
||||
|
||||
type TabCopy = {
|
||||
summaryLabel: string
|
||||
sourceDescription: string
|
||||
primaryAssetTitle: string
|
||||
primaryAssetDescription: string
|
||||
primaryAssetPlaceholder: string
|
||||
primaryAssetHelper: string
|
||||
primaryAssetAccept: string
|
||||
secondaryAssetTitle: string
|
||||
secondaryAssetDescription: string
|
||||
secondaryAssetPlaceholder: string
|
||||
secondaryAssetHelper: string
|
||||
secondaryAssetAccept: string
|
||||
sourceStrategies: string[]
|
||||
}
|
||||
|
||||
const activeTab = ref<WorkspaceTab>('remake')
|
||||
|
||||
const ratioOptions = ['9:16', '1:1', '16:9']
|
||||
const resolutionOptions = ['720P', '1080P', '2K']
|
||||
const durationOptions = ['35秒', '45秒', '60秒', '120秒']
|
||||
const styleOptions = ['强转化', '直播感', '高级感', '高节奏切片']
|
||||
const sceneModeOptions = ['提示词微调', '上传场景图替换']
|
||||
const genderOptions = ['女', '男']
|
||||
const voiceToneOptions = ['甜美', '萝莉', '御姐', '专业讲解', '原视频提取']
|
||||
const languageOptions = ['国语', '英语', '粤语', '西班牙语']
|
||||
const speechSourceOptions = ['文本直出', '从原视频提取语气', '从音频样本提取']
|
||||
const scriptModeOptions = ['直接口播', '原话术微调', '纯音乐版本']
|
||||
const categoryOptions = ['鞋子', '男装', '女装', '美妆', '家居', '零食', '数码']
|
||||
const platformOptions = ['抖音', '快手', '视频号', '小红书']
|
||||
|
||||
const tabCopies: Record<WorkspaceTab, TabCopy> = {
|
||||
remake: {
|
||||
summaryLabel: '视频复刻',
|
||||
sourceDescription: '上传原始带货视频后,可按原镜头节奏重组,也可以只保留人设或话术结构。',
|
||||
primaryAssetTitle: '原始带货视频',
|
||||
primaryAssetDescription: '主素材决定复刻的节奏、口播方式和镜头分布。',
|
||||
primaryAssetPlaceholder: '上传需要复刻的视频',
|
||||
primaryAssetHelper: '支持 MP4 / MOV,建议 35 秒到 2 分钟',
|
||||
primaryAssetAccept: 'video/*',
|
||||
secondaryAssetTitle: '补充素材',
|
||||
secondaryAssetDescription: '可上传细节镜头、对比图或备用商品画面。',
|
||||
secondaryAssetPlaceholder: '上传补充视频或商品图',
|
||||
secondaryAssetHelper: '支持视频或图片,作为替补镜头使用',
|
||||
secondaryAssetAccept: 'video/*,image/*',
|
||||
sourceStrategies: ['保留镜头节奏', '只保留话术结构', '只保留人设风格'],
|
||||
},
|
||||
imageToVideo: {
|
||||
summaryLabel: '图生视频',
|
||||
sourceDescription: '从静态图起稿,补上场景和人物驱动逻辑,最终生成完整带货短视频。',
|
||||
primaryAssetTitle: '产品主图',
|
||||
primaryAssetDescription: '主图是整条视频的起点,决定产品主体和构图中心。',
|
||||
primaryAssetPlaceholder: '上传产品主图',
|
||||
primaryAssetHelper: '建议白底图或精修图,主体完整清晰',
|
||||
primaryAssetAccept: 'image/*',
|
||||
secondaryAssetTitle: '场景参考图',
|
||||
secondaryAssetDescription: '需要指定拍摄环境时上传参考图,便于稳定光线和景别。',
|
||||
secondaryAssetPlaceholder: '上传场景参考图',
|
||||
secondaryAssetHelper: '支持 JPG / PNG',
|
||||
secondaryAssetAccept: 'image/*',
|
||||
sourceStrategies: ['静态图起稿', '主图+场景驱动', '主图+人像驱动'],
|
||||
},
|
||||
}
|
||||
|
||||
function createAssetState(): AssetState {
|
||||
return {
|
||||
fileName: '',
|
||||
previewUrl: '',
|
||||
previewKind: 'file',
|
||||
}
|
||||
}
|
||||
|
||||
function createWorkspaceState(): WorkspaceState {
|
||||
return {
|
||||
primaryAsset: createAssetState(),
|
||||
secondaryAsset: createAssetState(),
|
||||
sceneAsset: createAssetState(),
|
||||
avatarAsset: createAssetState(),
|
||||
musicAsset: createAssetState(),
|
||||
sourceStrategy: '保留镜头节奏',
|
||||
ratio: '9:16',
|
||||
resolution: '1080P',
|
||||
duration: '35秒',
|
||||
style: '强转化',
|
||||
sceneMode: '提示词微调',
|
||||
scenePrompt: '',
|
||||
cameraRhythm: 6,
|
||||
gender: '女',
|
||||
language: '国语',
|
||||
voiceTone: '甜美',
|
||||
speechSource: '文本直出',
|
||||
scriptMode: '直接口播',
|
||||
scriptText: '',
|
||||
scriptRules: '',
|
||||
categories: ['鞋子'],
|
||||
platforms: ['抖音'],
|
||||
productFocus: '',
|
||||
cta: '',
|
||||
notes: '',
|
||||
}
|
||||
}
|
||||
|
||||
const workspaces = reactive<Record<WorkspaceTab, WorkspaceState>>({
|
||||
remake: createWorkspaceState(),
|
||||
imageToVideo: {
|
||||
...createWorkspaceState(),
|
||||
sourceStrategy: '静态图起稿',
|
||||
},
|
||||
})
|
||||
|
||||
const currentWorkspace = computed(() => workspaces[activeTab.value])
|
||||
const currentTabCopy = computed(() => tabCopies[activeTab.value])
|
||||
|
||||
const previewUrls = new Set<string>()
|
||||
|
||||
function resolvePreviewKind(file: File): PreviewKind {
|
||||
if (file.type.startsWith('image/')) {
|
||||
return 'image'
|
||||
}
|
||||
if (file.type.startsWith('video/')) {
|
||||
return 'video'
|
||||
}
|
||||
return 'file'
|
||||
}
|
||||
|
||||
function releasePreviewUrl(url: string) {
|
||||
if (!url.startsWith('blob:')) {
|
||||
return
|
||||
}
|
||||
URL.revokeObjectURL(url)
|
||||
previewUrls.delete(url)
|
||||
}
|
||||
|
||||
function setAsset(tab: WorkspaceTab, assetKey: AssetKey, file: File) {
|
||||
const target = workspaces[tab][assetKey]
|
||||
if (target.previewUrl) {
|
||||
releasePreviewUrl(target.previewUrl)
|
||||
}
|
||||
|
||||
target.fileName = file.name
|
||||
target.previewKind = resolvePreviewKind(file)
|
||||
target.previewUrl = target.previewKind === 'file' ? '' : URL.createObjectURL(file)
|
||||
if (target.previewUrl) {
|
||||
previewUrls.add(target.previewUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function clearAsset(tab: WorkspaceTab, assetKey: AssetKey) {
|
||||
const target = workspaces[tab][assetKey]
|
||||
if (target.previewUrl) {
|
||||
releasePreviewUrl(target.previewUrl)
|
||||
}
|
||||
target.fileName = ''
|
||||
target.previewUrl = ''
|
||||
target.previewKind = 'file'
|
||||
}
|
||||
|
||||
function saveDraft() {
|
||||
ElMessage.success(`${currentTabCopy.value.summaryLabel}草稿已保存`)
|
||||
}
|
||||
|
||||
function exportConfig() {
|
||||
ElMessage.success(`已导出${currentTabCopy.value.summaryLabel}配置`)
|
||||
}
|
||||
|
||||
function buildStoryboard() {
|
||||
ElMessage.success(`已生成${currentTabCopy.value.summaryLabel}镜头方案草稿`)
|
||||
}
|
||||
|
||||
function startAssembly() {
|
||||
ElMessage.success(`已加入${currentTabCopy.value.summaryLabel}组装队列`)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
previewUrls.forEach((url) => URL.revokeObjectURL(url))
|
||||
previewUrls.clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.delivery-workspace {
|
||||
min-width: 0;
|
||||
--delivery-accent: #c59a5a;
|
||||
--delivery-accent-strong: #e0bb82;
|
||||
--delivery-accent-soft: rgba(197, 154, 90, 0.16);
|
||||
--delivery-surface: #171717;
|
||||
--delivery-surface-2: #1d1d1d;
|
||||
--delivery-border: #323232;
|
||||
}
|
||||
|
||||
.delivery-workspace__tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.delivery-workspace__tab-panel {
|
||||
--el-color-primary: var(--delivery-accent);
|
||||
}
|
||||
|
||||
:deep(.delivery-workspace__tab-panel .el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.delivery-workspace__tab-panel .el-tabs__nav-wrap::after) {
|
||||
background: #313131;
|
||||
}
|
||||
|
||||
:deep(.delivery-workspace__tab-panel .el-tabs__item) {
|
||||
height: 40px;
|
||||
color: #969696;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.delivery-workspace__tab-panel .el-tabs__item.is-active) {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:deep(.delivery-workspace__tab-panel .el-tabs__active-bar) {
|
||||
background: var(--delivery-accent);
|
||||
}
|
||||
|
||||
.delivery-grid {
|
||||
column-count: 4;
|
||||
column-gap: 14px;
|
||||
}
|
||||
|
||||
.delivery-card {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
display: inline-flex;
|
||||
margin: 0 0 14px;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.delivery-form-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.delivery-form-grid--two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.delivery-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.delivery-field__label {
|
||||
color: #f3f3f3;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.delivery-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.task-actions__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.task-text-btn {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--delivery-accent-strong);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.task-text-btn:hover {
|
||||
color: #f0d0a0;
|
||||
}
|
||||
|
||||
.preview-panel__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--delivery-border);
|
||||
background: var(--delivery-surface-2);
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.preview-row__label {
|
||||
color: #969696;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.preview-row__value {
|
||||
color: #f1f1f1;
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.preview-panel__surface {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 132px;
|
||||
border: 1px solid var(--delivery-border);
|
||||
border-radius: 14px;
|
||||
background: #202020;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-panel__media {
|
||||
width: 100%;
|
||||
height: 132px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.delivery-card--preview .preview-panel__meta {
|
||||
padding: 9px 11px;
|
||||
}
|
||||
|
||||
.delivery-card--preview .task-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.preview-panel__placeholder {
|
||||
padding: 20px;
|
||||
color: #7f7f7f;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.ai-section-card) {
|
||||
height: 100%;
|
||||
border-color: var(--delivery-border);
|
||||
border-radius: 14px;
|
||||
background: var(--delivery-surface);
|
||||
}
|
||||
|
||||
:deep(.ai-section-card__header) {
|
||||
align-items: flex-start;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
:deep(.ai-section-card__body) {
|
||||
gap: 12px;
|
||||
padding: 12px 14px 14px;
|
||||
}
|
||||
|
||||
:deep(.ai-section-card__badge) {
|
||||
background: linear-gradient(135deg, #7d6744 0%, #b18a54 100%);
|
||||
}
|
||||
|
||||
:deep(.ai-choice-pills) {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.ai-choice-pills__item) {
|
||||
min-height: 34px;
|
||||
padding: 0 12px;
|
||||
border-color: #444;
|
||||
background: #1a1a1a;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.ai-choice-pills__item:hover) {
|
||||
border-color: #7b6a52;
|
||||
}
|
||||
|
||||
:deep(.ai-choice-pills__item--active) {
|
||||
border-color: var(--delivery-accent);
|
||||
background: var(--delivery-accent-soft);
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__description) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__upload .el-upload-dragger) {
|
||||
min-height: 138px;
|
||||
padding: 8px;
|
||||
border-color: #434343;
|
||||
background: #1d1d1d;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__upload .el-upload-dragger:hover) {
|
||||
border-color: var(--delivery-accent);
|
||||
background: #212121;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__preview),
|
||||
:deep(.ai-asset-dropzone__empty) {
|
||||
min-height: 118px;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__media) {
|
||||
height: 118px;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__title) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.ai-asset-dropzone__description),
|
||||
:deep(.ai-asset-dropzone__helper),
|
||||
:deep(.ai-asset-dropzone__filename) {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
:deep(.ai-section-card__title) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.ai-section-card__description) {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper),
|
||||
:deep(.el-textarea__inner),
|
||||
:deep(.el-select__wrapper) {
|
||||
background: #121212;
|
||||
box-shadow: 0 0 0 1px #414141 inset;
|
||||
color: #f2f2f2;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper.is-focus),
|
||||
:deep(.el-textarea__inner:focus),
|
||||
:deep(.el-select__wrapper.is-focused) {
|
||||
box-shadow: 0 0 0 1px var(--delivery-accent) inset;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner),
|
||||
:deep(.el-select__selected-item),
|
||||
:deep(.el-textarea__inner) {
|
||||
color: #f2f2f2;
|
||||
}
|
||||
|
||||
:deep(.el-textarea__inner::placeholder),
|
||||
:deep(.el-input__inner::placeholder) {
|
||||
color: #717171;
|
||||
}
|
||||
|
||||
:deep(.el-slider__runway) {
|
||||
background: #2c2c2c;
|
||||
}
|
||||
|
||||
:deep(.el-slider__bar) {
|
||||
background: linear-gradient(90deg, #8f6a3b, #d1ac71);
|
||||
}
|
||||
|
||||
:deep(.el-button.delivery-primary-btn) {
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
background: linear-gradient(90deg, #8f6a3b, #c49a58);
|
||||
border-color: transparent;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:deep(.el-button.delivery-primary-btn:hover) {
|
||||
background: linear-gradient(90deg, #7b5a31, #b78b49);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
:deep(.el-button.delivery-secondary-btn) {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
background: #171717;
|
||||
border-color: #4e4e4e;
|
||||
color: #d8d8d8;
|
||||
}
|
||||
|
||||
:deep(.el-button.delivery-secondary-btn:hover) {
|
||||
border-color: var(--delivery-accent);
|
||||
color: #fff;
|
||||
background: #1b1b1b;
|
||||
}
|
||||
|
||||
@media (max-width: 1800px) {
|
||||
.delivery-grid {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1380px) {
|
||||
.delivery-grid {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.delivery-grid {
|
||||
column-count: 1;
|
||||
}
|
||||
|
||||
.delivery-form-grid--two {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user