AI_Painting_V2.0/src/platforms/music/controls/modeSelector.vue
WangLeo 2d12c5a20b feat: 新增 Music 音乐生成平台,遵循 Platform Descriptor 模式
基于旧项目 ai_music_v2.0 迁移,与 Painting/Video 统一架构:HTTP 轮询 + suanli 后端、
API 驱动配置、mode 独立 ref 驱动控件显隐。新增 AudioPlayer/CustomSlider 通用组件,
dialogBox/set.vue/taskPolling/modelApi 完成集成适配。
2026-06-12 19:20:18 +08:00

146 lines
4.2 KiB
Vue

<template>
<div class="custom-popover-wrapper">
<div class="choice-btn" :class="{ active: showPopover }" @click.stop="togglePopover">
<img :src="currentIcon" alt="" style="width: 16px;">
<span>{{ currentLabel }}</span>
</div>
<Transition name="popover">
<div v-show="showPopover" class="custom-popover" @click.stop>
<div class="select">
<div class="model-group">
<div
v-for="item in modeOptions"
:key="item.value"
class="model-item"
:class="{ active: modeValue === item.value, disabled: item.disabled }"
@click="selectMode(item)"
>
<img :src="item.icon" alt="" style="width: 16px; height: 16px;">
<span>{{ item.label }}</span>
</div>
</div>
</div>
</div>
</Transition>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import commonModeIcon from '@/assets/dialog/commonMode.svg'
import professionalModeIcon from '@/assets/dialog/professionalMode.svg'
import remixModeIcon from '@/assets/dialog/remixMode.svg'
import editModeIcon from '@/assets/dialog/editMode.svg'
const props = defineProps({
modelValue: { type: String, default: '' },
options: { type: Array, default: () => [] }
})
const emit = defineEmits(['update:modelValue'])
const showPopover = ref(false)
const modeValue = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v)
})
const iconMap = {
'常用模式': commonModeIcon,
'专业模式': professionalModeIcon,
'Remix模式': remixModeIcon,
'编辑模式': editModeIcon
}
const modeOptions = computed(() => {
if (props.options.length) return props.options.map(o => ({ ...o, icon: iconMap[o.label] || commonModeIcon }))
return [
{ value: '常用模式', label: '常用模式', icon: commonModeIcon },
{ value: '专业模式', label: '专业模式', icon: professionalModeIcon },
{ value: 'Remix模式', label: 'Remix模式', icon: remixModeIcon, disabled: true },
{ value: '编辑模式', label: '编辑模式', icon: editModeIcon, disabled: true }
]
})
const currentIcon = computed(() => {
const found = modeOptions.value.find(m => m.value === modeValue.value)
return found?.icon || commonModeIcon
})
const currentLabel = computed(() => {
const found = modeOptions.value.find(m => m.value === modeValue.value)
return found?.label || modeValue.value
})
const togglePopover = () => { showPopover.value = !showPopover.value }
const selectMode = (item) => {
if (item.disabled) return
modeValue.value = item.value
showPopover.value = false
}
</script>
<style lang="less" scoped>
.choice-btn {
display: flex;
height: 40px;
padding: 0 15px;
justify-content: center;
align-items: center;
gap: 5px;
border-radius: 10px;
border: 1px solid #E9EAEB;
background: #f5f6f7;
cursor: pointer;
position: relative;
&:hover, &.active { background: #E9EAEB; }
img { filter: brightness(0) drop-shadow(0 0 0 #000F33); }
}
.custom-popover-wrapper { position: relative; display: inline-block; }
.custom-popover {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
width: auto;
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.10);
border-radius: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
z-index: 9999;
}
.select { padding: 20px 10px; max-height: 510px; overflow-y: auto; }
.model-group { margin-bottom: 15px; &:last-child { margin-bottom: 0; } }
.model-item {
padding: 8px 10px;
border-radius: 10px;
cursor: pointer;
font-size: 14px;
margin-bottom: 4px;
transition: all 0.2s ease;
color: #666;
font-family: "Microsoft YaHei";
white-space: nowrap;
display: flex;
align-items: center;
gap: 8px;
&:last-child { margin-bottom: 0; }
&:hover:not(.disabled) { background: #f5f6f7; }
&.active {
background: rgba(0, 15, 51, 0.10);
color: #333;
font-weight: 500;
img { filter: brightness(0) drop-shadow(0 0 0 #000F33); }
}
&.disabled { cursor: not-allowed; opacity: 0.5; }
}
.popover-enter-active, .popover-leave-active { transition: all 0.3s ease; }
.popover-enter-from, .popover-leave-to { opacity: 0; transform: translateX(-50%) translateY(10px); }
</style>