| <template> |
| <div class="source-selector-overlay" @click="$emit('close')"> |
| <div class="source-selector-content" @click.stop> |
| <div class="selector-header"> |
| <h3>选择音乐源</h3> |
| <button class="close-btn" @click="$emit('close')"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="sources-list"> |
| <button |
| v-for="source in musicSources" |
| :key="source.code" |
| class="source-item" |
| :class="{ active: source.code === currentSource }" |
| @click="selectSource(source.code)" |
| > |
| <div class="source-info"> |
| <span class="source-name">{{ source.name }}</span> |
| <span class="source-priority">优先级 {{ source.priority }}</span> |
| </div> |
| <i v-if="source.code === currentSource" class="fas fa-check check-icon"></i> |
| </button> |
| </div> |
| |
| <div class="selector-footer"> |
| <p class="footer-tip">建议优先使用网易云音乐,资源更丰富</p> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script setup> |
| import { computed } from 'vue' |
| import { useSearchStore } from '@/stores/search' |
| import { MUSIC_SOURCES } from '@/services/musicApi' |
| |
| const emit = defineEmits(['close', 'select']) |
| |
| const searchStore = useSearchStore() |
| |
| |
| const currentSource = computed(() => searchStore.currentSource) |
| const musicSources = computed(() => |
| MUSIC_SOURCES.sort((a, b) => a.priority - b.priority) |
| ) |
| |
| |
| const selectSource = (sourceCode) => { |
| emit('select', sourceCode) |
| emit('close') |
| } |
| </script> |
| |
| <style scoped> |
| .source-selector-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: rgba(0, 0, 0, 0.6); |
| backdrop-filter: blur(4px); |
| display: flex; |
| align-items: flex-end; |
| z-index: 2000; |
| animation: fadeIn 0.3s ease; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| .source-selector-content { |
| width: 100%; |
| max-height: 70vh; |
| background: var(--bg-card); |
| backdrop-filter: blur(20px); |
| border-radius: 20px 20px 0 0; |
| animation: slideUp 0.3s ease; |
| overflow: hidden; |
| border: 1px solid var(--border-strong); |
| box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2); |
| } |
| |
| |
| @media (min-width: 768px) { |
| .source-selector-overlay { |
| align-items: center; |
| justify-content: center; |
| background: rgba(0, 0, 0, 0.5); |
| } |
| |
| .source-selector-content { |
| width: 400px; |
| max-height: 500px; |
| border-radius: 16px; |
| animation: scaleIn 0.3s ease; |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); |
| } |
| |
| @keyframes scaleIn { |
| from { |
| opacity: 0; |
| transform: scale(0.9) translateY(20px); |
| } |
| to { |
| opacity: 1; |
| transform: scale(1) translateY(0); |
| } |
| } |
| } |
| |
| @keyframes slideUp { |
| from { transform: translateY(100%); } |
| to { transform: translateY(0); } |
| } |
| |
| .selector-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 20px 24px; |
| border-bottom: 1px solid var(--border-strong); |
| background: var(--overlay-lighter); |
| } |
| |
| .selector-header h3 { |
| font-size: 18px; |
| font-weight: 600; |
| color: var(--text-primary); |
| margin: 0; |
| } |
| |
| .close-btn { |
| width: 32px; |
| height: 32px; |
| border-radius: 16px; |
| background: rgba(255, 255, 255, 0.1); |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| transition: var(--transition-fast); |
| } |
| |
| .close-btn:hover { |
| background: rgba(255, 255, 255, 0.2); |
| color: var(--text-primary); |
| } |
| |
| .sources-list { |
| max-height: calc(70vh - 160px); |
| overflow-y: auto; |
| padding: 8px 0; |
| background: var(--bg-card); |
| } |
| |
| .source-item { |
| width: 100%; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 16px 24px; |
| background: transparent; |
| border: none; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| border-bottom: 1px solid var(--border-lighter); |
| } |
| |
| .source-item:hover { |
| background: var(--overlay-lighter); |
| border-bottom: 1px solid var(--border-light); |
| } |
| |
| .source-item.active { |
| background: var(--bg-gradient-3); |
| color: var(--accent-red); |
| border-bottom: 1px solid var(--accent-red); |
| } |
| |
| .source-info { |
| display: flex; |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 4px; |
| } |
| |
| .source-name { |
| font-size: 16px; |
| font-weight: 500; |
| color: var(--text-primary); |
| } |
| |
| .source-item.active .source-name { |
| color: var(--accent-red); |
| } |
| |
| .source-priority { |
| font-size: 12px; |
| color: var(--text-disabled); |
| } |
| |
| .check-icon { |
| color: var(--accent-red); |
| font-size: 16px; |
| } |
| |
| .selector-footer { |
| padding: 16px 24px; |
| border-top: 1px solid var(--border-strong); |
| background: rgba(255, 255, 255, 0.02); |
| } |
| |
| .footer-tip { |
| font-size: 12px; |
| color: var(--text-disabled); |
| text-align: center; |
| margin: 0; |
| line-height: 1.4; |
| } |
| |
| |
| .sources-list::-webkit-scrollbar { |
| width: 6px; |
| } |
| |
| .sources-list::-webkit-scrollbar-track { |
| background: transparent; |
| } |
| |
| .sources-list::-webkit-scrollbar-thumb { |
| background: rgba(255, 255, 255, 0.3); |
| border-radius: 3px; |
| } |
| |
| .sources-list::-webkit-scrollbar-thumb:hover { |
| background: rgba(255, 255, 255, 0.5); |
| } |
| </style> |