Скриншот проекта (Gif)
предисловие
Я изучил апплет WeChat некоторое время назад, чтобы закрепить полученные знания и улучшить реальный боевой опыт, я решил создать небольшую программу самостоятельно. Поскольку Tingge использует NetEase Cloud Music, я сделал небольшую программу, имитирующую NetEase Cloud Music, по прихоти! В разработке я столкнулся со многими ямами, с которыми не сталкивался в исследовании, и я очень благодарен преподавателям и ученикам, которые помогли мне, когда я не смог исправить ошибку! С целью обучения и обмена я написал следующий текст, надеясь датьВсе в первой школеПринесите немного помощи, большой парень просто слегка распыляет.
Об использовании проекта
В конце этой статьи я разместил адрес исходного кода проекта, если он вам нужен, вы можете клонировать его на GitHub. После завершения вы можете напрямую открыть его в инструменте разработчика WeChat и использовать его.
Подготовка перед разработкой
- Редактор кода VScode.
- Инструменты разработчика WeChat
- ios NetEase Cloud Music (версия V5.9.1)
- Апплет Kugou Music (предлагает некоторые идеи)
- API облачной музыки NetEase
- (Библиотека векторных иконок Alibaba), чтобы предоставить некоторые значки
раздел tabBar
пользовательская панель вкладок
Как правило, вкладка TabBar, предоставленная нам апплетом WeChat, находится в разработке и может удовлетворить потребности. Однако некоторые особые потребности должны быть удовлетворены с помощью пользовательской панели вкладок.
Например, tabBar полупрозрачный. Итак, как я могу настроить tabBar?
1. Сначала объявите в «tabBar» в app.json"tabBar": { "custom": true }
2. Затем создайте новый в корневом каталоге проекта.custom-tab-bar
папка. содержитindex.wxml index.js index.json index.wxss
четыре файла. Более подробная информацияДокументация по мини-программе WeChat
<!-- index.html -->
<!-- 自定义tabbar页面 -->
<cover-view class="tab-bar">
<cover-view class="tab-bar-border"></cover-view><!--tabBal边框样式 -->
<!-- 乐库tabbar -->
<cover-view class='tab-bar-item' >
<cover-image src='../images/music.png' hidden='{{isShow_index}}' bindtap='switchTab_index'></cover-image>
<cover-image src='../images/selected-music.png' hidden='{{!isShow_index}}' bindtap='switchTab_index'></cover-image>
<cover-view style="color:{{isShow_index ? selectedColor : color}}">乐库</cover-view>
</cover-view>
<!-- 播放tabbar -->
<cover-view class='tab-bar-item' bindtap='switchTab_playing'>
<cover-image src='../images/selected-playing.png' hidden='{{isShow_playing}}'></cover-image>
<cover-image src='../images/playing.png' hidden='{{!isShow_playing}}'></cover-image>
<cover-view></cover-view>
</cover-view>
<!-- 我的tabbar -->
<cover-view class='tab-bar-item' bindtap='switchTab_me'>
<cover-image src='../images/me.png' hidden='{{isShow_me}}'></cover-image>
<cover-image src='../images/selected-me.png' hidden='{{!isShow_me}}'></cover-image>
<cover-view style="color:{{isShow_me ? selectedColor : color}}">我的</cover-view>
</cover-view>
</cover-view>
// index.js
Component({
data: {
isShow_index:true,
isShow_playing:false,
isShow_me:false,
selected: 0, //首页
color: "#8D8D8D",
selectedColor: "#C62F2F",
list: [{
pagePath: "/pages/index/index",
iconPath: "/images/music.png",
selectedIconPath: "/images/selected-music.png",
text: "乐库"
}, {
pagePath: "/pages/love/love",
iconPath: "/images/selected-playing.png",
selectedIconPath: "/images/playing.png",
text: ""
},
{
pagePath: "/pages/me/me",
iconPath: "/images/me.png",
selectedIconPath: "/images/selected-me.png",
text: "我的"
}]
},
methods: {
switchTab_index:function(){
wx.switchTab({
url:'/pages/index/index'
})
this.setData({
isShow_index: true,
isShow_me: false,
isShow_playing: false
})
},
switchTab_playing: function () {
wx.switchTab({
url: '/pages/love/love'
})
this.setData({
isShow_playing: true,
isShow_index: false,
isShow_me: false
})
},
switchTab_me: function () {
wx.switchTab({
url: '/pages/me/me'
})
this.setData({
isShow_me:true,
isShow_playing: false,
isShow_index: false
})
}
}
})
tabBar полупрозрачный
/* custom-tab-bar/index.wxss */
.tab-bar {
height:7%;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 48px;
background:#FAFBFD;
opacity: 0.93;
display: flex;
padding-bottom: env(safe-area-inset-bottom);
}
упаковка API
Как правило, наши https-запросы выполняются черезwx.request
для запроса, но этот метод может запрашивать данные только один раз, если домашняя страница используетwx.request
Для запроса код будет выглядеть многословным и беспорядочным. Мало того, что легко запутаться самому, так еще и другие люди очень устают, когда смотрят на код. Поэтому, чтобы сохранить код в чистоте и порядке, я создал здесь новый файл для хранения API. Обычно в корневом каталогеutils
создать новую папкуapi.js
, но я создал новую папку в корневом каталогеAPI
, который содержитapi.js
.
// api.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const request = (url, data) => {
let _url = API_BASE_URL + url;
return new Promise((resolve, reject) => {
wx.request({
url: _url,
method: "get",
data: data,
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success(request) {
resolve(request.data)
},
fail(error) {
reject(error)
}
})
});
}
module.exports ={
gethotsongs:(data) =>{
return request('/search/hot',data)//热搜接口
},
searchSuggest:(data)=>{
return request('/search/suggest',data)//搜索建议接口
},
searchResult:(data)=>{
return request('/search',data)//搜索结果接口
},
getBanner:(data)=>{
return request('/banner',data)//个性推荐轮播
},
getsongsheet:(data)=>{
return request('/top/playlist',data)//热门歌单接口
},
getNewSong:(data)=>{
return request('/personalized/newsong',data)//最新音乐接口
},
getDjRadios:(data)=>{
return request('/dj/recommend',data)//电台推荐接口
},
getProgramRecommend:(data)=>{
return request('/program/recommend',data)//推荐节目接口
},
getRecommendType:(data)=>{
return request('/dj/recommend/type',data)//所有电台分类推荐
},
getRecommendMV:(data)=>{
return request('/personalized/mv',data)//推荐MV
},
getNewMv:(data)=>{
return request('/mv/first',data)//最新MV
},
getNewEst:(data)=>{
return request('/album/newest',data)//最新专辑
},
getTopList:(data)=>{
return request('/top/list',data)//排行榜
},
getDjList:(data)=>{
return request('/dj/catelist',data) //电台分类
},
getPay:(data)=>{
return request('/dj/paygift',data)//付费精品
},
getSonger:(data)=>{
return request('/toplist/artist',data)//歌手排行
}
}
api.js
только черезmodule.exports
Чтобы разоблачить эту страницу, нужны данные отсюда. Если вы хотите использовать его на какой странице, вам также нужно ввести его в шапке:
const API = require('../../API/api')
Возьмем, к примеру, карусель персональных рекомендаций.
getBanner: function() {
API.getBanner({
type: 2
}).then(res => {
if (res.code === 200) { //更加严谨
this.setData({
banner: res.banners
})
}
})
}
Это сохраняет запрошенные данные вbanner
бинго.
Раздел поиска
стиль поля ввода
я импортирую сюдаWEUI
стиль,
1. Скачатьweui.wxss
, ссылку не могу найти, поэтому ставлю своюgithubВверхweui.wxss
.
2. Ставим скачанныйweui.wxss
в корневой каталог.
3. Вapp.wxss
середина@import "weui.wxss";
После его введения вы можете использовать стили, предоставляемые WeChat.
4.WeUI
библиотека стилей
популярные запросы
я уже упоминал вышеapi.js
Возьмите данные.
// 从接口到获取到数据导入到hotsongs
gethotsongs() {
API.gethotsongs({ type: 'new' }).then(res => {
wx.hideLoading()
if (res.code === 200) { //严谨
this.setData({
hotsongs: res.result.hots
})
}
})
}
история поиска
Идея: когда ввод в поле ввода завершен --> потерять фокус --> использоватьwx.setStorageSync
сохранить в кэш -->wx.getStorageSync
Получите его и распечатайте.
// input失去焦点函数
routeSearchResPage: function(e) {
console.log(e.detail.value)
let history = wx.getStorageSync("history") || [];
history.push(this.data.searchKey)
wx.setStorageSync("history", history);
},
//每次显示变动就去获取缓存,给history,并for出来。
onShow: function () {
this.setData({
history: wx.getStorageSync("history") || []
})
},
Очистить историю поиска
Идеи: × событие привязки значка -> диалог вызоваwx.showModal
-> Хорошо тогда поставьhistory
назначить пустой
// 清空page对象data的history数组 重置缓存为[]
clearHistory: function() {
const that = this;
wx.showModal({
content: '确认清空全部历史记录',
cancelColor:'#DE655C',
confirmColor: '#DE655C',
success(res) {
if (res.confirm) {
that.setData({
history: []
})
wx.setStorageSync("history", []) //把空数组给history,即清空历史记录
} else if (res.cancel) {
}
}
})
},
Предложения поиска в реальном времени
Идеи: получить значение поля ввода в режиме реального времени -> передать значение в API предложений поиска, инициировать сетевой запрос -> получить предложение поиска после запроса -> распечатать результат и скрыть другие компоненты и сохранить только поиск компонент предложения (аналог v в Vue-show)
//获取input文本并且实时搜索,动态隐藏组件
getsearchKey:function(e){
console.log(e.detail.value) //打印出输入框的值
let that = this;
if(e.detail.cursor != that.data.cursor){ //实时获取输入框的值
that.setData({
searchKey: e.detail.value
})
}
if(e.value!=""){ //组件的显示与隐藏
that.setData({
showView: false
})
} else{
that.setData({
showView: ""
})
}
if(e.detail.value!=""){ //解决 如果输入框的值为空时,传值给搜索建议,会报错的bug
that.searchSuggest();
}
}
// 搜索建议
searchSuggest(){
API.searchSuggest({ keywords: this.data.searchKey ,type:'mobile'}).then(res=>{
if(res.code === 200){
this.setData({
searchsuggest:res.result.allMatch
})
}
})
}
Нажмите «Горячий поиск» или «История», чтобы выполнить поиск.
Идея: главноеevent
, Пролистатьe.currentTarget.dataset.value
Получите значение clicked, а затем передайте его другим методам для выполнения поведения поиска.
// 点击热门搜索值或搜索历史,填入搜索框
fill_value:function(e){
let that = this;
console.log(history)
// console.log(e.currentTarget.dataset.value)
that.setData({
searchKey: e.currentTarget.dataset.value,//点击吧=把值给searchKey,让他去搜索
inputValue: e.currentTarget.dataset.value,//在输入框显示内容
showView:false,//给false值,隐藏 热搜和历史 界面
showsongresult: false, //给false值,隐藏搜索建议页面
})
that.searchResult(); //执行搜索功能
}
результаты поиска
Идея: конец ввода -> клавиша подтверждения -> вызовsearchResult
запросить результат
// 搜索完成点击确认
searchover:function(){
let that = this;
that.setData({
showsongresult: false
})
that.searchResult();
}
// 搜索结果
searchResult(){
console.log(this.data.searchKey)
API.searchResult({ keywords: this.data.searchKey, type: 1, limit: 100, offset:2 }).then(res => {
if (res.code === 200) {
this.setData({
searchresult: res.result.songs
})
}
})
}
Раздел музыкальной библиотеки
На самом деле в музыкальной библиотеке нет сложной части, и она в основном основана на структуре и стиле, поэтому я не буду здесь вдаваться в подробности. можешь прийти ко мнеgithubПосмотреть выше. Здесь делюсь реализацией некоторых мелких функций и ямами, на которые наступил.
Персональные рекомендации, переключение хост-станции
1. Личная рекомендация и якорная станция — это дваswiper-item
То есть они могут свайпать влево-вправо, как карусель, но карусель показывает картинки, а здесь — целая страница.
2. Эффект, которого я хочу добиться, это скользить влево и вправо,个性推荐
и主播电台
Белый квадрат внизу тоже скользит.
1. Первый способ
пакет дваswiper-item
изswiper
добавить однуbindchange="changeline"
событие, поместите объект событияevent
распечатай и найдиconsole.log(e.detail.current)
, когда мы проводим пальцем влево и вправоcuurrent
Значение будет в0
и1
переключаться между. Поэтому я добавляю к белому квадрату
class="{{changeline?'swiper_header_line_before':'swiper_header_line_after'}}"
if(e.detail.current === 0){
this.setData({
changeline:true
})
}else{
this.setData({
changeline:false
})
}
когдаcurrent
равно 0, то есть когда страница персонализированная рекомендация, пустьchangeline
заtrue
;когдаcurrent
Это 1, то есть когда страница находится на якорной станции, пустьchangeline
заfalse
;заtrue
, благослови белый квадратswiper_header_line_before
стиль, дляfalse
когда, поддержкаswiper_header_line_after
стиль. так что вы можете следоватьswiper-item
проведите, чтобы переключиться.Однако этот метод переключения слишком жесткий, не имеет эффекта плавного перехода и не подходит для многихswiper-item
страница.
2. Второй способ
Пусть половина ширины четверти ширины к переменным, чтобы быть совместимым с различными моделями телефонов. Поскольку данные записи наверняка будут мертвыми BUG, поэтому следует вычислить ширину.
<view class="weui-navbar-slider" style="transform:translateX({{slideOffset}}px);"></view>
.weui-navbar-slider{
width:28px;
height: 5px;
background: #ffffff;
border-radius:10rpx;
transition: transform .6s;
}
slideOffset
является переменной, которая динамически принимает изdata
передаваемые данные.
onLoad:function(){
wx.getSystemInfo({
success: function (res) {
// console.log(res.windowWidth)
// console.log(res.windowWidth / 2 / 2)
half = res.windowWidth / 2 ;
quarter = res.windowWidth / 2 / 2;
that.setData({
slideOffset: quarter - 14 //onLoad的时候让 quarter - 14 给slideOffset,即一开始就让他在个性推荐的下面,否则onLoad的时候一开始在0的位置
})
}
})
}
changeline:function(e){
// console.log(e)
// console.log(e.detail.current)
let current = e.detail.current; //获取swiper的current值
if(e.detail.current === 0){
this.setData({
slideOffset: quarter - 14
})
}
if(e.detail.current === 1){
this.setData({
slideOffset: (quarter - 14) + half
})
}
if(e.detail.current === null){
this.setData({
slideOffset: quarter - 14
})
}
}
Воспроизведение клипа
В основном структура и стиль, я перешел непосредственно к коду.
<!-- play_mv.wxml -->
<view class="mv_box">
<video src="{{mv.brs['480']}}" class="mv" autoplay="{{autoplay}}" loop="{{loop}}" direction="{{0}}" show-fullscreen-btn="{{showfullscreenbtn}}"
show-center-play-btn="{{showcenterplaybtn}}" enable-progress-gesture="{{enableprogressgesture}}" show-mute-btn="{{showmutebtn}}" title="{{mv.name}}"
play-btn-position="{{center}}" object-fit="{{objectfit}}"></video>
</view>
<view class="mv_name">{{mv.name}}</view>
<view class="mv_time"> 发行: {{mv.publishTime}}</view>
<view class="mv_time mv_times">播放次数: {{mv.playCount}}</view>
<view class="mv_time mv_desc">{{mv.desc}}</view>
<view class="mv_time mv_desc mv_other">点赞: {{mv.likeCount}}</view>
<view class="mv_time mv_desc mv_other">收藏: {{mv.subCount}}</view>
<view class="mv_time mv_desc mv_other">评论: {{mv.commentCount}}</view>
<view class="mv_time mv_desc mv_other">分享: {{mv.shareCount}}</view>
/* play/play_mv.wxss */
.mv_box{
width: 100%;
height: 480rpx;
margin-top:-2rpx;
}
.mv{
width: 100%;
height: 100%;
border-radius:15rpx;
}
.mv_name{
margin-top:20rpx;
margin-left:20rpx;
}
.mv_time{
font-size: 12px;
margin-left:20rpx;
color:#979798;
display:initial;
}
.mv_times{
margin-left: 100rpx;
}
.mv_desc{
display: block;
color:#6A6B6C;
}
.mv_other{
display: block;
}
// play_mv.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
data: {
mv: [],
autoplay: true,
loop: true,
showfullscreenbtn: true,
showcenterplaybtn: true,
enableprogressgesture: true,
showmutebtn: true,
objectfit: 'contain',
},
onLoad: function (options) {
// console.log(mv_url);
const mvid = options.id; // onLoad()后获取到歌曲视频之类的id
// 请求MV的地址,失败则播放出错,成功则传值给createBgAudio(后台播放管理器,让其后台播放)
wx.request({
url: API_BASE_URL + '/mv/detail',
data: {
mvid: mvid
},
success: res => {
console.log(res.data.data.brs['480'])
console.log('歌曲音频url:', res)
if (res.data.data.brs === null) { //如果是MV 电台 广告 之类的就提示播放出错,并返回首页
console.log('播放出错')
wx.showModal({
content: '服务器开了点小差~~',
cancelColor: '#DE655C',
confirmColor: '#DE655C',
showCancel: false,
confirmText: '返回',
complete() {
wx.switchTab({
url: '/pages/index/index'
})
}
})
} else {
this.setData({
mv: res.data.data
})
}
}
})
},
})
список певцов
// 歌手榜的js
const API = require('../../API/api');
const app = getApp();
Page({
data: {
songers: [], //歌手榜
},
onLoad: function (options) {
wx.showLoading({
title: '加载中',
});
this.getSonger();
},
getSonger: function () {
API.getSonger({}).then(res => {
wx.hideLoading()
this.setData({
songers: res.list.artists.slice(0, 100)
})
})
},
handleSheet: function (event) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性
const sheetId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId
wx.navigateTo({ //获取到id带着完整url后跳转到play页面
url: `./moremore_songer?id=${sheetId}`
})
},
})
<!-- 歌手榜结构 -->
<view wx:for="{{songers}}" wx:key="" class='songer_box' data-id="{{item.id}}" bindtap='handleSheet'>
<view class='songer_index_box'>
<text class='songer_index'>{{index + 1}}</text>
</view>
<view class='songer_img_box'>
<image src="{{item.picUrl}}" class='songer_img'></image>
</view>
<view class='songer_name_box'>
<text class='songer_name'>{{item.name}}</text>
<text class='songer_score'>{{item.score}}热度</text>
</view>
</view>
// 歌手下级路由歌曲列表
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
data: {
songList: []
},
onLoad: function (options) {
wx.showLoading({
title: '加载中',
});
const sheetId = options.id;
wx.request({
url: API_BASE_URL + '/artists',
data: {
id: sheetId
},
success: res => {
const waitForPlay = new Array;
for (let i = 0; i <= res.data.hotSongs.length - 1; i++) { //循环打印出其id
waitForPlay.push(res.data.hotSongs[i].id) //循环push ID 到waitForPlay数组
app.globalData.waitForPlaying = waitForPlay //让waitForPlay数组给全局数组
// console.log(app.globalData.waitForPlaying)
}
wx.hideLoading()
console.log(res.data.hotSongs)
this.setData({
songList: res.data.hotSongs
})
}
})
},
handlePlayAudio: function (event) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性
const audioId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId
wx.navigateTo({ //获取到id带着完整url后跳转到play页面
url: `../../play/play?id=${audioId}`
})
}
})
<!-- more/more_songer/moremore_songer.wxml歌手下面的歌曲 -->
<view class='search_result_songs'>
<view wx:for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'>
<view class='songer_index_box'>
<text class='songer_index'>{{index + 1}}</text>
</view>
<view class='songer_img_box'>
<view class='search_result_song_song_name'>{{item.name}}</view>
<view class='search_result_song_song_art-album'>{{item.ar[0].name}} - {{item.al.name}}</view>
</view>
</view>
</view>
Рекомендуемый плейлист
Так как стиль аналогичен лидерборду, выкладываются только картинки, а исходный код можно найти у меняgithubПосмотреть выше.
ранжирование
Пожалуйста, просмотрите исходный код
изменить функцию
Идеи: Привязать событие клика -> выбрать три случайных числа -> дать нулевые значения -> запихнуть в массив три случайных числа -> переназначить значения.
// 换一换
change_1:function(){
let maxNum = this.data.more_recommend_create.length //计算数据长度
let r1 = parseInt(Math.random() * (maxNum - 0) + 0); //取【0-数据长度】内的整数随机数
let r2 = parseInt(Math.random() * (maxNum - 0) + 0);
let r3 = parseInt(Math.random() * (maxNum - 0) + 0);
this.setData({
recommend_create: []
})
//重新取3组数据
this.data.recommend_create.push(this.data.more_recommend_create[r1])
this.data.recommend_create.push(this.data.more_recommend_create[r2])
this.data.recommend_create.push(this.data.more_recommend_create[r3])
//重新赋值
this.setData({
recommend_create: this.data.recommend_create
})
}
Интерфейс игры
Картинка слишком большая, так что ускорьте.
Воспроизведение
идея: использоватьdata-id="{{item.id}}"
Получите идентификатор песни и вставьте егоevent
Средний -> Пройденоevent
Событие объекта получает идентификатор и переходит на страницу воспроизведения ->wx.request
Получите аудиоадрес и детали-> менеджер фонового звука песниwx.getBackgroundAudioManager()
-> Играть
В качестве примера возьмем список песен маршрутизации нижнего уровня из списка исполнителей.
<view wx:for="{{songList}}" wx:key="" class='search_result_song_item songer_box' data-id="{{item.id}}" bindtap='handlePlayAudio'>
handlePlayAudio: function (event) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性
const audioId = event.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给audioId
wx.navigateTo({ //获取到id带着完整url后跳转到play页面
url: `../../play/play?id=${audioId}`
})
}
// play.js
const API_BASE_URL = 'http://musicapi.leanapp.cn';
const app = getApp();
Page({
data: {
isPlay: '',
song:[],
innerAudioContext: {},
show:true,
showLyric:true,
songid:[],
history_songId:[]
},
onLoad: function (options) {
const audioid = options.id; // onLoad()后获取到歌曲视频之类的id
this.play(audioid); //把从wxml获取到的值传给play()
},
play: function (audioid){
const audioId = audioid;
app.globalData.songId = audioId; //让每一个要播放的歌曲ID给全局变量的songId
const innerAudioContext = wx.createInnerAudioContext();
this.setData({
innerAudioContext,
isPlay: true
})
// 请求歌曲音频的地址,失败则播放出错,成功则传值给createBgAudio(后台播放管理器,让其后台播放)
wx.request({
url: API_BASE_URL + '/song/url',
data: {
id: audioId
},
success: res => {
if (res.data.data[0].url === null) { //如果是MV 电台 广告 之类的就提示播放出错,并返回首页
wx.showModal({
content: '服务器开了点小差~~',
cancelColor: '#DE655C',
confirmColor: '#DE655C',
showCancel: false,
confirmText: '返回',
complete() {
wx.switchTab({
url: '/pages/index/index'
})
}
})
} else {
this.createBgAudio(res.data.data[0]);
}
}
})
//获取到歌曲音频,则显示出歌曲的名字,歌手的信息,即获取歌曲详情;如果失败,则播放出错。
wx.request({
url: API_BASE_URL + '/song/detail',
data: {
ids: audioId //必选参数ids
},
success: res => {
if (res.data.songs.length === 0) {
wx.showModal({
content: '服务器开了点小差~~',
cancelColor: '#DE655C',
confirmColor: '#DE655C',
showCancel: false,
confirmText: '返回',
complete() {
wx.switchTab({
url: '/pages/index/index'
})
}
})
} else {
this.setData({
song: res.data.songs[0], //获取到歌曲的详细内容,传给song
})
app.globalData.songName = res.data.songs[0].name;
}
},
})
},
createBgAudio(res) {
const bgAudioManage = wx.getBackgroundAudioManager(); //获取全局唯一的背景音频管理器。并把它给实例bgAudioManage
app.globalData.bgAudioManage = bgAudioManage; //把实例bgAudioManage(背景音频管理器) 给 全局
bgAudioManage.title = 'title'; //把title 音频标题 给实例
bgAudioManage.src = res.url; // res.url 在createBgAudio 为 mp3音频 url为空,播放出错
const history_songId = this.data.history_songId
const historySong = {
id: app.globalData.songId,
songName:app.globalData.songName
}
history_songId.push(historySong)
bgAudioManage.onPlay(res => { // 监听背景音频播放事件
this.setData({
isPlay: true,
history_songId
})
});
bgAudioManage.onEnded(() => { //监听背景音乐自然结束事件,结束后自动播放下一首。自然结束,调用go_lastSong()函数,即歌曲结束自动播放下一首歌
this.go_lastSong();
})
wx.setStorageSync('historyId', history_songId); //把historyId存入缓存
},
})
Пауза воспроизведения
<!-- 暂停播放图标 -->
<view class="play_suspend">
<view class="icon_playing"><image bindtap="handleToggleBGAudio" src="../images/suspend.png" hidden="{{!isPlay}}" class="{{'img_play_suspend'}}" /> <!-- 暂停图标-->
<image bindtap="handleToggleBGAudio" src="../images/play.png" hidden="{{isPlay}}" class="{{'img_play_suspend'}}" /></view> <!--播放图标-->
</view>
// 播放和暂停
handleToggleBGAudio() {
// const innerAudioContext = app.globalData.innerAudioContext;
const bgAudioManage = app.globalData.bgAudioManage;
const {isPlay} = this.data;
if (isPlay) {
bgAudioManage.pause();
// innerAudioContext.pause();handleToggleBGAudio
} else {
bgAudioManage.play();
// innerAudioContext.play();
}
this.setData({
isPlay: !isPlay
})
console.log(this.data.isPlay)
}
Предыдущий/Следующий (в случайном порядке)
Идея: Нажмите на плейлист или страницу исполнителя, чтобы получить соответствующий плейлист/идентификатор исполнителя->wx.request
Запросить данные, чтобы получить аудиоадрес всех песен в плейлисте/популярной песне исполнителя -> дать глобальную переменную globalData -> нажать на предыдущую/следующую песню, чтобы случайным образом получить фрагмент данных глобальной переменной -> дать воспроизведение( ) метод -> играть
<!--歌单-->
onLoad: function (options) {
wx.showLoading({
title: '加载中',
});
const sheetId = options.id;
wx.request({
url: API_BASE_URL + '/playlist/detail',
data: {
id: sheetId
},
success: res => {
const waitForPlay = new Array;
for (let i = 0; i <= res.data.playlist.trackIds.length - 1;i++){ //循环打印出其id
waitForPlay.push(res.data.playlist.trackIds[i].id) //循环push ID 到waitForPlay数组
app.globalData.waitForPlaying = waitForPlay //让waitForPlay数组给全局数组
}
wx.hideLoading()
this.setData({
songList: res.data.playlist.tracks
})
}
})
}
<view class="icon_playing "><image src="../images/lastSong.png" class=" icon_play" bindtap="go_lastSong" /></view>
<view class="icon_playing "><image src="../images/nextSong.png" class=" icon_play" bindtap="go_lastSong" /></view>
go_lastSong:function(){
let that = this;
const lastSongId = app.globalData.waitForPlaying;
const songId = lastSongId[Math.floor(Math.random() * lastSongId.length)]; //随机选取lastSongId数组的一个元素
that.data.songid = songId;
this.play(songId)//传进play()方法中
app.globalData.songId=songId;
}
Переключатель слов/обложек
Поскольку интерфейс текстов песен NetEase Cloud API дал сбой и тексты песен не могли быть запрошены, я мог только написать текст до смерти, как纯音乐,请欣赏
. похожий наv-show
.
<!-- 封面 -->
<!-- 一开始onload时,showLyric=true, 显示为转动的图标,点击图标,切换为歌词-->
<view class="sing-show" bindtap="showLyric" >
<view class="moveCircle {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}">
<image src="{{song.al.picUrl}}" class="coverImg {{isPlay ? 'play' : ''}}" hidden="{{!showLyric}}"/>
</view>
<text hidden="{{showLyric}}" class="songLyric">纯音乐,请欣赏</text>
</view>
// 点击切换歌词和封面
showLyric(){
const {showLyric} = this.data;
this.setData({
showLyric: !showLyric
})
}
Обанкротившаяся версия анимации Lonely Planet
Вращение обложки:
@keyframes rotate {
0%{
transform: rotate(0);
}
100%{
transform: rotate(360deg);
}
}
Рассеянные круговые линии: На самом деле снаружи есть коробка, ширина и высота коробки становятся больше, а прозрачность постепенно снижается.
@keyframes moveCircle {
0%{
width: 400rpx;
height: 400rpx;
border: 1px solid rgba(255, 255, 255, 1)
}
30%{
width: 510rpx;
height: 510rpx;
border: 1px solid rgba(255, 255, 255, 0.8)
}
50%{
width: 610rpx;
height: 610rpx;
border: 1px solid rgba(255, 255, 255, 0.6)
}
80%{
width: 700rpx;
height: 700rpx;
border: 1px solid rgba(255, 255, 255, 0.4)
}
99%{
width: 375px;
height: 375px;
border: 1px solid rgba(255, 255, 255, 0.1)
}
100%{
width: 0px;
height: 0px;
border: 1px solid rgba(255, 255, 255, 0)
}
}
фон матовое стекло
<!-- play.wxml -->
<image src="{{song.al.picUrl}}" class="background_img" ></image>
/* 播放界面毛玻璃效果 */
.background_img{
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
filter: blur(20px);
z-index: -1;
transform: scale(1.5); /*和网易云音乐对比了一下,发现也是放大1.5倍*/
}
играть tabBar
Идея состоит в том, чтобы сослаться на апплет Kugou Music. js и wxml этой панели вкладок такие же, как js и wxml интерфейса функции воспроизведения. Поскольку воспроизведение музыкиwx.getBackgroundAudioManager()
Управляется фоновым аудиоплеером, поэтому его можно синхронизировать.
мой tabBar
История игр
Идея: после успешного воспроизведения в play.js передайте название песни и идентификатор песни в глобальную переменную -> поместите в массив в play.js ->wx.setStorageSync
Сохранить данные в кеше -> на нужной страницеwx.getStorageSync
Получить кеш.
<!--play.js-->
const history_songId = this.data.history_songId
const historySong = {
// id: res.id
id: app.globalData.songId,
songName:app.globalData.songName
}
history_songId.push(historySong)
wx.setStorageSync('historyId', history_songId); //把historyId存入缓存
<!--me.js-->
onShow:function(){
var history = wx.getStorageSync('historyId');
// console.log(history)
this.setData({
hidden:true,
// historyId: app.globalData.songName
historyId: history
})
console.log(this.data.historyId)
}
Эпилог
Процесс выполнения проекта в целом болезненный и радостный, потому что действительно стыдно не иметь возможности исправить баг, но момент, когда некая функция реализована, очень радует. Еще раз спасибо учителям и ученикам, которые помогали. Если вам понравилась эта статья или она может быть вам полезна, пожалуйста, поставьте лайк! В то же время, я также очень надеюсь, что вы, кто читает эту статью, даст свои предложения ниже!