VUE移动端音乐APP学习【二十六】:用户个人中心开发
用户个人中心分为两个部分:用户个人收藏的歌曲列表和用户播放历史列表。
先创建user-center.vue,基本代码如下
<template> <transition name="slide"> <div class="user-center"> <div class="back"> <i class="iconfont icon-back"></i> </div> <div class="switches-wrapper"> </div> <div class="play-btn" ref="playBtn"> <i class="iconfont icon-play"></i> <span class="text">随机播放全部</span> </div> <div class="list-wrapper" ref="listWrapper"></div> </div> </transition> </template> <script> export default { }; </script> <style lang="scss"> .user-center { position: fixed; top: 0; bottom: 0; z-index: 100; width: 100%; background: $color-background; &.slide-enter-active, &.slide-leave-active { transition: all 0.3s; } &.slide-enter, &.slide-leave-to { transform: translate3d(100%, 0, 0); } .back { position: absolute; top: 0; left: 6px; z-index: 50; .icon-back { display: block; padding: 10px; font-size: $color-size-large-x; color: $color-theme; } } .switches-wrapper { margin: 10px 0 30px 0; } .play-btn { box-sizing: border-box; width: 135px; padding: 7px 0; margin: 0 auto; text-align: center; border: 1px solid $color-text-l; color: $color-text-l; border-radius: 100px; font-size: 0; .icon-play { display: inline-block; vertical-align: middle; margin-right: 6px; font-size: $font-size-medium-x; } .text { display: inline-block; vertical-align: middle; font-size: $font-size-small; } } .list-wrapper { position: absolute; top: 110px; bottom: 0; width: 100%; .list-scroll { height: 100%; overflow: hidden; .list-inner { padding: 20px 30px; } } } .no-result-wrapper { position: absolute; width: 100%; top: 50%; transform: translateY(-50%); } } </style>
它有一个一级路由页面m-header,点击首页header右上角的头像就可以进入用户个人中心
在m-header.vue添加路由入口
<router-link tag="div" class="mine" to="/user"> <i class="iconfont music-icon"></i> </router-link>
去router下的index.js定义这个路由
{ path: '/user', name: 'User', component: UserCenter, },
在个人中心页添加switches组件,并且往switches传入两个props:switchItem和currentIndex和定义siwtchItem方法
<div class="switches-wrapper"> <switches @switch="switchItem" :switches="switches" :currentIndex="currentIndex"></switches> </div> import Switches from '../../base/switches/switches.vue'; export default { data() { return { currentIndex: 0, switches: [ { name: '我喜欢的' }, { name: '最近听的' }, ], }; }, methods: { switchItem(index) { this.currentIndex = index; }, }, components: { Switches, }, }; </script>
这样switches就实现了
实现收藏列表:收藏列表的展示是在个人中心页的“我喜欢”,设置是通过播放器内核以及播放列表的操作,本质就是多个组件共享的数据,这种共享数据最终也是用vuex存储。
在vuex里存一个state
// 收藏列表 favoriteList: loadFavorite(),
定义mutation-types、mutations以及getters
//mutation-types.js export const SET_FAVORITE_LIST = 'SET_FAVORITE_LIST'; //mutations.js [types.SET_FAVORITE_LIST](state, list) { state.favoriteList = list; }, //getters.js export const favoriteList = (state) => state.favoriteList;
favoriteList与之前的searchHistory以及playList数据类似,也是同样存储在本地缓存中。在cache.js定义favoriteList的存储、删除、加载方法
export function saveFavorite(song) { let songs = storage.get(FAVORITE_KEY, []); insertArray(songs, song, (item) => { return song.id === item.id; }, FAVORITE_MAX_LENGTH); storage.set(FAVORITE_KEY, songs); return songs; } export function deleteFavoriteList(song) { let songs = storage.get(FAVORITE_KEY, []); deleteFromArray(songs, (item) => { return song.id === item.id; }); storage.set(FAVORITE_KEY, songs); return songs; } export function loadFavorite() { return storage.get(FAVORITE_KEY, []); }
定义几个favoriteList相关的actions,commit时调用上面的方法完成相关操作。
export const saveFavoriteList = function ({ commit }, song) { commit(types.SET_FAVORITE_LIST, saveFavorite(song)); }; export const deleteFavoriteList = function ({ commit }, song) { commit(types.SET_FAVORITE_LIST, deleteFavorite(song)); };
因为播放器内核以及播放列表都有收藏歌曲的按钮,这两处的样式和逻辑都是可以复用的。所以接下来都是在mixin里定义这些操作和逻辑。
但是首先需要先修改播放器内核和播放列表的收藏图标写死的样式:我们需要通过函数以及点击事件改变收藏图标的样式,这两个处理方法同样也是在mixin里定义
//player.vue <div class="icon i-right"> <i class="iconfont icon" @click="toggleFavorite(currentSong)" :class="getFavoriteIcon(currentSong)"></i> </div>
在playerMixin里定义方法
...mapGetters([ …… 'favoriteList', ]), toggleFavorite(song) { if (this.isFavorite(song)) { this.deleteFavoriteList(song); } else { this.saveFavoriteList(song); } }, getFavoriteIcon(song) { if (this.isFavorite(song)) { return 'icon-favorite'; } return 'icon-not-favorite'; }, // 判断当前点击的歌曲是不是收藏列表里的 isFavorite(song) { const index = this.favoriteList.findIndex((item) => { return item.id === song.id; }); return index > -1; },
给playlist也加上这个功能
<span @click.stop="toggleFavorite(item)" class="like"> <i class="iconfont" :class="getFavoriteIcon(item)"></i> </span>
回到用户中心,通过mapGetters获取收藏列表数据和搜索历史数据之后就可以将它渲染在页面上了,然后给收藏列表添加点击事件:点击之后就可以播放该歌曲
<div class="list-wrapper" ref="listWrapper"> <scroll ref="favoriteList" class="list-scroll" v-if="currentIndex === 0" :data="favoriteList"> <div class="list-inner"> <song-list :songs="favoriteList" @select="selectSong"></song-list> </div> </scroll> <scroll ref="playList" class="list-scroll" v-if="currentIndex === 1" :data="playHistory"> <div class="list-inner"> <song-list :songs="playHistory" @select="selectSong"></song-list> </div> </scroll> </div>
selectSong(song) { // 这里的song也是从缓存拿出来的,需要实例化 this.insertSong(new Song(song)); }, ...mapActions([ 'insertSong', ]),
实现剩余功能
- 返回按钮的点击,给back添加点击事件
<div class="back" @click="back"> <i class="iconfont icon-back"></i> </div> back() { // 回到上一级 this.$router.back(); },
- 给随机播放按钮添加点击事件
<div class="play-btn" ref="playBtn" @click="random"> <i class="iconfont icon-play"></i> <span class="text">随机播放全部</span> </div> random() { let list = this.currentIndex === 0 ? this.favoriteList : this.playHistory;
//如果list 为空时什么都不做
if(list.length === 0){
return;
}
// list也不是Song的实例,需要去封装实例化 list = list.map((song) => { return new Song(song); }); this.randomPlay({ list, }); }, ...mapActions([ 'insertSong', 'randomPlay', ]),
- 实现滚动列表和底部迷你播放器自适应
import { playlistMixin } from '../../common/js/mixin'; mixins: [playlistMixin], handlePlaylist(playlist) { const bottom = playlist.length > 0 ? '60px' : ''; this.$refs.listWrapper.style.bottom = bottom; // 不能直接调用refresh,因为scroll用的是v-if,它是有可能不存在的 this.$refs.favoriteList && this.$refs.favoriteList.refresh(); this.$refs.playList && this.$refs.playList.refresh(); },
- 当无数据时, 使用no-result组件展示给用户提示。它的显示与否以及文案的设置都由计算属性来控制。
<div class="no-result-wrapper" v-show="noResult"> <no-result :title="noResultDesc"></no-result> </div> noResult() { // 判断在哪个tab下 if (this.currentIndex === 0) { return !this.favoriteList.length; } else { return !this.playHistory.length; } }, noResultDesc() { if (this.currentIndex === 0) { return '暂无收藏歌曲'; } else { return '你还没有听过歌曲'; } },