feat: 优化
This commit is contained in:
@@ -148,24 +148,30 @@
|
|||||||
<!-- 场景内容 -->
|
<!-- 场景内容 -->
|
||||||
<div class="scene-body">
|
<div class="scene-body">
|
||||||
<template v-if="scene.candidates?.length > 0">
|
<template v-if="scene.candidates?.length > 0">
|
||||||
<div
|
<!-- 候选素材可拖拽排序 -->
|
||||||
v-for="(candidate, cIndex) in scene.candidates"
|
<draggable
|
||||||
:key="cIndex"
|
v-model="scene.candidates"
|
||||||
class="candidate-thumb"
|
item-key="fileId"
|
||||||
@click.stop="removeCandidate(index, cIndex)"
|
animation="200"
|
||||||
|
class="candidates-draggable"
|
||||||
|
:group="{ name: `scene-${index}` }"
|
||||||
>
|
>
|
||||||
<img
|
<template #item="{ element: candidate, index: cIndex }">
|
||||||
v-if="getFileById(candidate.fileId)?.coverBase64"
|
<div class="candidate-thumb">
|
||||||
:src="getFileById(candidate.fileId).coverBase64"
|
<img
|
||||||
/>
|
v-if="getFileById(candidate.fileId)?.coverBase64"
|
||||||
<div v-else class="thumb-placeholder">
|
:src="getFileById(candidate.fileId).coverBase64"
|
||||||
<VideoCameraOutlined />
|
/>
|
||||||
</div>
|
<div v-else class="thumb-placeholder">
|
||||||
<div class="thumb-order">{{ cIndex + 1 }}</div>
|
<VideoCameraOutlined />
|
||||||
<div class="thumb-remove">
|
</div>
|
||||||
<CloseOutlined />
|
<div class="thumb-order">{{ cIndex + 1 }}</div>
|
||||||
</div>
|
<div class="thumb-remove" @click.stop="removeCandidate(index, cIndex)">
|
||||||
</div>
|
<CloseOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
<div
|
<div
|
||||||
v-if="scene.candidates.length < constants.MAX_CANDIDATES_PER_SCENE"
|
v-if="scene.candidates.length < constants.MAX_CANDIDATES_PER_SCENE"
|
||||||
class="candidate-add"
|
class="candidate-add"
|
||||||
@@ -420,19 +426,18 @@ watch(sceneCount, (newCount) => {
|
|||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 当 clipDuration 变化时,同步更新所有场景的 duration,并保存到本地
|
// 同步场景时长并保存参数到本地
|
||||||
watch(() => formData.value.clipDuration, (newDuration) => {
|
watch([
|
||||||
localStorage.setItem('mix-clip-duration', newDuration.toString())
|
() => 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 => {
|
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 = () => {
|
const onSceneDragEnd = () => {
|
||||||
dataState.value.scenes.forEach((scene, index) => {
|
dataState.value.scenes.forEach((scene, index) => {
|
||||||
scene.index = index
|
scene.index = index
|
||||||
@@ -450,11 +455,8 @@ const clearScenes = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removeCandidate = (sceneIndex, candidateIndex) => {
|
const removeCandidate = (sceneIndex, candidateIndex) => {
|
||||||
const candidates = dataState.value.scenes[sceneIndex]?.candidates
|
dataState.value.scenes[sceneIndex]?.candidates?.splice(candidateIndex, 1)
|
||||||
if (candidates?.[candidateIndex] !== undefined) {
|
message.success('已移除候选')
|
||||||
candidates.splice(candidateIndex, 1)
|
|
||||||
message.success('已移除候选')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSceneSelector = (index) => {
|
const openSceneSelector = (index) => {
|
||||||
@@ -462,20 +464,6 @@ const openSceneSelector = (index) => {
|
|||||||
uiState.value.selectorVisible = true
|
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
|
fileUrl: getFileById(fileId)?.fileUrl
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (setGroupId !== null && setGroupId !== undefined) {
|
if (setGroupId != null) {
|
||||||
scene.groupId = setGroupId
|
scene.groupId = setGroupId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,6 +947,12 @@ onMounted(() => {
|
|||||||
min-height: 90px;
|
min-height: 90px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
|
// 可拖拽容器
|
||||||
|
.candidates-draggable {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
.candidate-thumb {
|
.candidate-thumb {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
@@ -966,10 +960,14 @@ onMounted(() => {
|
|||||||
border-radius: var(--radius-button);
|
border-radius: var(--radius-button);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: grab;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
transition: all var(--duration-base) ease;
|
transition: all var(--duration-base) ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -1001,24 +999,38 @@ onMounted(() => {
|
|||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🗑️ 删除遮罩
|
// 🗑️ 删除按钮 - 右上角小按钮
|
||||||
.thumb-remove {
|
.thumb-remove {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba(239, 68, 68, 0.9);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
color: var(--color-text-inverse);
|
color: var(--color-text-inverse);
|
||||||
font-size: var(--font-size-md);
|
font-size: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity var(--duration-base) ease;
|
z-index: 3;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--duration-fast) ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-error-500);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hover 效果 - 柔和的高亮边框
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: @danger;
|
border-color: var(--color-primary-400);
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
||||||
|
|
||||||
.thumb-remove {
|
.thumb-remove {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@@ -21,15 +21,15 @@
|
|||||||
|
|
||||||
<!-- 分组切换 -->
|
<!-- 分组切换 -->
|
||||||
<div v-if="groupList.length > 1" class="group-tabs">
|
<div v-if="groupList.length > 1" class="group-tabs">
|
||||||
<a-tag
|
<div
|
||||||
v-for="g in groupList"
|
v-for="g in groupList"
|
||||||
:key="g.id"
|
:key="g.id"
|
||||||
:color="selectorGroupId === g.id ? 'blue' : 'default'"
|
class="group-tab-item"
|
||||||
style="cursor: pointer;"
|
:class="{ 'group-tab-item--active': selectorGroupId === g.id }"
|
||||||
@click="handleSelectorGroupChange(g.id)"
|
@click="handleSelectorGroupChange(g.id)"
|
||||||
>
|
>
|
||||||
{{ g.name }}
|
{{ g.name }}
|
||||||
</a-tag>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-button type="text" size="small" @click="$emit('update:visible', false)">
|
<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 emit = defineEmits(['update:visible', 'confirm', 'load-group-files'])
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_KEY = 'mix-scene-selector-group-id'
|
||||||
|
|
||||||
const selectorGroupId = ref(null)
|
const selectorGroupId = ref(null)
|
||||||
const tempSelectedFiles = ref([])
|
const tempSelectedFiles = ref([])
|
||||||
|
|
||||||
@@ -152,7 +154,28 @@ const selectorFiles = computed(() => {
|
|||||||
watch(() => props.visible, (visible) => {
|
watch(() => props.visible, (visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
tempSelectedFiles.value = [...props.initialCandidates]
|
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]) {
|
if (selectorGroupId.value && !props.allGroupFiles[selectorGroupId.value]) {
|
||||||
emit('load-group-files', 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 isSelected = (fileId) => tempSelectedFiles.value.includes(fileId)
|
||||||
const getSelectionOrder = (fileId) => tempSelectedFiles.value.indexOf(fileId) + 1
|
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 toggleSelection = (file) => {
|
||||||
const index = tempSelectedFiles.value.indexOf(file.id)
|
const index = tempSelectedFiles.value.indexOf(file.id)
|
||||||
if (index > -1) {
|
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) => {
|
const handleQuickConfirm = (file) => {
|
||||||
if (!isSelected(file.id)) toggleSelection(file)
|
if (!isSelected(file.id)) toggleSelection(file)
|
||||||
handleConfirm()
|
handleConfirm()
|
||||||
@@ -236,6 +249,8 @@ const handleAutoFill = () => {
|
|||||||
|
|
||||||
const handleSelectorGroupChange = (groupId) => {
|
const handleSelectorGroupChange = (groupId) => {
|
||||||
selectorGroupId.value = groupId
|
selectorGroupId.value = groupId
|
||||||
|
// 保存到本地存储
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY, String(groupId))
|
||||||
tempSelectedFiles.value = []
|
tempSelectedFiles.value = []
|
||||||
if (groupId && !props.allGroupFiles[groupId]) {
|
if (groupId && !props.allGroupFiles[groupId]) {
|
||||||
emit('load-group-files', groupId)
|
emit('load-group-files', groupId)
|
||||||
@@ -345,7 +360,35 @@ const handleConfirm = () => {
|
|||||||
|
|
||||||
.group-tabs {
|
.group-tabs {
|
||||||
display: flex;
|
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