Skip to content

音乐页面

第一章:搜索栏

一、界面布局

音乐 tabBar 页整体背景颜色为 #fafafa。所以改变 van-search 组件背景颜色为 #fafafa。

xml
<van-search
  shape="round"
  background="#fafafa"
  placeholder="请输入搜索关键词"
  bind:tap="onSearchClick" />

但想让搜索框内的背景颜色为白色,怎么办?解决办法①: 使用调试工具找到类名,覆盖样式;解决办法②: 全局变量;解决办法③: 外部样式类。

css
page {
  --search-background-color: #fff; /* 这里采用全局变量方式覆盖默认样式 */
  --search-padding: 10px 0;

  box-sizing: border-box;
  padding: 0 24rpx;
  background-color: #fafafa;
}

第二章:轮播图

一、获取数据

1. 封装 API

\services\modules\music.js

js
export function getMusicBanner(type = 0) {
  return ddfRequest.get("/banner", {
    type
  })
}

2. 调用接口

\pages\main-music\main-music.js

js
onLoad(options) {
  this.fetchMusicBanner()
}

async fetchMusicBanner() {
  const res = await getMusicBanner()
  const arr = res.banners.filter(item => item.typeTitle !== "广告")
  this.setData({ banners: arr })
},

二、展示数据

1. 基本展示

\pages\main-music\main-music.wxml

xml
<!-- 轮播图 -->
<swiper
  class="banner"
  circular
  indicator-dots
  autoplay
  wx:if="{{banners.length}}"
>
  <block wx:for="{{banners}}" wx:key="targetId">
    <swiper-item class="item">
      <image
        class="banner-image"
        src="{{item.imageUrl}}"
        mode="widthFix"
      />
    </swiper-item>
  </block>
</swiper>

mode 属性设置为 widthFix 表示宽度不变,高度自动变化,保持原图宽高比不变。

css
.banner {
  border-radius: 12rpx;
  overflow: hidden;
}

.banner-image {
  width: 100%;
}

2. 轮播图高度怎么计算?

image 组件使用 mode 属性值为 widthFix,发现图片高度小于轮播图默认高度。

使用微信小程序提供的 API 来获取组件(图片)大小、位置等信息。

1)封装工具函数 \utils\query-select.js

js
export default function querySelect(selector) {
  return new Promise(resolve => {
    const query = wx.createSelectorQuery()
    query.select(selector).boundingClientRect()
    query.exec((res) => {
      resolve(res)
    })
  })
}

2)在 image 组件添加 bindload="onBannerImageLoad"。bindload 属性是当图片载入完毕时触发。

js
// \pages\main-music\main-music.js
onBannerImageLoad(event) {
  querySelectThrottle(".banner-image").then(res => {
    this.setData({ bannerHeight: res[0].height })
  })
},

3)高度存到 data 中了,这样就可以设置 swiper 组件的高度了。给 swiper 组件添加 style="height: px;"

轮播图图片有多个,这样 image 组件的 onBannerImageLoad 回调就会执行多次。这里要使用节流函数。

第三章:推荐歌曲

一、数据获取

1. 封装 API

接口文档:获取歌单详情

推荐歌曲其实是网易云音乐的热歌歌单。

\services\modules\music.js

js
export function getPlaylistDetail(id) {
  return ddfRequest.get("/playlist/detail", {
    id
  })
}

2. 数据共享

1)安装

bash
npm install hy-event-store

别忘记构建 npm。

2)使用

\store\recommendStore.js

js
import { HYEventStore } from "hy-event-store"

const recommendStore = new HYEventStore({
  state: {
    // 要共享的数据
    recommendSongInfo: {}
  },
  actions: {
    // 更改 state 中的数据,也可以发起异步任务
    fetchRecommendSongsAction(ctx) {
      const res = await wx.request( /* ...... */ )
      ctx.recommendSongInfo = res.playlist
    }
  }
})

export default recommendStore

\pages\main-music\main-music.js

js
import recommendStore from '../store/recommendStore'

// 监听所需数据的改变,执行回调
recommendStore.onState("recommendSongInfo", this.handleRecommendSongs)
// 派发事件,发起网络请求
recommendStore.dispatch("fetchRecommendSongsAction")

// ====================== 从Store中获取数据 ======================
handleRecommendSongs(value) {
  if (!value.tracks) return
  this.setData({ recommendSongs: value.tracks.slice(0, 6) })
},

二、界面展示

第四章:热门 & 华语歌单

一、数据获取

1. 封装 API

接口文档:获取不同分类下的歌单

热门 & 华语歌单取自网易云音乐的热门全部歌单。

\services\modules\music.js

js
export function getSongMenuList(cat = "全部", limit = 6, offset = 0) {
  return ddfRequest.get("/top/playlist", {
    cat,
    limit,
    offset
  })
}

2. 获取数据

\pages\main-music\main-music.js

js
import { getSongMenuList } from '../../services/index'

onLoad(options) {
  // ......
  this.fetchSongMenuList()
}

async fetchSongMenuList() {
  getSongMenuList().then(res => {
    this.setData({ hotMenuList: res.playlists })
  })
  getSongMenuList("华语").then(res => {
    this.setData({ recMenuList: res.playlists })
  })
},

二、界面展示

热门 & 华语歌单观察页面样式发现一模一样,所以封装为组件。

第五章:榜单

一、数据获取

接口文档:获取歌单详情

榜单其实是网易云音乐的不同分类歌单。

1. 封装 API

\services\modules\music.js

依旧是之前的歌单详情信息获取 API。

js
export function getPlaylistDetail(id) {
  return ddfRequest.get("/playlist/detail", {
    id
  })
}

2.存到 store

在单击榜单每个分类的时候,会跳转到歌曲详情页,也展示的是 /playlist/detail 接口信息,只不过是展示多少的问题。音乐页和歌曲详情页都要使用同样的数据,所以数据需要共享。

\store\rankingStore.js

js
import { HYEventStore } from "hy-event-store"
import { getPlaylistDetail } from "../services/index"

export const rankingsMap = {
  newRanking: 3779629,
  originRanking: 2884035,
  upRanking: 19723756
}

const rankingStore = new HYEventStore({
  state: {
    newRanking: {},
    originRanking: {},
    upRanking: {}
  },
  actions: {
    fetchRankingDataAction(ctx) {
      for (const key in rankingsMap) {
        const id = rankingsMap[key]
        getPlaylistDetail(id).then(res => {
          ctx[key] = res.playlist
        })
      }
    }
  }
})

export default rankingStore

3. 页面拿到数据

js
onLoad(options) {
  rankingStore.onState("newRanking", this.handleNewRanking)
  rankingStore.onState("originRanking", this.handleOriginRanking)
  rankingStore.onState("upRanking", this.handleUpRanking)
  rankingStore.dispatch("fetchRankingDataAction")
}

handleNewRanking(value) {
  if (!value.name) return
  this.setData({ isRankingData: true })
  const newRankingInfos = { ...this.data.rankingInfos, newRanking: value }
  this.setData({ rankingInfos: newRankingInfos })
},
handleOriginRanking(value) {
  if (!value.name) return
  this.setData({ isRankingData: true })
  const newRankingInfos = { ...this.data.rankingInfos, originRanking: value }
  this.setData({ rankingInfos: newRankingInfos })
},
handleUpRanking(value) {
  if (!value.name) return
  this.setData({ isRankingData: true })
  const newRankingInfos = { ...this.data.rankingInfos, upRanking: value }
  this.setData({ rankingInfos: newRankingInfos })
},

onUnload() {
  rankingStore.offState("newRanking", this.handleNewRanking)
  rankingStore.offState("originRanking", this.handleOriginRanking)
  rankingStore.offState("upRanking", this.handleUpRanking)
}

4. 优化代码

上面 handleNewRanking、handleOriginRanking、handleUpRanking 以及 onState 代码重复,优化代码。

js
// ============================= 监听所需数据改变 =============================
rankingStore.onState("newRanking", this.getRankingHanlder("newRanking"))
rankingStore.onState("originRanking", this.getRankingHanlder("originRanking"))
rankingStore.onState("upRanking", this.getRankingHanlder("upRanking"))

// 上面还是有重复代码,进一步优化
for (const key in rankingsMap) {
 rankingStore.onState(key, this.getRankingHanlder(key))
}


// ============================= 监听数据改变的 state =============================
getRankingHanlder(ranking) {
  return value => {
    const newRankingInfos = { ...this.data.rankingInfos, [ranking]: value }
    this.setData({ rankingInfos: newRankingInfos })
  }
},

二、界面展示

xml
<!-- 榜单 -->
<view class="ranking" wx:if="{{isRankingData}}">
  <area-header title="榜单" hasMore="{{false}}"/>
  <view class="ranking-list">
    <block wx:for="{{rankingInfos}}" wx:key="id">
      <ranking-item itemData="{{item}}" key="{{index}}"/>
    </block>
  </view>
</view>

Released under the MIT License.