|
|
<template> |
|
|
<div class="search-box"> |
|
|
<div class="search-wrapper"> |
|
|
<div class="search-input-container"> |
|
|
<i class="fas fa-search search-icon"></i> |
|
|
<input |
|
|
v-model="searchKeyword" |
|
|
type="text" |
|
|
placeholder="搜索音乐、歌手、专辑..." |
|
|
class="search-input" |
|
|
@keypress.enter="handleSearch" |
|
|
> |
|
|
<button |
|
|
v-if="searchKeyword" |
|
|
class="clear-btn" |
|
|
@click="clearSearch" |
|
|
> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
<button |
|
|
class="history-btn" |
|
|
@click="showSearchHistory" |
|
|
title="搜索历史" |
|
|
> |
|
|
<i class="fas fa-history"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<button |
|
|
v-if="isMobile" |
|
|
class="source-btn" |
|
|
@click="showSourceSelector = true" |
|
|
> |
|
|
{{ currentSourceName }} |
|
|
<i class="fas fa-chevron-down"></i> |
|
|
</button> |
|
|
|
|
|
<select |
|
|
v-else |
|
|
:value="searchStore.currentSource" |
|
|
@change="handleSourceChange" |
|
|
class="pc-source-select" |
|
|
> |
|
|
<option |
|
|
v-for="source in sortedSources" |
|
|
:key="source.code" |
|
|
:value="source.code" |
|
|
> |
|
|
{{ source.name }} |
|
|
</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
|
|
|
<SourceSelector |
|
|
v-if="showSourceSelector" |
|
|
@close="showSourceSelector = false" |
|
|
@select="handleSourceSelect" |
|
|
/> |
|
|
</div> |
|
|
</template> |
|
|
|
|
|
<script setup> |
|
|
import { ref, computed, watch } from 'vue' |
|
|
import { useRouter } from 'vue-router' |
|
|
import { useSearchStore } from '@/stores/search' |
|
|
import { MUSIC_SOURCES } from '@/services/musicApi' |
|
|
import SourceSelector from './SourceSelector.vue' |
|
|
|
|
|
const emit = defineEmits(['search']) |
|
|
const router = useRouter() |
|
|
|
|
|
const searchStore = useSearchStore() |
|
|
|
|
|
|
|
|
const searchKeyword = ref('') |
|
|
const showSourceSelector = ref(false) |
|
|
|
|
|
|
|
|
const currentSourceName = computed(() => { |
|
|
const source = MUSIC_SOURCES.find(s => s.code === searchStore.currentSource) |
|
|
return source ? source.name : '网易云音乐' |
|
|
}) |
|
|
|
|
|
const sortedSources = computed(() => |
|
|
MUSIC_SOURCES.sort((a, b) => a.priority - b.priority) |
|
|
) |
|
|
|
|
|
const isMobile = computed(() => { |
|
|
return window.innerWidth <= 768 |
|
|
}) |
|
|
|
|
|
|
|
|
const handleSearch = () => { |
|
|
const keyword = searchKeyword.value.trim() |
|
|
if (keyword) { |
|
|
emit('search', keyword) |
|
|
} |
|
|
} |
|
|
|
|
|
const clearSearch = () => { |
|
|
searchKeyword.value = '' |
|
|
} |
|
|
|
|
|
const clearKeyword = () => { |
|
|
searchKeyword.value = '' |
|
|
} |
|
|
|
|
|
const showSearchHistory = () => { |
|
|
|
|
|
router.push('/search-history') |
|
|
} |
|
|
|
|
|
const handleSourceSelect = (sourceCode) => { |
|
|
searchStore.setSource(sourceCode) |
|
|
showSourceSelector.value = false |
|
|
} |
|
|
|
|
|
const handleSourceChange = (event) => { |
|
|
searchStore.setSource(event.target.value) |
|
|
} |
|
|
|
|
|
|
|
|
watch(() => searchStore.keyword, (newKeyword) => { |
|
|
if (newKeyword && newKeyword !== searchKeyword.value) { |
|
|
searchKeyword.value = newKeyword |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
defineExpose({ |
|
|
clearKeyword |
|
|
}) |
|
|
</script> |
|
|
|
|
|
<style scoped> |
|
|
.search-box { |
|
|
position: relative; |
|
|
background: transparent; |
|
|
} |
|
|
|
|
|
.search-wrapper { |
|
|
display: flex; |
|
|
gap: 8px; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.search-input-container { |
|
|
flex: 1; |
|
|
position: relative; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
background: var(--bg-card); |
|
|
border: 1px solid var(--border-light); |
|
|
border-radius: 25px; |
|
|
padding: 0 16px; |
|
|
transition: var(--transition-fast); |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|
|
min-width: 0; |
|
|
} |
|
|
|
|
|
.search-input-container:focus-within { |
|
|
border-color: var(--accent-red); |
|
|
box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.3), 0 4px 12px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.search-icon { |
|
|
color: var(--text-secondary); |
|
|
margin-right: 8px; |
|
|
font-size: 16px; |
|
|
} |
|
|
|
|
|
.search-input { |
|
|
flex: 1; |
|
|
height: 44px; |
|
|
background: transparent; |
|
|
border: none; |
|
|
outline: none; |
|
|
color: var(--text-primary); |
|
|
font-size: 16px; |
|
|
min-width: 0; |
|
|
} |
|
|
|
|
|
.search-input::placeholder { |
|
|
color: var(--text-disabled); |
|
|
} |
|
|
|
|
|
.clear-btn { |
|
|
width: 24px; |
|
|
height: 24px; |
|
|
border-radius: 50%; |
|
|
background: rgba(255, 255, 255, 0.2); |
|
|
border: none; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-size: 12px; |
|
|
transition: var(--transition-fast); |
|
|
} |
|
|
|
|
|
.clear-btn:hover { |
|
|
background: rgba(255, 255, 255, 0.3); |
|
|
} |
|
|
|
|
|
.history-btn { |
|
|
width: 32px; |
|
|
height: 32px; |
|
|
border-radius: 50%; |
|
|
background: transparent; |
|
|
border: none; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-size: 14px; |
|
|
transition: var(--transition-fast); |
|
|
margin-left: 4px; |
|
|
} |
|
|
|
|
|
.history-btn:hover { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
color: var(--accent-red); |
|
|
} |
|
|
|
|
|
.source-btn { |
|
|
height: 44px; |
|
|
padding: 0 16px; |
|
|
background: var(--bg-card); |
|
|
border: 1px solid var(--border-light); |
|
|
border-radius: 22px; |
|
|
color: var(--text-primary); |
|
|
font-size: 14px; |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 6px; |
|
|
white-space: nowrap; |
|
|
transition: var(--transition-fast); |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|
|
flex-shrink: 0; |
|
|
max-width: 120px; |
|
|
overflow: hidden; |
|
|
text-overflow: ellipsis; |
|
|
} |
|
|
|
|
|
.pc-source-select { |
|
|
height: 44px; |
|
|
padding: 0 16px; |
|
|
background: var(--bg-card); |
|
|
border: 1px solid var(--border-light); |
|
|
border-radius: 22px; |
|
|
color: var(--text-primary); |
|
|
font-size: 14px; |
|
|
cursor: pointer; |
|
|
outline: none; |
|
|
transition: var(--transition-fast); |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
|
|
flex-shrink: 0; |
|
|
min-width: 120px; |
|
|
appearance: none; |
|
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); |
|
|
background-repeat: no-repeat; |
|
|
background-position: right 12px center; |
|
|
background-size: 16px; |
|
|
padding-right: 40px; |
|
|
} |
|
|
|
|
|
.pc-source-select:hover { |
|
|
border-color: var(--accent-red); |
|
|
color: var(--accent-red); |
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
|
transform: translateY(-1px); |
|
|
} |
|
|
|
|
|
.pc-source-select:focus { |
|
|
border-color: var(--accent-red); |
|
|
box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.1); |
|
|
} |
|
|
|
|
|
.source-btn:hover { |
|
|
border-color: var(--accent-red); |
|
|
color: var(--accent-red); |
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
|
transform: translateY(-1px); |
|
|
} |
|
|
|
|
|
.source-btn i { |
|
|
font-size: 12px; |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 375px) { |
|
|
.search-wrapper { |
|
|
gap: 6px; |
|
|
} |
|
|
|
|
|
.source-btn { |
|
|
padding: 0 12px; |
|
|
font-size: 13px; |
|
|
max-width: 100px; |
|
|
} |
|
|
|
|
|
.search-input-container { |
|
|
border: 1px solid var(--border-light); |
|
|
} |
|
|
|
|
|
.source-btn { |
|
|
border: 1px solid var(--border-light); |
|
|
} |
|
|
} |
|
|
|
|
|
@media (min-width: 768px) { |
|
|
|
|
|
.search-input-container { |
|
|
min-width: 200px; |
|
|
} |
|
|
|
|
|
.source-btn { |
|
|
max-width: 150px; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
|