feat: 优化
This commit is contained in:
@@ -148,12 +148,16 @@
|
||||
<!-- 场景内容 -->
|
||||
<div class="scene-body">
|
||||
<template v-if="scene.candidates?.length > 0">
|
||||
<div
|
||||
v-for="(candidate, cIndex) in scene.candidates"
|
||||
:key="cIndex"
|
||||
class="candidate-thumb"
|
||||
@click.stop="removeCandidate(index, cIndex)"
|
||||
<!-- 候选素材可拖拽排序 -->
|
||||
<draggable
|
||||
v-model="scene.candidates"
|
||||
item-key="fileId"
|
||||
animation="200"
|
||||
class="candidates-draggable"
|
||||
:group="{ name: `scene-${index}` }"
|
||||
>
|
||||
<template #item="{ element: candidate, index: cIndex }">
|
||||
<div class="candidate-thumb">
|
||||
<img
|
||||
v-if="getFileById(candidate.fileId)?.coverBase64"
|
||||
:src="getFileById(candidate.fileId).coverBase64"
|
||||
@@ -162,10 +166,12 @@
|
||||
<VideoCameraOutlined />
|
||||
</div>
|
||||
<div class="thumb-order">{{ cIndex + 1 }}</div>
|
||||
<div class="thumb-remove">
|
||||
<div class="thumb-remove" @click.stop="removeCandidate(index, cIndex)">
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div
|
||||
v-if="scene.candidates.length < constants.MAX_CANDIDATES_PER_SCENE"
|
||||
class="candidate-add"
|
||||
@@ -420,19 +426,18 @@ watch(sceneCount, (newCount) => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 当 clipDuration 变化时,同步更新所有场景的 duration,并保存到本地
|
||||
watch(() => formData.value.clipDuration, (newDuration) => {
|
||||
localStorage.setItem('mix-clip-duration', newDuration.toString())
|
||||
// 同步场景时长并保存参数到本地
|
||||
watch([
|
||||
() => formData.value.clipDuration,
|
||||
() => formData.value.totalDuration
|
||||
], ([clipDuration, totalDuration]) => {
|
||||
localStorage.setItem('mix-clip-duration', clipDuration.toString())
|
||||
localStorage.setItem('mix-total-duration', totalDuration.toString())
|
||||
dataState.value.scenes.forEach(scene => {
|
||||
scene.duration = newDuration
|
||||
scene.duration = clipDuration
|
||||
})
|
||||
})
|
||||
|
||||
// 当 totalDuration 变化时,保存到本地
|
||||
watch(() => formData.value.totalDuration, (newDuration) => {
|
||||
localStorage.setItem('mix-total-duration', newDuration.toString())
|
||||
})
|
||||
|
||||
const onSceneDragEnd = () => {
|
||||
dataState.value.scenes.forEach((scene, index) => {
|
||||
scene.index = index
|
||||
@@ -450,32 +455,15 @@ const clearScenes = () => {
|
||||
}
|
||||
|
||||
const removeCandidate = (sceneIndex, candidateIndex) => {
|
||||
const candidates = dataState.value.scenes[sceneIndex]?.candidates
|
||||
if (candidates?.[candidateIndex] !== undefined) {
|
||||
candidates.splice(candidateIndex, 1)
|
||||
dataState.value.scenes[sceneIndex]?.candidates?.splice(candidateIndex, 1)
|
||||
message.success('已移除候选')
|
||||
}
|
||||
}
|
||||
|
||||
const openSceneSelector = (index) => {
|
||||
uiState.value.currentSceneIndex = index
|
||||
uiState.value.selectorVisible = true
|
||||
}
|
||||
|
||||
const openGroupSelector = (index) => {
|
||||
const groups = dataState.value.groupList
|
||||
if (groups.length === 0) return
|
||||
|
||||
const currentGroupId = dataState.value.scenes[index].groupId
|
||||
const currentIndex = groups.findIndex(g => g.id === currentGroupId)
|
||||
const nextIndex = (currentIndex + 1) % groups.length
|
||||
const nextGroup = groups[nextIndex]
|
||||
|
||||
dataState.value.scenes[index].groupId = nextGroup.id
|
||||
dataState.value.scenes[index].candidates = []
|
||||
loadGroupFiles(nextGroup.id)
|
||||
message.success(`场景${index + 1} 切换到 ${nextGroup.name}`)
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// 素材操作
|
||||
@@ -607,7 +595,7 @@ const handleModalConfirm = ({ sceneIndex, fileIds, setGroupId }) => {
|
||||
fileUrl: getFileById(fileId)?.fileUrl
|
||||
}))
|
||||
|
||||
if (setGroupId !== null && setGroupId !== undefined) {
|
||||
if (setGroupId != null) {
|
||||
scene.groupId = setGroupId
|
||||
}
|
||||
|
||||
@@ -959,6 +947,12 @@ onMounted(() => {
|
||||
min-height: 90px;
|
||||
overflow-x: auto;
|
||||
|
||||
// 可拖拽容器
|
||||
.candidates-draggable {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.candidate-thumb {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
@@ -966,10 +960,14 @@ onMounted(() => {
|
||||
border-radius: var(--radius-button);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
cursor: grab;
|
||||
border: 2px solid transparent;
|
||||
transition: all var(--duration-base) ease;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -1001,24 +999,38 @@ onMounted(() => {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
border-radius: 50%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// 🗑️ 删除遮罩
|
||||
// 🗑️ 删除按钮 - 右上角小按钮
|
||||
.thumb-remove {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: var(--color-text-inverse);
|
||||
font-size: var(--font-size-md);
|
||||
font-size: 10px;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transition: opacity var(--duration-base) ease;
|
||||
}
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-fast) ease;
|
||||
|
||||
&:hover {
|
||||
border-color: @danger;
|
||||
background: var(--color-error-500);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// hover 效果 - 柔和的高亮边框
|
||||
&:hover {
|
||||
border-color: var(--color-primary-400);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
||||
|
||||
.thumb-remove {
|
||||
opacity: 1;
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
|
||||
<!-- 分组切换 -->
|
||||
<div v-if="groupList.length > 1" class="group-tabs">
|
||||
<a-tag
|
||||
<div
|
||||
v-for="g in groupList"
|
||||
:key="g.id"
|
||||
:color="selectorGroupId === g.id ? 'blue' : 'default'"
|
||||
style="cursor: pointer;"
|
||||
class="group-tab-item"
|
||||
:class="{ 'group-tab-item--active': selectorGroupId === g.id }"
|
||||
@click="handleSelectorGroupChange(g.id)"
|
||||
>
|
||||
{{ g.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-button type="text" size="small" @click="$emit('update:visible', false)">
|
||||
@@ -134,6 +134,8 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:visible', 'confirm', 'load-group-files'])
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'mix-scene-selector-group-id'
|
||||
|
||||
const selectorGroupId = ref(null)
|
||||
const tempSelectedFiles = ref([])
|
||||
|
||||
@@ -152,7 +154,28 @@ const selectorFiles = computed(() => {
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible) {
|
||||
tempSelectedFiles.value = [...props.initialCandidates]
|
||||
selectorGroupId.value = props.sceneGroupId || props.globalGroupId || null
|
||||
|
||||
// 优先级:1. 场景自带分组 2. 本地存储的分组 3. 全局分组 4. 第一个分组
|
||||
let targetGroupId = props.sceneGroupId
|
||||
|
||||
if (!targetGroupId) {
|
||||
const savedGroupId = localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||
if (savedGroupId && props.groupList.some(g => String(g.id) === savedGroupId)) {
|
||||
targetGroupId = savedGroupId
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetGroupId && props.globalGroupId) {
|
||||
targetGroupId = props.globalGroupId
|
||||
}
|
||||
|
||||
if (!targetGroupId && props.groupList.length > 0) {
|
||||
targetGroupId = props.groupList[0].id
|
||||
}
|
||||
|
||||
selectorGroupId.value = targetGroupId
|
||||
|
||||
// 加载分组文件
|
||||
if (selectorGroupId.value && !props.allGroupFiles[selectorGroupId.value]) {
|
||||
emit('load-group-files', selectorGroupId.value)
|
||||
}
|
||||
@@ -174,11 +197,6 @@ onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
|
||||
const isSelected = (fileId) => tempSelectedFiles.value.includes(fileId)
|
||||
const getSelectionOrder = (fileId) => tempSelectedFiles.value.indexOf(fileId) + 1
|
||||
|
||||
const getFileName = (fileId) => {
|
||||
const file = selectorFiles.value.find(f => f.id === fileId)
|
||||
return file ? (file.displayName || file.fileName) : '未知'
|
||||
}
|
||||
|
||||
const toggleSelection = (file) => {
|
||||
const index = tempSelectedFiles.value.indexOf(file.id)
|
||||
if (index > -1) {
|
||||
@@ -192,11 +210,6 @@ const toggleSelection = (file) => {
|
||||
}
|
||||
}
|
||||
|
||||
const removeSelection = (fileId) => {
|
||||
const index = tempSelectedFiles.value.indexOf(fileId)
|
||||
if (index > -1) tempSelectedFiles.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleQuickConfirm = (file) => {
|
||||
if (!isSelected(file.id)) toggleSelection(file)
|
||||
handleConfirm()
|
||||
@@ -236,6 +249,8 @@ const handleAutoFill = () => {
|
||||
|
||||
const handleSelectorGroupChange = (groupId) => {
|
||||
selectorGroupId.value = groupId
|
||||
// 保存到本地存储
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, String(groupId))
|
||||
tempSelectedFiles.value = []
|
||||
if (groupId && !props.allGroupFiles[groupId]) {
|
||||
emit('load-group-files', groupId)
|
||||
@@ -345,7 +360,35 @@ const handleConfirm = () => {
|
||||
|
||||
.group-tabs {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
gap: var(--space-2);
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.group-tab-item {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
color: @text-secondary;
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-button);
|
||||
cursor: pointer;
|
||||
transition: all var(--duration-base) ease;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-gray-200);
|
||||
color: @text;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: @primary;
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
&:hover {
|
||||
background: @primary-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user