Vue 2.0 高级实战-开发移动端音乐WebApp 课程笔记(第十章 搜索页面开发)

Vue 2.0 高级实战-开发移动端音乐WebApp 课程笔记(第十章 搜索页面开发)

搜索页面search-box组件开发

  1. 添加基础组件search-box

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    <template>
    <div class="search-box">
    <i class="icon-search"></i>
    <input ref="query" v-model="query" class="box" :placeholder="placeholder"/>
    <i @click="clear" v-show="query" class="icon-dismiss"></i>
    </div>
    </template>

    <script>
    import {debounce} from 'common/js/util'

    export default {
    props: {
    placeholder: {
    type: String,
    default: '搜索歌曲、歌手'
    }
    },
    data() {
    return {
    query: ''
    }
    },
    methods: {
    clear() {
    this.query = ''
    },
    setQuery(query) {
    this.query = query
    },
    blur() {
    this.$refs.query.blur()
    }
    },
    created() {
    // 监听query
    this.$watch('query', debounce((newQuery) => {
    this.$emit('query', newQuery)
    }, 200))
    }
    }
    </script>

    <style scoped lang="stylus" rel="stylesheet/stylus">
    @import "~common/stylus/variable"
    .search-box
    display: flex
    align-items: center
    box-sizing: border-box
    width: 100%
    padding: 0 6px
    height: 40px
    background: $color-highlight-background
    border-radius: 6px
    .icon-search
    font-size: 24px
    color: $color-background
    .box
    flex: 1
    margin: 0 5px
    line-height: 18px
    background: $color-highlight-background
    color: $color-text
    font-size: $font-size-medium
    &::placeholder
    color: $color-text-d
    .icon-dismiss
    font-size: 16px
    color: $color-background
    </style>
  2. 添加业务组件search

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    <template>
    <div class="search">
    <div class="search-box-wrapper">
    <search-box ref="searchBox"></search-box>
    </div>
    </div>
    </template>

    <script>
    import SearchBox from 'base/search-box/search-box'

    export default {
    components: {
    SearchBox
    }
    }
    </script>

    <style lang="stylus" rel="stylesheet/stylus">
    @import "~common/stylus/variable"
    @import "~common/stylus/mixin"
    .search
    .search-box-wrapper
    margin: 20px
    .shortcut-wrapper
    position: fixed
    top: 178px
    bottom: 0
    width: 100%
    .shortcut
    height: 100%
    overflow: hidden
    .hot-key
    margin: 0 20px 20px 20px
    .title
    margin-bottom: 20px
    font-size: $font-size-medium
    color: $color-text-l
    .item
    display: inline-block
    padding: 5px 10px
    margin: 0 20px 10px 0
    border-radius: 6px
    background: $color-highlight-background
    font-size: $font-size-medium
    color: $color-text-d
    .search-history
    position: relative
    margin: 0 20px
    .title
    display: flex
    align-items: center
    height: 40px
    font-size: $font-size-medium
    color: $color-text-l
    .text
    flex: 1
    .clear
    extend-click()
    .icon-clear
    font-size: $font-size-medium
    color: $color-text-d
    .search-result
    position: fixed
    width: 100%
    top: 178px
    bottom: 0
    </style>

搜索页面热门搜索数据抓取和应用

1
2
3
4
5
6
7
8
9
10
11
export function getHotKey() {
const url = 'https://c.y.qq.com/splcloud/fcgi-bin/gethotkey.fcg'

const data = Object.assign({}, commonParams, {
uin: 0,
needNewCode: 1,
platform: 'h5'
})

return jsonp(url, data, options)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<div class="search">
<div class="search-box-wrapper">
<search-box ref="searchBox"></search-box>
</div>
<div ref="shortcutWrapper" class="shortcut-wrapper" v-show="!query">
<div class="shortcut">
<div>
<div class="hot-key">
<h1 class="title">热门搜索</h1>
<ul>
<li @click="addQuery(item.k)" class="item" v-for="item in hotKey">
<span>{{item.k}}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import SearchBox from 'base/search-box/search-box'
import {getHotKey} from 'api/search'
import {ERR_OK} from 'api/config'

export default {
data() {
return {
hotKey: []
}
},
created() {
this._getHotKey()
},
methods: {
addQuery(query) {
this.$refs.searchBox.setQuery(query)
},
_getHotKey() {
getHotKey().then((res) => {
if (res.code === ERR_OK) {
this.hotKey = res.data.hotkey.slice(0, 10)
}
})
}
},
watch: {
query(newQuery) {
if (!newQuery) {
setTimeout(() => {
this.$refs.shortcut.refresh()
}, 20)
}
}
},
components: {
SearchBox
}
}
</script>

搜索页面suggest组件开发

  1. 添加接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    export function search(query, page, zhida, perpage) {
    const url = 'https://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp'

    const data = Object.assign({}, commonParams, {
    w: query,
    p: page,
    perpage,
    n: perpage,
    catZhida: zhida ? 1 : 0,
    zhidaqu: 1,
    t: 0,
    flag: 1,
    ie: 'utf-8',
    sem: 1,
    aggr: 0,
    remoteplace: 'txt.mqq.all',
    uin: 0,
    needNewCode: 1,
    platform: 'h5'
    })

    return jsonp(url, data, options)
    }
  2. 实现上拉刷新, 扩展scroll组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // props
    pullup: {
    type: Boolean,
    default: false
    }
    // methods
    // 当滚动结束时离底部还有50px时, 派发事件scrollToEnd
    if (this.pullup) {
    this.scroll.on('scrollEnd', () => {
    if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
    this.$emit('scrollToEnd')
    }
    })
    }
  3. suggest中添加方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    searchMore() {
    if (!this.hasMore) {
    return
    }
    this.page++
    search(this.query, this.page, this.showSinger, perpage).then((res) => {
    if (res.code === ERR_OK) {
    this.result = this.result.concat(this._genResult(res.data))
    this._checkMore(res.data)
    }
    })
    },
    _checkMore(data) {
    const song = data.song
    if (!song.list.length || (song.curnum + song.curpage * perpage) >= song.totalnum) {
    this.hasMore = false
    }
    }
  4. 添加actions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    export const insertSong = function ({commit, state}, song) {
    let playlist = state.playlist.slice()
    let sequenceList = state.sequenceList.slice()
    let currentIndex = state.currentIndex
    // 记录当前歌曲
    let currentSong = playlist[currentIndex]
    // 查找当前列表中是否有待插入的歌曲并返回其索引
    let fpIndex = findIndex(playlist, song)
    // 因为是插入歌曲,所以索引+1
    currentIndex++
    // 插入这首歌到当前索引位置
    playlist.splice(currentIndex, 0, song)
    // 如果已经包含了这首歌
    if (fpIndex > -1) {
    // 如果当前插入的序号大于列表中的序号
    if (currentIndex > fpIndex) {
    playlist.splice(fpIndex, 1)
    currentIndex--
    } else {
    playlist.splice(fpIndex + 1, 1)
    }
    }

    // 应该插入的位置
    let currentSIndex = findIndex(sequenceList, currentSong) + 1

    let fsIndex = findIndex(sequenceList, song)

    sequenceList.splice(currentSIndex, 0, song)

    if (fsIndex > -1) {
    if (currentSIndex > fsIndex) {
    sequenceList.splice(fsIndex, 1)
    } else {
    sequenceList.splice(fsIndex + 1, 1)
    }
    }

    commit(types.SET_PLAYLIST, playlist)
    commit(types.SET_SEQUENCE_LIST, sequenceList)
    commit(types.SET_CURRENT_INDEX, currentIndex)
    commit(types.SET_FULL_SCREEN, true)
    commit(types.SET_PLAYING_STATE, true)
    }
  5. 添加通用组件no-result

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <template>
    <div class="no-result">
    <div class="no-result-icon"></div>
    <p class="no-result-text">{{title}}</p>
    </div>
    </template>

    <script type="text/ecmascript-6">
    export default {
    props: {
    title: {
    type: String,
    default: ''
    }
    }
    }
    </script>

    <style scoped lang="stylus" rel="stylesheet/stylus">
    @import "~common/stylus/variable"
    @import "~common/stylus/mixin"
    .no-result
    text-align: center
    .no-result-icon
    width: 86px
    height: 90px
    margin: 0 auto
    bg-image('no-result')
    background-size: 86px 90px
    .no-result-text
    margin-top: 30px
    font-size: $font-size-medium
    color: $color-text-d
    </style>
  6. 在common/js/util.js中添加节流函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    export function debounce(func, delay) {
    let timer

    return function (...args) {
    if (timer) {
    clearTimeout(timer)
    }
    timer = setTimeout(() => {
    func.apply(this, args)
    }, delay)
    }
    }

搜索页面搜索结果保存功能实现

  1. suggest组件selectItem方法派发事件

    1
    this.$emit('select', item)
  2. 添加commom/js/cache.js 缓存操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function insertArray(arr, val, compare, maxLen) {
    const index = arr.findIndex(compare)
    if (index === 0) {
    return
    }
    // 存在元素删除后插入, 没有直接插入
    if (index > 0) {
    arr.splice(index, 1)
    }
    arr.unshift(val)
    if (maxLen && arr.length > maxLen) {
    arr.pop()
    }
    }
    export function saveSearch(query) {
    let searches = storage.get(SEARCH_KEY, [])
    insertArray(searches, query, (item) => {
    return item === query
    }, SEARCH_MAX_LEN)
    storage.set(SEARCH_KEY, searches)
    return searches
    }
  3. 添加actions

    1
    2
    3
    export const saveSearchHistory = function ({commit}, query) {
    commit(types.SET_SEARCH_HISTORY, saveSearch(query))
    }

搜索页面search-list 组件功能实现

  1. 添加组件search-list

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <template>
    <div class="search-list" v-show="searches.length">
    <ul>
    <li class="search-item" @click="selectItem(item)" v-for="item in searches">
    <span class="text">{{item}}</span>
    <span class="icon" @click.stop="deleteOne(item)">
    <i class="icon-delete"></i>
    </span>
    </li>
    </ul>
    </div>
    </template>

    <script>
    export default {
    props: {
    searches: {
    type: Array,
    default: []
    }
    },
    methods: {
    selectItem(item) {
    this.$emit('select', item)
    },
    deleteOne(item) {
    this.$emit('delete', item)
    }
    }
    }
    </script>

    <style scoped lang="stylus" rel="stylesheet/stylus">
    @import "~common/stylus/variable"
    .search-list
    .search-item
    display: flex
    align-items: center
    height: 40px
    .text
    flex: 1
    color: $color-text-l
    .icon
    extend-click()
    .icon-delete
    font-size: $font-size-small
    color: $color-text-d
    </style>
  2. cache添加方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function deleteFromArray(arr, compare) {
    const index = arr.findIndex(compare)
    if (index > -1) {
    arr.splice(index, 1)
    }
    }
    export function deleteSearch(query) {
    let searches = storage.get(SEARCH_KEY, [])
    deleteFromArray(searches, (item) => {
    return item === query
    })
    storage.set(SEARCH_KEY, searches)
    return searches
    }
  3. 添加action

    1
    2
    3
    export const deleteSearchHistory = function ({commit}, query) {
    commit(types.SET_SEARCH_HISTORY, deleteSearch(query))
    }
  4. 添加cache

    1
    2
    3
    4
    export function clearSearch() {
    storage.remove(SEARCH_KEY)
    return []
    }
  5. 添加actions

    1
    2
    3
    export const clearSearchHistory = function ({commit}) {
    commit(types.SET_SEARCH_HISTORY, clearSearch())
    }

搜索页面confirm 组件功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<template>
<transition name="confirm-fade">
<div class="confirm" v-show="showFlag" @click.stop>
<div class="confirm-wrapper">
<div class="confirm-content">
<p class="text">{{text}}</p>
<div class="operate">
<div @click="cancel" class="operate-btn left">{{cancelBtnText}}</div>
<div @click="confirm" class="operate-btn">{{confirmBtnText}}</div>
</div>
</div>
</div>
</div>
</transition>
</template>

<script>
export default {
props: {
text: {
type: String,
default: ''
},
confirmBtnText: {
type: String,
default: '确定'
},
cancelBtnText: {
type: String,
default: '取消'
}
},
data() {
return {
showFlag: false
}
},
methods: {
show() {
this.showFlag = true
},
hide() {
this.showFlag = false
},
cancel() {
this.hide()
this.$emit('cancel')
},
confirm() {
this.hide()
this.$emit('confirm')
}
}
}
</script>

<style scoped lang="stylus" rel="stylesheet/stylus">
@import "~common/stylus/variable"
.confirm
position: fixed
left: 0
right: 0
top: 0
bottom: 0
z-index: 998
background-color: $color-background-d
&.confirm-fade-enter-active
animation: confirm-fadein 0.3s
.confirm-content
animation: confirm-zoom 0.3s
.confirm-wrapper
position: absolute
top: 50%
left: 50%
transform: translate(-50%, -50%)
z-index: 999
.confirm-content
width: 270px
border-radius: 13px
background: $color-highlight-background
.text
padding: 19px 15px
line-height: 22px
text-align: center
font-size: $font-size-large
color: $color-text-l
.operate
display: flex
align-items: center
text-align: center
font-size: $font-size-large
.operate-btn
flex: 1
line-height: 22px
padding: 10px 0
border-top: 1px solid $color-background-d
color: $color-text-d
&.left
border-right: 1px solid $color-background-d
@keyframes confirm-fadein
0%
opacity: 0
100%
opacity: 1
@keyframes confirm-zoom
0%
transform: scale(0)
50%
transform: scale(1.1)
100%
transform: scale(1)
</style>

搜索页面剩余功能实现

给滚动组件传值, 一个复合属性

1
2
3
4
5
computed: {
shortcut() {
return this.hotKey.concat(this.searchHistory)
},
}