分离搜索框业务逻辑,优化搜索框搜索体验

pull/96/head
lyswhut 2019-10-17 10:58:09 +08:00
parent 45fea5726b
commit 59627e048f
4 changed files with 148 additions and 61 deletions

View File

@ -3,6 +3,10 @@
- 新增网易云源歌曲搜索 - 新增网易云源歌曲搜索
- 新增网易云源歌单 - 新增网易云源歌单
#### 优化
- 优化搜索框搜索体验
#### 修复 #### 修复
- 修复QQ源歌单无法翻页Bug - 修复QQ源歌单无法翻页Bug

View File

@ -2,7 +2,9 @@
div(:class="$style.toolbar") div(:class="$style.toolbar")
//- img(v-if="icon") //- img(v-if="icon")
//- h1 {{title}} //- h1 {{title}}
material-search-input(:class="$style.input") material-search-input(:class="$style.input"
@event="handleEvent" :list="tipList" :visibleList="visibleList"
v-model="searchText")
div(:class="$style.control") div(:class="$style.control")
button(type="button" :class="$style.min" title="最小化" @click="min") button(type="button" :class="$style.min" title="最小化" @click="min")
//- button(type="button" :class="$style.max" @click="max") //- button(type="button" :class="$style.max" @click="max")
@ -11,22 +13,93 @@
<script> <script>
import { rendererSend } from 'common/icp' import { rendererSend } from 'common/icp'
// import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import music from '../../utils/music'
import { debounce } from '../../utils'
export default { export default {
// props: { data() {
// color: { return {
// type: String, searchText: '',
// default: color, visibleList: false,
// }, tipList: [],
// icon: { tipSearch: null,
// type: Boolean, }
// default: false, },
// },
// },
computed: { computed: {
// ...mapGetters(['theme']), ...mapGetters(['route', 'setting']),
...mapGetters('search', {
storeSearchText: 'searchText',
}),
source() {
return this.setting.search.tempSearchSource
},
isAutoClearInput() {
return this.setting.odc.isAutoClearSearchInput
},
},
watch: {
route(n) {
if (this.isAutoClearInput && n.name != 'search' && this.searchText) this.searchText = ''
},
'storeSearchText'(n) {
if (n !== this.searchText) this.searchText = n
},
searchText(n) {
this.handleTipSearch()
},
},
mounted() {
this.tipSearch = debounce(() => {
if (this.searchText === '') {
this.tipList.splice(0, this.tipList.length)
music[this.source].tempSearch.cancelTempSearch()
return
}
music[this.source].tempSearch.search(this.searchText).then(list => {
this.tipList = list
}).catch(() => {})
}, 50)
}, },
methods: { methods: {
handleEvent({ action, data }) {
switch (action) {
case 'focus':
if (!this.visibleList) this.visibleList = true
if (this.searchText) this.handleTipSearch()
break
case 'blur':
setTimeout(() => {
if (this.visibleList) this.visibleList = false
}, 50)
break
case 'submit':
this.handleSearch()
break
case 'listClick':
this.searchText = this.tipList[data]
this.$nextTick(() => {
this.handleSearch()
})
}
},
handleTipSearch() {
if (!this.visibleList) this.visibleList = true
this.tipSearch()
},
handleSearch() {
if (this.visibleList) this.visibleList = false
setTimeout(() => {
this.$router.push({
path: 'search',
query: {
text: this.searchText,
},
}).catch(_ => _)
}, 200)
},
min() { min() {
rendererSend('min') rendererSend('min')
}, },

View File

@ -1,31 +1,46 @@
<template lang="pug"> <template lang="pug">
div(:class="[$style.search, focus ? $style.active : '']") div(:class="[$style.search, focus ? $style.active : '']")
div(:class="$style.form") div(:class="$style.form")
input(placeholder="Search for something..." v-model.trim="text" input(:placeholder="placeholder" v-model.trim="text"
@focus="handleFocus" @blur="handleBlur" @input="handleInput" @focus="handleFocus" @blur="handleBlur" @input="$emit('input', text)"
@change="sendEvent('change')"
@keyup.enter="handleSearch") @keyup.enter="handleSearch")
button(type="button" @click="handleSearch") button(type="button" @click="handleSearch")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve') slot
use(xlink:href='#icon-search') svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve')
use(xlink:href='#icon-search')
//- transition(name="custom-classes-transition" //- transition(name="custom-classes-transition"
//- enter-active-class="animated flipInX" //- enter-active-class="animated flipInX"
//- leave-active-class="animated flipOutX") //- leave-active-class="animated flipOutX")
div(:class="$style.list" :style="listStyle") div(v-if="list" :class="$style.list" :style="listStyle")
ul(ref="dom_list") ul(ref="dom_list")
li(v-for="(item, index) in list" :key="item" @click="handleTemplistClick(index)") li(v-for="(item, index) in list" :key="item" @click="handleTemplistClick(index)")
span {{item}} span {{item}}
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import music from '../../utils/music'
export default { export default {
props: {
placeholder: {
type: String,
default: 'Search for something...',
},
list: {
type: Array,
},
visibleList: {
type: Boolean,
default: false,
},
value: {
type: String,
default: '',
},
},
data() { data() {
return { return {
isShow: false, isShow: false,
text: '', text: '',
list: [],
index: null, index: null,
focus: false, focus: false,
listStyle: { listStyle: {
@ -33,64 +48,35 @@ export default {
}, },
} }
}, },
computed: {
...mapGetters(['route', 'setting']),
...mapGetters('search', ['searchText']),
isAutoClearInput() {
return this.setting.odc.isAutoClearSearchInput
},
source() {
return this.setting.search.tempSearchSource
},
},
watch: { watch: {
list(n) { list(n) {
if (!this.visibleList) return
this.$nextTick(() => { this.$nextTick(() => {
this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px' this.listStyle.height = this.$refs.dom_list.scrollHeight + 'px'
}) })
}, },
'searchText'(n) { value(n) {
if (n !== this.text) this.text = n this.text = n
}, },
route(n) { visibleList(n) {
if (this.isAutoClearInput && n.name != 'search' && this.text) this.text = '' n ? this.showList() : this.hideList()
}, },
}, },
methods: { methods: {
handleTemplistClick(index) { handleTemplistClick(index) {
this.text = this.list[index] this.sendEvent('listClick', index)
this.handleSearch()
}, },
handleFocus() { handleFocus() {
this.focus = true this.focus = true
if (this.text) this.handleInput() this.sendEvent('focus')
this.showList()
}, },
handleBlur() { handleBlur() {
setTimeout(() => { this.focus = false
this.focus = false this.sendEvent('blur')
this.hideList()
}, 200)
}, },
handleSearch() { handleSearch() {
this.hideList() this.hideList()
this.$router.push({ this.sendEvent('submit')
path: 'search',
query: {
text: this.text,
},
}).catch(_ => _)
},
handleInput() {
if (this.text === '') {
this.list.splice(0, this.list.length)
music[this.source].tempSearch.cancelTempSearch()
return
}
if (!this.isShow) this.showList()
music[this.source].tempSearch.search(this.text).then(list => {
this.list = list
}).catch(() => {})
}, },
showList() { showList() {
this.isShow = true this.isShow = true
@ -100,6 +86,12 @@ export default {
this.isShow = false this.isShow = false
this.listStyle.height = 0 this.listStyle.height = 0
}, },
sendEvent(action, data) {
this.$emit('event', {
action,
data,
})
},
}, },
} }
</script> </script>

View File

@ -305,6 +305,24 @@ export const throttle = (fn, delay = 100) => {
} }
} }
/**
* 生成防抖函数
* @param {*} fn
* @param {*} delay
*/
export const debounce = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
const async_removeItem = (arr, num, callback) => window.requestAnimationFrame(() => { const async_removeItem = (arr, num, callback) => window.requestAnimationFrame(() => {
let len = arr.length let len = arr.length
if (len > num) { if (len > num) {