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>
user-center.vue

它有一个一级路由页面m-header,点击首页header右上角的头像就可以进入用户个人中心

在m-header.vue添加路由入口

    <router-link tag="div" class="mine" to="/user">
      <i class="iconfont music-icon">&#xe66d;</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;
    },
  ...mapActions([
      'saveFavoriteList',
      'deleteFavoriteList',
    ]),
 

给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 '你还没有听过歌曲';
      }
    },

 

posted @ 2021-07-12 23:46  小风车吱呀转  阅读(264)  评论(0编辑  收藏  举报