其多个页面:营造音讯实际情况页面 分分

作者:分分快三计划

音乐控制

由于初始的时候是听第一首歌(其实第几首根本无所谓),所以currentMusicIndex初始化为个单列表的长度减一(这句话是后来补上的,不能初始化为length

1,因为这个数组实际上在一开始的时候是没有定义的)。然后我想切换播放歌曲不管是点击下一首也好,还是左右滑动也好,还是自动播放下一首也好,本质上都是对currentMusicIndex进行修改。所以可以观察currentMusicIndex这个变量的变化,如果有变化,那么就切换资源并且播放音频。修改代码如下:

  <template>
    <div class="music-player">
      <header-bar></header-bar>
      <div class="mid">
        <audio :src="currentMusicUrl" autoplay @ended="_playDefaultMusic(currentMusicIndex)" ref="audio"></audio>
        <img src="../../assets/logo.png">
      </div>
      <music-controller></music-controller>
    </div>
  </template>
  ​
  ​
  <script>
    import HeaderBar from 'components/header-bar/header-bar'
    import MusicController from 'components/music-controller/music-controller'
    import getDefaultMusicList from 'api/getDefaultMusicList'
    import {getMusicUrl} from 'api/playThisMusic'
  ​
    export default {
      data () {
        return {
          defaultMusicList: [],
          currentMusicUrl: '',
          currentMusicIndex: 0,
        }
      },
      components: {
        HeaderBar,
        MusicController
      },
      created () {
        console.log('MusicPlayer Created')
        this._getDefaultMusicList()
      },
      methods: {
        _getDefaultMusicList () {
          getDefaultMusicList()
            .then((res) => {
              if (res.code === 200) {
                this.defaultMusicList = res.result
                console.log(this.defaultMusicList)
              }
            })
            .then(() => {
              this._playDefaultMusic()
            })
        },
        _playDefaultMusic () {
          if (this.currentMusicIndex === this.defaultMusicList.length - 1) {
            this.currentMusicIndex = 0
          } else {
            this.currentMusicIndex = this.currentMusicIndex   1
          }
          getMusicUrl(this.defaultMusicList[this.currentMusicIndex].id)
            .then((res) => {
              this.currentMusicUrl = res.data[0].url
            })
        }
      },
      watch: {
        currentMusicIndex: function (newVal, oldVal) {
          console.log(this.$refs.audio)
          this.$refs.audio.play()
        }
      }
    }
  </script>

可以运行,但是报一个很诡异的错误:

  Uncaught (in promise) DOMException: The element has no supported sources.

为什么呢?我想是因为一开始的时候audio中的src绑定的变量是currentMusicUrl,但是这个data初始化为空字符串,然而我这里play()方法调用的时机是在_playDefaultMusic中改变了currentMusicIndex,然后在修改的currentMusicUrl。所以会出现src没有的情况。并且还由别的bug。这个错误只报一次是因为最开始的一次直接没有src。把两段代码交换一下位置,有什么事明天再说。太晚了,得回去。

 

 

参考链接:

  1. axios中文说明

  2. axios github

  3. express 文档

  4. 网易云API文档

  5. Promise 介绍

  6. audio W3C介绍

  7. vue watch 文档

缓存Storage的基本用法

在文章详情页中我们需要实现一个文章收藏的功能,由于我们没有使用到服务器,所以使用本地缓存来记录这个文章是否被用户收藏的一个状态。

小程序中提供了一个setStorageSync方法来实现缓存,从方法名也可以看出这个方法是带有同步的。除此之外还有一个异步的缓存方法setStorage,这个方法可以用于异步缓存数据。

注:和缓存相关的方法,例如得到缓存数据、删除缓存数据等方法,都有同步和异步两个,方法名末尾有Sync的表示同步,否则是异步。

首先演示一下setStorageSync方法的使用方式:

// 第一个参数是键,第二个参数则是需要存储的数据
wx.setStorageSync('key', "Test");

我在post-detail.js文件中的onLoad方法里加入了以上这段代码,此时我点击进入文章详情页面,就会缓存这个数据,缓存数据在Storage界面查看:
分分快三计划 1

在小程序中,如果用户不去主动清除缓存数据,那么数据就会一直存在,所以现在即便我关闭开发工具或者重新进行编译,这个数据都会存在,除非我主动删掉它。

通过键可以改变缓存数据:

wx.setStorageSync('key', {
      game:"eat chicken",
      developer:"LD",
});

运行结果:
分分快三计划 2

获取缓存的数据:

<!-- 在收藏图标上加上一个事件 -->
<image catchtap='onCollectionTap' src='icon/collection.png'></image>

使用getStorageSync方法即可得到缓存数据:

onCollectionTap:function(event){
    var game=wx.getStorageSync('key');
    console.log(game);
}

运行结果:
分分快三计划 3

删除缓存数据:

<!-- 在分享图标上加上一个事件 -->
<image catchtap='onShareTap' class='share-img' src='icon/share.png'></image>

使用removeStorageSync方法即可删除缓存数据:

onShareTap:function(event){
    wx.removeStorageSync('key');
},

运行结果,可以看到数据已经不存在了:
分分快三计划 4

删除所有缓存数据的方法:

wx.clearStorageSync();

注:小程序规定缓存数据的大小上限是10MB


初始化音乐

首先,在properties中接收页面传来的音乐文件地址,

music: {
  type: String,
  value: '',
  observer: function (newVal) {
    this._initMusic(newVal)
  }
}

这里的处理是,一旦接收到页面传来的 music 地址,就初始化音乐:

_initMusic: function (newVal) {
  // 当页面传来新的music时,先销毁之前的audioCtx,否则页面会很嗨
  if (this.data.audioCtx) {
    this.data.audioCtx.destroy()
  }
  if (newVal) {
    var audioCtx = wx.createInnerAudioContext()
    this.setData({
        audioCtx: audioCtx
    })
    if (this.data.audioStatus == '1') {
        audioCtx.autoplay = true
    }
    audioCtx.loop = true
    audioCtx.src = newVal
  }
}

 audioStatus 用来记录音乐播放状态,在data中默认设置为1:

data: {
    icon: '',
    audioStatus: 1,
    audioCtx: '',
    musicClass: 'music-on'
}

wxml文件里,只用一个 <image> 标签:

<image class='music {{ rotate && musicClass }}'  
        style="{{ musicStyle }}"  
        src="{{ icon }}"  
        bindtap='_switch'  
        wx:if="{{ music }}"></image>

其中, icon 在组件ready()时赋值成播放状态的icon:

ready() {
    this.setData({
      icon: this.data.iconOn
    })
}

轻量级音乐播放器搭建 3

 

接着之前的工作,现在想要对歌曲进行请求。首先应当启动网易云音乐的API的服务器,github地址如下。克隆这个项目后,在终端启动:

  npm install
  node app.js

现在这个服务器就启动了,默认为3000端口。但是又有一个问题就是跨域,webpack没有更改默认的配置的话实在本地服务器的8080端口,但是以上的网易云API服务器的端口为本地服务器的3000端口,所以端口不一致,不符合同源策略,所以这里就需要使用axios。

axios是什么?链接中是中文说明。据说之前尤雨溪已经建议使用axios,VueResource不再进行维护。axios就是一个基于Promise的http客户端。安装axios:

  cnpm install axios --save

下面就是在webpack的本地服务器中配置axios,回到项目的根目录中,找到dev-server.js这个文件,这个文件就是开发环境的服务器。就在这个文件中进行路由与axios请求转发的配置。

  ......
  var axios = require('axios')
  var app = express()
  ​
  var apiRoutes = express.Router()
  ​
  apiRoutes.get('/getSomething', function (req, res) {
    var url = 'https://anotherUrl.com/something'
    axios.get(url, {
      headers: {
        referer: 'https://anotherUrl.com/',
        host: 'anotherUrl.com'
      },
      params: req.query
    }).then((response) => {
      res.json(response.data)
    }).catch((e) => {
      console.log(e)
    })
  })
  ​
  app.use('/api', apiRoutes)
  ......

由以上的代码示例可以看出,使用axios步骤如下:

  1. 创建express.Router实例,用以获得路由。

  2. 对express.Router的实例进行监听各种请求,如get等。当匹配到相应的url的时候,执行回调函数。

  3. 编写回调函数,回调函数用于对请求进行转发,并获取响应。所以函数中需要转发的url,然后就可以调用axios模块的get方法(这里以用户发起get请求为例)。axios的get方法有三个参数,第一个参数就是要进行转发的url,这个必选;第二个参数是各种配置,包括headers,这个对于跨域的请求来说非常重要,因为他改变了请求头部信息;汗包括一个参数就是params,就是请求所携带的参数。然后就是执行promise的then回调,将response的data传回res的json。

  4. 最后,将express框架应用实例调用use方法来使用这个router实例,当请求的路径匹配到第一个参数的路径的时候,就路由到apiRoutes这个模块进行处理。

所以在dev-server.js中添加如下代码:

  // axios
  var apiRouter = express.Router()
  apiRouter.get('/getDefaultMusicList', function (req, res) {
    let url = 'http://localhost:3000/personalized/newsong'
    axios.get(url, {
      headers: {
        referer: 'http://localhost:3000/',
        host: 'localhost:3000'
      },
      params: req.query
    }).then((response) => {
      console.log(response)
      res.json(response.data)
    }).catch((err) => {
      console.log(err)
    })
  })
  app.use('/api', apiRouter)

这样的话,就可以在music-player.vue中进行歌单的请求。但是先不要着急,因为比较好的思路与框架是进行模块化,于是在src的common/api目录中创建这些请求的功能,然后在vue组件中去引入使用这些模块。所以先创建getDefaultMusicList.js文件:

  import axios from 'axios'
  export default function getDefaultMusicList () {
    return axios.get('/api/getDefaultMusicList')
      .then((res) => {
        return Promise.resolve(res.data)
      })
  }

这里就是使用了一个axios的http请求功能,地址就是本地服务器的地址加上路由的api(与之前在apiRouter实例中的路由对应),返回这个函数的请求返回值。那么这个请求得到数据之后又再一次的进行解析,将解析后的数据返回。现在可以在music-player.vue中使用了:

  <template>
    <div class="music-player">
      <header-bar></header-bar>
      <div class="mid">
        <img src="../../assets/logo.png">
      </div>
      <music-controller></music-controller>
    </div>
  </template>
  ​
  ​
  <script>
    import HeaderBar from 'components/header-bar/header-bar'
    import MusicController from 'components/music-controller/music-controller'
    import getDefaultMusicList from 'api/getDefaultMusicList'
  ​
    export default {
      components: {
        HeaderBar,
        MusicController
      },
      created () {
        console.log('MusicPlayer Created')
        this._getDefaultMusicList()
      },
      methods: {
        _getDefaultMusicList () {
          getDefaultMusicList().then((res) => {
              if (res.code === 200) {
                this.defaultMusicList = res.result
                console.log(this.defaultMusicList)
              }
            })
        }
      },
      data () {
        return {
          defaultMusicList: [],
        }
      }
    }
  </script>

现在如果打开浏览器的控制台,就会看到当前这个music-player组件中的defaultMusicList的数据,我们已经获取到了,是一个长度为10的数组。

现在要想播放一首歌曲,改怎么办呢?查询API的文档,找到了获取音乐 url的接口。如果将获取到的歌单中任一id作为参数进行请求,在浏览器中会是如下的返回结果:

然后呢,返回值中data数组只有一个对象,对象里有一个url,打开这个url就可以听到歌曲了。然而机智的我发现事情并不是那么简单,歌曲的时间长度在哪?歌曲的背景图片在哪?歌曲的各种参数都在哪?我靠这个list中的元素展开之后十分复杂。各种参数都不知道是干什么的,以第一个元素为例,他的歌曲背景图片的地址是在song->album->blurPicUrl之中。至于播放时间长度,这个好像是在song->bMusic/hMusic/lMusic/mMusic->playtime中。这几个music我估计应该是音乐品质的区分吧。但是这个playtime怎么解释,这里的数字是185696,臣妾想不通啊。计算得不到一个像是时间的结果。另外这个是在list中才有的属性,如果在其他的地方可能就没有这个属性了。真让人头大。算了这些先不管了,先用这个数据吧,至于播放时间就先不用了,也就是进度条暂时也不写了。

那么下面就是对歌单中的歌曲进行播放,我想要对列表进行循环播放。因为列表的长度是有限的但是不能播放完默认的列表就停止了,所以应当进行循环的播放。所以获取完歌单之后就进行自动的播放。所以在api目录中新建一个播放歌曲的函数,播放歌曲是另外一个请求,所以还是使用axios来进行转发:

所以创建playThisMusic.js文件:

  import axios from 'axios'
  ​
  export default function playThisMusic (music) {
    let response = getMusicUrl(music.id)
    //axios.get('')
  }
  ​
  function getMusicUrl(id) {
    let url = `/api/getMusicUrl/url?id=${id}`
    return axios.get(url).then((res) => {
      console.log(res.data)
      return Promise.resolve(res.data)
    })
  }

这里就是说现在有一个在defaultMusicList中的元素music。有了这个元素,可以获得一些关于这个歌曲的信息,但是获得不了歌曲播放的地址。所以使用getMusicUrl方法来获取歌曲播放的地址。那这个url地址怎么获得呢?还是要通过当前歌曲的id发送一个请求,然后再进行解析。但是还是老问题,就是获得url又跨域了,所以还是要再服务器端使用axios来发送请求。

由于跨域发送的请求估计会有很多,所以我想把这个apiRouter做成一个模块来引入到服务器端文件。所以再build目录创建apiRouter文件,并进行修改与引用如下:

  ......
  apiRouter.get('/getMusicUrl/url', function (req, res) {
    let url = `http://localhost:3000/music/url`
    axios.get(url, {
      headers: {
        referer: 'http://localhost:3000/',
        host: 'localhost:3000'
      },
      params: req.query
    }).then((response) => {
      res.json(response.data)
    }).catch((err) => {
      console.log(err)
    })
  })
  ​
  module.exports = apiRouter

这里有一点就是注意apiRouter所get的第一个url参数,参数必须要完全匹配才可以,如果只写为'/getMusicUrl'则是匹配不到的。url部分就是一个请求的'?'之前的部分,之后为params部分。然后作为一个模块,随后应当使用module.exports来对apiRouter进行暴露出去。

现在再控制台中就可以看到有了返回的信息。是一个对象,对象的data部分是一个长度为1的数组。数组中的url属性就是我们需要的播放地址。所以返回playThisMusic.js文件继续对playThisMusic函数进行修改:

  import axios from 'axios'
  ​
  export function playThisMusic (music) {
    let url = ''
    getMusicUrl(music.id).then((res) => {
      if (res.code === 200) {
        url = res.data[0].url
      } else {
        console.log('未能获取播放地址')
      }
    })
  }
  ​
  export function getMusicUrl(id) {
    let url = `/api/getMusicUrl/url?id=${id}`
    return axios.get(url).then((res) => {
      console.log(res.data)
      return Promise.resolve(res.data)
    })
  }

我一开始以为需要对这个播放的url进行请求才能播放音乐,结果发现不是这回事。在html5中,有专门的audio标签来播放音频文件。所以以上代码中的playThisMusic方法就不需要了。修改music-player.vue文件:

  <template>
    <div class="music-player">
      <header-bar></header-bar>
      <div class="mid">
        <audio :src="currentMusicUrl" autoplay></audio>
        <img src="../../assets/logo.png">
      </div>
      <music-controller></music-controller>
    </div>
  </template>
  ​
  ​
  <script>
    import HeaderBar from 'components/header-bar/header-bar'
    import MusicController from 'components/music-controller/music-controller'
    import getDefaultMusicList from 'api/getDefaultMusicList'
    import {getMusicUrl} from 'api/playThisMusic'
  ​
    export default {
      components: {
        HeaderBar,
        MusicController
      },
      created () {
        console.log('MusicPlayer Created')
        this._getDefaultMusicList()
      },
      methods: {
        _getDefaultMusicList () {
          getDefaultMusicList()
            .then((res) => {
              if (res.code === 200) {
                this.defaultMusicList = res.result
                console.log(this.defaultMusicList)
              }
            })
            .then(() => {
              this._playDefaultMusic(this.defaultMusicList.length - 1)
            })
        },
        _playDefaultMusic (lastIndex) {
          let currentIndex
          if (lastIndex === this.defaultMusicList.length - 1) {
            currentIndex = 0
          } else {
            currentIndex = lastIndex   1
          }
          getMusicUrl(this.defaultMusicList[currentIndex].id)
            .then((res) => {
              this.currentMusicUrl = res.data[0].url
            })
        }
      },
      data () {
        return {
          defaultMusicList: [],
          currentMusicUrl: '',
        }
      }
    }
  </script>

怎么没有声音?我找了半天的错误,发现原因就是慢!音频没有缓冲好,但是耐心等待一会确实会听到断断续续地播放的,至于为什么这么慢我也不知道,如果直接请求音乐播放的url的话可以瞬间打开,但是作为audio标签的src就很慢,我也不知道是为什么。欸,不行。现在直接请求的话直接没有歌了,我猜可能是因为网易云音乐那边的限制。

接着往下进行,现在如果要让歌曲要自动调到下一个歌曲。经过查阅W3C文档,发现audio元素有许多有用的事件。

Event name Dispatched when...
loadstart The user agent begins looking for media data, as part of the resource selection algorithm.
progress The user agent is fetching media data.
suspend The user agent is intentionally not currently fetching media data, but does not have the entire media resource downloaded.
abort The user agent stops fetching the media data before it is completely downloaded, but not due to an error.
emptied A media element whose networkState was previously not in the NETWORK_EMPTY state has just switched to that state (either because of a fatal error during load that's about to be reported, or because the load() method was invoked while the resource selection algorithm was already running).
error An error occurs while fetching the media data.
stalled The user agent is trying to fetch media data, but data is unexpectedly not forthcoming.
play Playback has begun. Fired after the play() method has returned, or when the autoplay attribute has caused playback to begin.
pause Playback has been paused. Fired after the pause() method has returned.
loadedmetadata The user agent has just determined the duration and dimensions of the media resource
loadeddata The user agent can render the media data at the current playback position for the first time.
waiting Playback has stopped because the next frame is not available, but the user agent expects that frame to become available in due course.
playing Playback has started.
canplay The user agent can resume playback of the media data, but estimates that if playback were to be started now, the media resource could not be rendered at the current playback rate up to its end without having to stop for further buffering of content.
canplaythrough The user agent estimates that if playback were to be started now, the media resource could be rendered at the current playback rate all the way to its end without having to stop for further buffering.
seeking The seeking IDL attribute changed to true and the seek operation is taking long enough that the user agent has time to fire the event.
seeked The seeking IDL attribute changed to false.
timeupdate The current playback position changed as part of normal playback or in an especially interesting way, for example discontinuously.
ended Playback has stopped because the end of the media resource was reached.
ratechange Either the defaultPlaybackRate or the playbackRate attribute has just been updated.
durationchange The duration attribute has just been updated.
volumechange Either the volume attribute or the muted attribute has changed. Fired after the relevant attribute's setter has returned.

对于要切换歌曲,时机就在于一首歌的结束位置。所以可以使用ended事件来切换当前播放的音乐。由于要切换歌曲,绑定事件等。所以要对audio元素绑定ended事件,所触发的函数为_playDefaultMusic,但是这个函数之前写的需要传递一个当前的索引值。目前我想有两种方案,一是在audio元素上绑定一个自定义特性index来表示索引;另一个是不用传递索引,函数改为无参数,索引值改为由data保存(后期修改为vuex控制索引状态)。显然无论从代码简洁、资源控制还是后期的扩展上都是第二种方式较好。

操作反馈wx.showModal

wx.showModal可以显示模态弹窗,我们可以把wx.showModal与wx.showToast相结合使用。

修改代码如下:

var postsData = require('../../../data/posts-data.js')

Page({
  data: {

  },
  onLoad: function (option) {
    var postId = option.id; // 这里的id对应的是url参数上的id
    // 把postId设置到数据集里,这样就能够全局获取
    this.data.currentPostId = postId;
    var postData = postsData.postList[postId];
    this.setData({
      postData
    });

    // 从缓存中获取数据
    var postsCollected = wx.getStorageSync('posts_collected');
    // 判断数据是否不为空
    if (postsCollected) {
      // 不为空就拿出与postId对应的下标值
      var postsCollected = postsCollected[postId];
      // 并将值更新到数据绑定里
      this.setData({
        collected: postsCollected
      });
    } else {
      // 如果为空就赋值一个空对象
      var postsCollected = {}
      // 并把与postId对应的下标中的值设置为false
      postsCollected[postId] = false;
      // 更新到缓存里
      wx.setStorageSync('posts_collected', postsCollected);
    }
  },

  // 点击事件方法
  onCollectionTap: function (event) {
    // 获取缓存数据
    var postsCollected = wx.getStorageSync('posts_collected');
    // 获取postId
    var postCollected = postsCollected[this.data.currentPostId];
    // 收藏变成未收藏,未收藏变成收藏
    postCollected = !postCollected;
    postsCollected[this.data.currentPostId] = postCollected;

    // 自定义函数也需要使用this来访问
    this.showModal(postsCollected, postCollected);
  },

  showModal: function (postsCollected, postCollected){
    // 把this指代的Page对象先存储起来
    var that = this;
    wx.showModal({
      title: '收藏',
      content: postCollected ? '是否收藏该文章?' :'是否取消收藏该文章?',
      showCancel: 'true',
      cancelText: '取消',
      cancelColor: '#333',
      confirmText: '确认',
      confirmColor: '#405f80',
      success:function(res){
        if(res.confirm){
          // 更新文章是否收藏的缓存值
          wx.setStorageSync('posts_collected', postsCollected);
          // 更新数据绑定变量,从而实现切换图片
          that.setData({
            collected: postCollected
          });
          that.showToast(postsCollected, postCollected);
        }
      },
    });
  },

  showToast: function (postsCollected, postCollected){
    wx.showToast({
      // 使用三元表达式来判断状态
      title: postCollected ? '收藏成功!' : '取消成功!',
      // 设置图标停留的时间,单位是毫秒
      duration: 1000,
      // icon可以设置图标,默认就是success
      icon: "success",
    });
  },
})

运行效果:
收藏:
分分快三计划 5
分分快三计划 6

取消收藏:
分分快三计划 7
分分快三计划 8

注:在实际开发中这种成本低的操作是不需要把交互反馈做得这么麻烦的,一般只使用showToast即可。所谓成本指的是误操作带来的损失,如果成本低的操作交互反馈太麻烦的话,会感觉体验不好。


在微信开发中,写过的一个简单的音乐播放组件,记录下。

继续完善音乐播放

以上我们简单介绍了一下关于页面状态与全局变量以及应用程序生命周期,现在就可以使用全局变量来继续完善音乐播放的功能了:

app.js代码如下:

App({

  globalData:{
    g_isPlayingMusic:false
  },

});

post-detail.js代码如下:

var postsData = require('../../../data/posts-data.js')
// 获得全局的app对象
var app = getApp();

Page({
  data: {
    isPlayingMusic: false
  },

  onLoad: function (option) {

        ......以上代码省略......

    // 当全局变量的状态为播放时,也把页面的状态设置为teur 
    if (app.globalData.g_isPlayingMusic){
      this.setData({
        isPlayingMusic: true
      });
    }

    this.setMusicMonitor();
  },

  // 把监听音乐的事件代码提取出来
  setMusicMonitor:function(){
    var that = this;
    // 当音乐播放时将页面以及全局状态状态改为true
    wx.onBackgroundAudioPlay(function () {
      that.setData({
        isPlayingMusic: true
      });
      app.globalData.g_isPlayingMusic = true;
    });

    // 当音乐暂停时将页面以及全局状态都改为false
    wx.onBackgroundAudioPause(function () {
      that.setData({
        isPlayingMusic: false
      });
      app.globalData.g_isPlayingMusic = false;
    });
  },

              ......以下代码省略......

})

使用全局变量记录状态后,就不会出现之前的问题了,这时我们就可以在页面加载时根据全局变量来设置页面变量的状态。


属性

属性名 类型 默认值 说明
music String   传入的音乐资源地址
musicStyle String (随便写了个) 音乐组件的样式
rotate Boolean true 播放时是否有旋转效果
iconOn String (随便写了个) 音乐播放时的icon地址
iconOff String (随便写了个) 音乐暂停时的icon地址

音乐播放最终章

除了以上的问题之外,我还找到了三个问题,第一个问题是当我们点击音乐播放时,背景图片会切换,但是我们只需要切换当前页面的图片,别的页面不应该也跟着切换,而这个问题就是别的页面也会跟着切换图片,这个问题我们可以通过把页面id存储到全局变量里,根据id来决定是哪个页面才会切换图片,这样就可以解决这个问题。

第二个问题是当我们关闭音乐播放器时,图片不会切换,依旧停留在播放状态,这个问题我们可以通过wx.onBackgroundAudioStop来解决。

第三个问题就是当我们在文章A里播放了音乐,然后到文章B上点击音乐播放器的总控开关时,文章B的图片也会跟着切换,解决这个问题稍微有点麻烦,因为要考虑到点击图标开关和点击音乐播放器开关两种情况,以及回到原本的页面时还需切换图片,我的解决思路是使用一个全局变量记录上一个页面,也就是原始页面的id,通过这个id来决定切不切换图片。

app.js文件内容如下:

App({

  globalData:{
    g_isPlayingMusic:false,
    g_currentMusicPostId:"",
    g_beforeMusicPostId: ""
  },

});

以下是修改后的post-detail.js文件内容:

var postsData = require('../../../data/posts-data.js')
// 获得全局的app对象
var app = getApp();

Page({
  data: {
    isPlayingMusic: false
  },

  onLoad: function (option) {
    var postId = option.id; // 这里的id对应的是url参数上的id
    // 把postId设置到数据集里,这样就能够全局获取
    this.data.currentPostId = postId;
    var postData = postsData.postList[postId];
    this.setData({
      postData
    });

    // 从缓存中获取数据
    var postsCollected = wx.getStorageSync('posts_collected');
    // 判断数据是否不为空
    if (postsCollected) {
      // 不为空就拿出与postId对应的下标值
      var postsCollected = postsCollected[postId];
      // 并将值更新到数据绑定里
      this.setData({
        collected: postsCollected
      });
    } else {
      // 如果为空就赋值一个空对象
      var postsCollected = {}
      // 并把与postId对应的下标中的值设置为false
      postsCollected[postId] = false;
      // 更新到缓存里
      wx.setStorageSync('posts_collected', postsCollected);
    }

    // 音乐在播放时改变状态为true 
    if (app.globalData.g_isPlayingMusic && app.globalData.g_currentMusicPostId === postId){
      this.setData({
        isPlayingMusic: true
      });
    }

    this.setMusicMonitor();
  },

  // 把监听音乐的事件代码提取出来
  setMusicMonitor:function(){
    var that = this;

    // 监听音乐播放
    wx.onBackgroundAudioPlay(function () {
      // 在原始页面触发播放事件时,切换页面图片,并且记录当前文章的id
      if (app.globalData.g_beforeMusicPostId != "" && app.globalData.g_beforeMusicPostId === that.data.currentPostId){
        that.setData({
          isPlayingMusic: true
        });
        app.globalData.g_isPlayingMusic = true;
        app.globalData.g_currentMusicPostId = that.data.currentPostId;
        app.globalData.g_beforeMusicPostId = app.globalData.g_beforeMusicPostId;

      // 产生原始页面时,或者图标开关被点击时,切换页面图片,并且记录当前文章的id
      } else if (app.globalData.g_beforeMusicPostId == "" || that.data.isPlayingMusic){
        that.setData({
          isPlayingMusic: true
        });
        app.globalData.g_isPlayingMusic = true;
        app.globalData.g_currentMusicPostId = that.data.currentPostId;
        app.globalData.g_beforeMusicPostId = that.data.currentPostId;

      // 在非原始页面触发播放事件时,不切换该页面的图片
      } else{
        that.setData({
          isPlayingMusic: false
        });
        app.globalData.g_isPlayingMusic = true;
        app.globalData.g_currentMusicPostId = app.globalData.g_beforeMusicPostId;
      }
    });

    // 当音乐暂停时将页面以及全局状态都改为false,并且把当前文章的id清空
    wx.onBackgroundAudioPause(function () {
      that.setData({
        isPlayingMusic: false
      });
      app.globalData.g_isPlayingMusic = false;
      app.globalData.g_currentMusicPostId = "";
    });

    // 当音乐停止时将页面以及全局状态都改为false,并且把当前文章以及上一篇文章的id清空
    wx.onBackgroundAudioStop(function(){
      that.setData({
        isPlayingMusic: false
      });
      app.globalData.g_isPlayingMusic = false;
      app.globalData.g_currentMusicPostId = "";
      app.globalData.g_beforeMusicPostId = "";
    });
  },

  //点击事件方法
  onCollectionTap: function (event) {
    // 获取缓存数据
    var postsCollected = wx.getStorageSync('posts_collected');
    // 获取postId
    var postCollected = postsCollected[this.data.currentPostId];
    // 收藏变成未收藏,未收藏变成收藏
    postCollected = !postCollected;
    postsCollected[this.data.currentPostId] = postCollected;

    // 自定义函数也需要使用this来访问
    this.showModal(postsCollected, postCollected);
  },

  showModal: function (postsCollected, postCollected) {
    // 把this指代的Page对象先存储起来
    var that = this;
    wx.showModal({
      title: '收藏',
      content: postCollected ? '是否收藏该文章?' : '是否取消收藏该文章?',
      showCancel: 'true',
      cancelText: '取消',
      cancelColor: '#333',
      confirmText: '确认',
      confirmColor: '#405f80',
      success: function (res) {
        if (res.confirm) {
          // 更新文章是否收藏的缓存值
          wx.setStorageSync('posts_collected', postsCollected);
          // 更新数据绑定变量,从而实现切换图片
          that.setData({
            collected: postCollected
          });
          that.showToast(postsCollected, postCollected);
        }
      },
    });
  },

  showToast: function (postsCollected, postCollected) {
    wx.showToast({
      // 使用三元表达式来判断状态
      title: postCollected ? '收藏成功!' : '取消成功!',
      // 设置图标停留的时间,单位是毫秒
      duration: 1000,
      // icon可以设置图标,默认就是success
      icon: "success",
    });
  },

  onShareTap: function (event) {
    var itemList = [
      "分享给微信好友",
      "分享到朋友圈",
      "分享到QQ",
      "分享到微博",
    ]
    wx.showActionSheet({
      itemList: itemList,
      itemColor: "#405f80",
      success: function (res) {
        //res.cancel  用户是否点击了取消按钮
        //res.tapIndex  数组元素的索引
        wx.showModal({
          title: itemList[res.tapIndex],
          content: '现在无法实现分享功能,什么时候能支持还未知',
        })
      },
    })
  },

  onMusicTap: function (event) {
    var currentPostId = this.data.currentPostId;
    var postData = postsData.postList[currentPostId];

    // 使用变量来记录音乐的状态
    var isPlayingMusic = this.data.isPlayingMusic;
    if (isPlayingMusic) {
      // 暂停音乐
      wx.pauseBackgroundAudio();
      this.setData({
        isPlayingMusic : false
      });

    } else {
      wx.playBackgroundAudio({
        // 流媒体文件的URL,目前支持的格式有 m4a, aac, mp3, wav
        dataUrl: postData.music.url,
        // 音乐标题
        title: postData.music.title,
        // 音乐封面URL
        coverImgUrl: postData.music.coverImg,
      });
      this.setData({
        isPlayingMusic: true
      });
    }
  },
})

以上就算是完成了一个基本的音乐播放效果,这个文章详情页面也就是算是完成了,虽然我感觉可能还存在一些问题,毕竟没有完美的代码,如果后续出现问题后再进行修复。我个人觉得开发项目应该先开发出一个能够上线运行的原型,在运营的过程中再逐步去修复bug,迭代版本。


使用

你可以

  • 通过阅读本文,根据自身实际情况写一个
  • 或者,直接凑合用

同步异步方法对比

我们把之前的onCollectionTap方法中的同步获取缓存的方法改为异步获取缓存的方法,以此来演示同步与异步方法之间的区别,修改代码如下:

onCollectionTap: function (event) {
    // 把当前this指代的当前对象先存储起来
    var that = this;
    // 异步获取缓存数据
    var postsCollected = wx.getStorage({
      key: 'posts_collected',
      success: function (res) {
        var postsCollected = res.data;
        // 获取postId
        var postCollected = postsCollected[that.data.currentPostId];
        // 收藏变成未收藏,未收藏变成收藏
        postCollected = !postCollected;
        postsCollected[that.data.currentPostId] = postCollected;

        that.showModal(postsCollected, postCollected);
      },
    });
  },

以上就是异步方法的实现方式,与同步方法的主要区别在于,同步会等待wx.getStorageSync('posts_collected');方法执行完之后才会往下执行,所以如果当获取缓存得很慢的时候,操作界面就会卡在那。而异步则不会,异步获取缓存数据的时候,代码还会继续往下执行,异步获取完成之后再执行success里的方法。

注:通常情况下,在小程序中必须要使用异步方法的情况比较少,建议如果对异步方法不熟悉的话,最好不要使用异步方法,不然不仅会让你的代码变得难以阅读,而且很容易埋下一些隐藏bug,或者难以解决的错误。至于使用异步还是同步,需要根据业务来决定,当可以使用同步的情况下,优先使用同步。


音乐旋转效果

音乐播放时的旋转效果,是用css动画实现的,wxss文件如下:

.music {
  position: absolute;
  z-index: 99;
  -webkit-animation-iteration-count: infinite;
}
/* 旋转class */
.music-on {
  animation: music-rotate 4s linear infinite;
}
/* 旋转动画 */
@keyframes music-rotate {
  0% {
    transform: rotateZ(0deg);
  }

  100% {
    transform: rotateZ(360deg);
  }
}

当 rotate 为true时,使 musicClass 的值为 music-on,就能实现旋转了。

当然, musicClass 需要用 this.setData 的方式来切换值。

爆丑照:

分分快三计划 9

交互反馈wx.showActionSheet

​showActionSheet可以显示操作菜单,以下使用实际示例演示一下showActionSheet的使用:

1.在分享图标上加上一个点击事件:

<image catchtap='onShareTap' class='share-img' src='icon/share.png'></image>

2.事件代码如下:

onShareTap:function(event){
    var itemList = [
      "分享给微信好友",
      "分享到朋友圈",
      "分享到QQ",
      "分享到微博",
    ]
    wx.showActionSheet({
      itemList: itemList,  // 按钮的文字数组,数组长度最大为6个
      itemColor:"#405f80",  // 设置字体颜色
      success:function(res){
        //res.tapIndex  用户点击的按钮,从上到下的顺序,从0开始
        wx.showModal({
          title: itemList[res.tapIndex],
          content: '现在无法实现分享功能,什么时候能支持还未知',
        })
      }, 
    })
  },

运行效果:
分分快三计划 10
分分快三计划 11

注:到目前为止,微信小程序官方还尚未提供能够将小程序直接分享到朋友圈的相关api,不过有一些曲线救国的方案,可以参考以下两篇文章,或者使用百度、谷歌等搜索引擎搜索解决方案:



代码

properties: {
    // 音乐路径
    music: {
      type: String,
      value: '',
      observer: function (newVal) {
        this._initMusic(newVal)
      }
    },
    // 样式
    musicStyle: {
      type: String,
      value: 'position: absolute; right: 20rpx; top: 20rpx; width: 100rpx; height: 100rpx;'
    },
    // 播放时是否有旋转效果
    rotate: {
      type: Boolean,
      value: true
    },
    // 播放时的icon路径
    iconOn: {
      type: String,
      value: '/resources/img/music-on.png' // 请填写默认的图片地址
    },
    // 暂停时的icon路径
    iconOff: {
      type: String,
      value: '/resources/img/music-off.png' // 请填写默认的图片地址
    }
  }

真机如何清除缓存与template的路径问题

在小程序中的缓存数据都是没有时效期的,不主动清除的话就会一直存在,在模拟器上我们可以点击工具提供的清除缓存按钮清除缓存数据:
分分快三计划 12

但是如果在真机上,则需要自己手动编写一个清除缓存的按钮,需要使用到wx.clearStorageSync()或wx.clearStorage()方法,前者是同步通清理本地数据缓存,后者则是异步清理本地数据缓存。当点击这个按钮的时候就能触发一个点击事件去执行这个清除缓存的方法。

template的路径问题:

我们都知道template文件中的代码目的是为了给其他页面文件复用的,所以template代码中的所包含的文件路径不要写相对路径,写绝对路径比较好,因为如果文件A引用了template文件中的代码,但是文件A和template文件并不是同一级的,那么这时候如果使用相对路径就会有问题。

手动切换

手动点击时,用取反的逻辑控制音乐的播放和暂停:

_switch: function () {
  // 如果是播放就停止   
  if (this.data.audioStatus) {
    this.setData({
      audioStatus: 0,
      icon: this.data.iconOff,
      musicClass: ''
    })
    this.data.audioCtx.pause()
  // 如果是停止就播放 
  } else {
    this.setData({
      audioStatus: 1,
      icon: this.data.iconOn,
      musicClass: 'music-on'
    })
    this.data.audioCtx.play()
  }
}

配置全局导航栏颜色

上面把基本的静态页面做完了,但是导航栏颜色还不太对,而且少了个标识文字,所以现在就来把这个位置的样式完善好。
app.json文件内容:

{
  "pages": [
    "pages/welcome/welcome",
    "pages/posts/post",
    "pages/posts/post-detail/post-detail"
  ],
  "window": {
    "navigationBarBackgroundColor": "#405f80"
  }
}

welcome.json文件内容:

{
  "navigationBarBackgroundColor": "#b3d4db"
}

post.json文件内容:

{
    "navigationBarTitleText": "文与字"
}

post-detail.json文件内容:

{
  "navigationBarTitleText": "阅读"
}

完成效果:
分分快三计划 13


其它情况

同时,还要对下列情况做处理:

  • 分享时,进入选好友界面、音乐停止,分享回来后,音乐没有继续播放
  • 从此页面跳转到下一个页面时,音乐还在继续
  • 从此页面撤回到上一个页面时,音乐还在继续

解决的方法,是在组件的methods中又写了两个方法:

// 写在组件的methods中:

// 在引用组件页面的onShow()中调用
//  否则,如果当发生分享页面行为并返回时,音乐不会自动播放
onShow: function () {
  if (this.data.music && this.data.audioStatus) {
    this.data.audioCtx.play()
  }
},

// 在引用组件页面的onHide()中调用
//  否则,在跳转到下一个页面后,音乐还在继续
onHide: function () {
  if (this.data.music && this.data.audioStatus) {
    this.data.audioCtx.pause()
  }
  this.setData({
    animationData: {}
  })
}

这两个方法分别在页面中的 onShow 和 onHide 中调用,调用方式就是父组件获取到子组件实例对象:

例如,给<music>组件加id为"music-componet",调用时就是:

// 写在调用页面中

onShow: function () {
    this.selectComponent('#music-component').onShow()
},

onHide: function () {
    this.selectComponent('#music-component').onHide()
}

最后,在组件的detached中也调用一下 onHide 方法:

// 页面关闭时销毁音乐
detached() {
    this.onHide()
}

笔记内容:构建新闻详情页面
笔记日期:2018-01-09

music

音乐播放组件。

先静后动,先构建新闻详情页面样式

编写post-detail.wxml代码:

<view class='container'>
  <image src='post/sls.jpg' class='head-iamge'></image>
  <image class='audio' src='music/music-start.png'></image>
  <view class='author-date'>
    <image src='vatar/2.png' class='avatar' ></image>
    <text class='author'>zero</text>
    <text class='const-text'>发表于</text>
    <text class='date'>3天前</text>
  </view>
  <text class='title'>审美的进化机制</text>
  <view class='tool'>
    <view class='circle-img'>
      <image src='icon/collection.png'></image>
      <image class='share-img' src='icon/share.png'></image>
    </view>
    <view class='horizon'></view>
  </view>
  <text class='detail'>2017年读的书中,有一本书是《纽约时报》年度十佳(2017 Ten Best Books),即:《美的进化——达尔文被遗忘的择偶理论如何塑造动物世界乃至我们》(The Evolution of Beauty: How Darwin’s Forgotten Theory of Mate Choice Shapes the Animal World — and Us)。时报网站刊登了英文版评介的译文:如果一本科学书籍能做到有颠覆性,倡导女权主义,还能改变我们看待自己身体的方式,但同时主要还是关于鸟类的,那就是这本书了。普鲁姆是一位鸟类学家,他为达尔文的另一个基本上被忽略了的雌雄淘汰理论进行了辩护。</text>
</view>

post-detail.wxss代码:

.container {
  display: flex;
  flex-direction: column;
}

.head-iamge {
  width: 750rpx;
  height: 460rpx;
}

.author-date {
  flex-direction: row;
  margin-left: 30rpx;
  margin-top: 20rpx;
}

.avatar {
  height: 64rpx;
  width: 64rpx;
  vertical-align: middle;
}

.author {
  font-size: 30rpx;
  font-weight: 300;
  margin-left: 20rpx;
  vertical-align: middle;
  color: #666;
}

.const-text {
  font-size: 24rpx;
  color: #999;
  margin-left: 20rpx;
}

.date {
  font-size: 24rpx;
  margin-left: 30rpx;
  color: #999;
  vertical-align: middle;
}

.title {
  margin-left: 40rpx;
  font-size: 36rpx;
  font-weight: 700;
  margin-top: 30rpx;
  letter-spacing: 2px;
  color: #4b556c;
}

.tool {
  margin-top: 20rpx;
}

.circle-img {
  float: right;
  margin-right: 40rpx;
  vertical-align: middle;
}
.circle-img image{
  width: 90rpx;
  height: 90rpx;
}
.share-img {
  margin-left: 30rpx;
}

.horizon {
  width: 660rpx;
  height: 1px;
  background-color: #e5e5e5;
  vertical-align: middle;
  position: relative;
  top: 46rpx;
  margin: 0 auto;
  z-index: -99;
}

.detail{
  color: #666;
  margin-left: 30rpx;
  margin-top: 20rpx;
  margin-right: 30rpx;
  line-height: 44rpx;
  letter-spacing: 2px;
}

.audio{
  width: 102rpx;
  height: 110rpx;
  position: absolute;
  left: 50%;
  margin-left: -51rpx;
  top: 180rpx;
  opacity: 0.6;  // 设置透明度为0.6
}

app.wxss代码:

text{
  font-family: MicroSoft Yahei;
  font-size: 24rpx;
}

其多个页面:营造音讯实际情况页面 分分快三计划。完成效果:
分分快三计划 14


使用缓存实现文章收藏功能

实现这个功能我们需要使用到两个图标进行状态的轮换,由于小程序中没有document,我们需要使用if判断来实现这个功能。

post-detail.wxml代码如下:

<!-- 判断collected的值是否为真,是的话就显示src中指定的图片 -->
<image wx:if="{{collected}}" catchtap='onCollectionTap' src='icon/collection.png'></image>
<!-- 否则显示这张图片 -->
<image wx:else catchtap='onCollectionTap' src='icon/collection-anti.png'></image>

post-detail.js代码如下:

var postsData = require('../../../data/posts-data.js')

Page({
  data:{

  },
  onLoad: function (option) {
    var postId = option.id; // 这里的id对应的是url参数上的id
    // 把postId设置到数据集里,这样就能够全局获取
    this.data.currentPostId = postId;
    var postData = postsData.postList[postId];
    this.setData({
      postData
    });

    // 从缓存中获取数据,键值对形式的
    var postsCollected = wx.getStorageSync('posts_collected');
    // 判断数据是否不为空
    if (postsCollected) {
      // 不为空就拿出与postId对应的下标值
      var postsCollected = postsCollected[postId];
      // 并将值更新到数据绑定里
      this.setData({
        collected: postsCollected
      });
    } else {
      // 如果为空就赋值一个空对象
      var postsCollected = {}
      // 并把与postId对应的下标中的值设置为false
      postsCollected[postId] = false;
      // 更新到缓存里
      wx.setStorageSync('posts_collected', postsCollected);
    }
  },

  // 点击事件方法
  onCollectionTap: function (event) {
    // 获取缓存数据
    var postsCollected = wx.getStorageSync('posts_collected');
    // 从数据集中获取postId
    var postCollected = postsCollected[this.data.currentPostId];
    // 收藏变成未收藏,未收藏变成收藏
    postCollected = !postCollected;
    postsCollected[this.data.currentPostId] = postCollected;
    // 更新文章是否收藏的缓存值
    wx.setStorageSync('posts_collected', postsCollected);
    // 更新数据绑定变量,从而实现切换图片
    this.setData({
      collected: postCollected
    });
  },

})

运行效果:

未收藏状态:
分分快三计划 15

收藏状态:
分分快三计划 16


应用程序生命周期

文章详情页中的音乐播放功能看起来基本是没什么问题了,不过这也仅限于文章详情这一个页面内而已,如果我点击了播放音乐,然后返回到上一级页面,再点击进入文章详情页的话,页面的状态就会是初始化时的状态,这时音乐图片就不会自动切换到播放状态的图片。这是因为我们的状态代码写在js文件的Page对象里,会受到页面生命周期的影响,当我们返回上一级页面,再点击进入文章详情页时,Page对象会被加载,页面代码就会被重新执行一遍,所以音乐图标的状态就会是初始时的状态。

解决这个问题我们需要用到全局变量来保存状态,全局变量不会受页面生命周期的影响,而且在任何页面中都可以获取到全局变量的值。在小程序中全局变量需要写在app.js文件中,该文件中的代码需要写在App对象里,就像我们的页面代码需要写在Page中一样,Page代表的是一个页面,而App对象则是代表着整个应用程序,该对象的生命周期也就是应用程序的生命周期。以下是该对象的生命周期方法:

App({

  /**
   * 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
   */
  onLaunch: function () {

  },

  /**
   * 当小程序启动,或从后台进入前台显示,会触发 onShow
   */
  onShow: function (options) {

  },

  /**
   * 当小程序从前台进入后台,会触发 onHide
   */
  onHide: function () {

  },

  /**
   * 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
   */
  onError: function (msg) {

  }
})

切换文章图片

以上我们完成了简单的音乐播放和暂停以及音乐图标的切换,而且也说明了coverImg中引入的图片只有在真机上,进入音乐界面后才能够看到,但是我们也可以将coverImg引入的图片显示在文章详情页上,只需要做一个简单的图片切换即可:

<image src='{{isPlayingMusic ? postData.music.coverImg : postData.headImgSrc}}' class='head-iamge'></image>

运行效果:
播放:
分分快三计划 17

暂停:
分分快三计划 18


使用数据填充新闻详情页面

首先是postId的获得,因为不同的postId需要输出不同的文章详情数据:
1.在post.js的navigateTo方法的url参数中,加上一个id参数:

onPostTap: function(event){
    // 从事件源中获取postId数据
    var postId = event.currentTarget.dataset.postid;
    //console.log("on post id is "   postId);
    wx.navigateTo({
      url: 'post-detail/post-detail?id='   postId,  // 通过url传递postId
    })
  },

2.然后在post-detail.js中,写一个onLoad函数把id获得到手:

Page({
  onLoad:function(option){
    var postId = option.id; // 这里的id对应的是url参数上的id
    console.log(postId);
  }
})

3.编译之后,点击不同的文章,看看控制台中是否有输出相应的postId,有输出的话就证明获得成功。

完成以上操作后,就可以把新闻详情页面的数据放进数据文件中,然后进行数据绑定:
post-data.js文件内容:

// 将数据整合成数组类型
var local_database = [
  {
    date: "Jan 06 2018",
    title: "正是虾肥蟹壮时",
    imgSrc: "post/crab.png",
    avatar: "vatar/1.png",
    content: "“山明水净夜来霜,数树深红出浅黄。试上高楼清入骨,岂如春色嗾人狂。”金秋时节,天高云淡,秋风送爽,气候宜人。秋风秋阳中,硕果坠挂枝头,玉米抚须含笑,高粱引颈高歌,豆荚饱满圆润。",
    reading: "112",
    collection: "96",
    headImgSrc: "post/crab.png",
    author: "zero",
    dataTime:"24小时前",
    detail: "“山明水净夜来霜,数树深红出浅黄。试上高楼清入骨,岂如春色嗾人狂。”金秋时节,天高云淡,秋风送爽,气候宜人。秋风秋阳中,硕果坠挂枝头,玉米抚须含笑,高粱引颈高歌,豆荚饱满圆润。棉桃鼓胀欲裂,水稻灌浆初熟,世间万物经过春的孕育,夏的生长,即将抵达收获的季节。在这瓜果飘香、稻黍起舞的召唤声中,又是一度蟹肥虾壮时。地处黄海之滨的小城,在秋风的抚慰、秋阳的光照下,瞬间也喧嚣起来。任意走进城中的每一个菜市场,在显眼的位置上,冲入耳际的是此起彼伏的吆喝声,映入眼帘的是那些小商小贩们抢占有利地势将一只只塑料箱一字排开的情景。浅箱中,健壮的对虾、竹节虾在水中跳跃,舒展着弯曲的身体;深箱中,一贯横行霸道的螃蟹拥挤在狭小的空间里,相互肆意践踏,有些不甘蜗居的螃蟹,顺着笔直的箱壁艰难地攀爬着,虽经百般努力,终以失败而告终。那些聚集在网兜里的螃蟹,更是不甘寂寞,身体被束缚着无法动弹,便利用可以自由呼吸的嘴巴,于窸窸窣窣中不停地吐着一串串泡沫,以示抗议,也以此证明自己是个活物。特别是那些个头较大的螃蟹,仿佛知道自己的身价不菲,为此,更是气宇轩昂,自以为是。也许,它们是得到垂青和恩宠的一类吧,受到了特别的眷顾,活动的空间相对较大,所以也更加肆无忌惮。只要有人试图靠近,便会举着那两只肥硕的大螯向你示威,仿佛在警告你:别碰我,否则休怪我无礼!",
    postId: 0,
  },
  {
    date: "Jan 03 2018",
    title: "比利·林恩的中场战事",
    imgSrc: "post/bl.png",
    avatar: "vatar/2.png",
    content: "伊拉克战争时期,来自美国德州的19岁技术兵比利·林恩(乔·阿尔文 Joe Alwyn 饰)因为一段偶然拍摄的视频而家喻户晓。那是一次规模不大却激烈非常的遭遇战,战斗中林恩所在的B班班长(范·迪塞尔 Vin Diesel 饰)遭到当地武装分子的伏击和劫持,而林恩为了营救班长不惜铤而走险冲锋陷阵。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/bl.png",
    dataTime: "一天前",
    author: "妮可",
    detail: "伊拉克战争时期,来自美国德州的19岁技术兵比利·林恩(乔·阿尔文 Joe Alwyn 饰)因为一段偶然拍摄的视频而家喻户晓。那是一次规模不大却激烈非常的遭遇战,战斗中林恩所在的B班班长(范·迪塞尔 Vin Diesel 饰)遭到当地武装分子的伏击和劫持,而林恩为了营救班长不惜铤而走险冲锋陷阵。视频公布于世让他成为全美民众所崇拜的英雄,然而却鲜有人理解他和战友们所经历的一切。为了安葬班长,B班得到了短暂的休假,因此他们得以受邀参加一场在德州举行的橄榄球比赛。林恩的姐姐因某事件深感愧疚,她希望弟弟能借此机缘回归普通生活。而周围的经纪人、球迷、大老板、普通民众则对战争、卫国、士兵有着各种各样想当然的理解。球场上的庆典盛大开幕,林恩和战友们的心却愈加沉重与焦躁…… ",
    postId: 1,
  },
  {
    date: "Jan 05 2018",
    title: "肖申克的救赎",
    imgSrc: "post/xs.jpg",
    avatar: "vatar/3.png",
    content: "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/xs.jpg",
    dataTime: "两天前",
    author: "John",
    detail: "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。未过多久,安迪尝试接近囚犯中颇有声望的瑞德(摩根·弗里曼 Morgan Freeman 饰),请求对方帮自己搞来小锤子。以此为契机,二人逐渐熟稔,安迪也仿佛在鱼龙混杂、罪恶横生、黑白混淆的牢狱中找到属于自己的求生之道。他利用自身的专业知识,帮助监狱管理层逃税、洗黑钱,同时凭借与瑞德的交往在×××中间也渐渐受到礼遇。表面看来,他已如瑞德那样对那堵高墙从憎恨转变为处之泰然,但是对自由的渴望仍促使他朝着心中的希望和目标前进。而关于其罪行的真相,似乎更使这一切朝前推进了一步…… ",
    postId: 2,
  },
  {
    date: "Jan 01 2018",
    title: "霸王别姬",
    imgSrc: "post/bj.jpg",
    avatar: "vatar/4.png",
    content: "段小楼(张丰毅)与程蝶衣(张国荣)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/bj.jpg",
    dataTime: "三天前",
    author: "Jack",
    detail: "段小楼(张丰毅)与程蝶衣(张国荣)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。",
    postId: 3,
  },
  {
    date: "Jan 08 2018",
    title: "这个杀手不太冷",
    imgSrc: "post/ss.jpg",
    avatar: "vatar/5.png",
    content: "里昂(让·雷诺饰)是名孤独的×××,受人雇佣。一天,邻居家小姑娘马蒂尔达(纳塔丽·波特曼饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包×××而遭恶警(加里·奥德曼饰)杀害全家的惩罚。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/ss.jpg",
    dataTime: "四天前",
    author: "Bill",
    detail: "里昂(让·雷诺饰)是名孤独的×××,受人雇佣。一天,邻居家小姑娘马蒂尔达(纳塔丽·波特曼饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包×××而遭恶警(加里·奥德曼饰)杀害全家的惩罚。马蒂尔达得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。女孩想着去×××,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免…… ",
    postId: 4,
  },
  {
    date: "Jan 04 2018",
    title: "阿甘正传",
    imgSrc: "post/ag.jpg",
    avatar: "vatar/1.png",
    content: "阿甘(汤姆·汉克斯 饰)于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75,然而他的妈妈是一个性格坚强的女性,她常常鼓励阿甘“傻人有傻福”,要他自强不息。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/ag.jpg",
    dataTime: "五天前",
    author: "Tony",
    detail: "阿甘(汤姆·汉克斯 饰)于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75,然而他的妈妈是一个性格坚强的女性,她常常鼓励阿甘“傻人有傻福”,要他自强不息。阿甘像普通孩子一样上学,并且认识了一生的朋友和至爱珍妮(罗宾·莱特·潘 饰),在珍妮和妈妈的爱护下,阿甘凭着上帝赐予的“飞毛腿”开始了一生不停的奔跑。阿甘成为橄榄球巨星、越战英雄、乒乓球外交使者、亿万富翁,但是,他始终忘不了珍妮,几次匆匆的相聚和离别,更是加深了阿甘的思念。有一天,阿甘收到珍妮的信,他们终于又要见面了……",
    postId: 5,
  },
]

// 设置一个数据出口
module.exports = {
  // 输出的是一个Array对象
  postList: local_database,
}

post-detail.js文件内容:

var postsData = require('../../../data/posts-data.js')

Page({
  onLoad:function(option){
    var postId = option.id; // 这里的id对应的是url参数上的id
    var postData=postsData.postList[postId];
    this.setData({
        postData
    });
  }
})

post-detail.wxml文件内容:

<view class='container'>
  <image src='{{postData.headImgSrc}}' class='head-iamge'></image>
  <image class='audio' src='music/music-start.png'></image>
  <view class='author-date'>
    <image src='{{postData.avatar}}' class='avatar' ></image>
    <text class='author'>{{postData.author}}</text>
    <text class='const-text'>发表于</text>
    <text class='date'>{{postData.dataTime}}</text>
  </view>
  <text class='title'>{{postData.title}}</text>
  <view class='tool'>
    <view class='circle-img'>
      <image src='icon/collection.png'></image>
      <image class='share-img' src='icon/share.png'></image>
    </view>
    <view class='horizon'></view>
  </view>
  <text class='detail'>{{postData.detail}}</text>
</view>

运行效果:
分分快三计划 19


监听播放事件完善音乐播放

在音乐播放的时候,可以看到会弹出来一个音乐播放的总控开关,我们点击音乐图标的时候能够正常的切换图片,但是点击总控开关的时候不会切换图片,这是因为我们只监听了图标上的事件,没有监听音乐播放、暂停的事件。所以我们还需要完善这点小细节,让点击总控开关的时候也能切换图片,实现这一步需要使用到两个API,onBackgroundAudioPlay以及onBackgroundAudioPause,前者用于监听音乐播放,后者用于监听音乐暂停。

在onLoad生命周期方法中增加以下代码:

    var that = this;
    // 当音乐播放时将isPlayingMusic状态改为true
    wx.onBackgroundAudioPlay(function(){
      that.setData({
        isPlayingMusic: true
      });
    });

    // 当音乐暂停时将isPlayingMusic状态改为false
    wx.onBackgroundAudioPause(function(){
      that.setData({
        isPlayingMusic: false
      });
    })

加入以上代码后就可以实现点击总控开关也能切换图片,而且以上代码也体现出了数据绑定机制的好处,只需要修改数据集中相应数据的值即可,无需每次都去获得节点对象后才能操作相应的数据的值。



交互反馈 wx.showToast

以上我们实现了收藏和未收藏图标的一个轮换功能,但是还缺少了个提示功能,在用户点击收藏时要提示用户收藏成功,再次点击则需要提示用户取消成功。

小程序提供了几个实现交互反馈功能的API,详情参考以下官方文档:

我们需要使用到其中三个API,分别是wx.showToast、wx.showModal、wx.showActionSheet。

其多个页面:营造音讯实际情况页面 分分快三计划。首先来应用wx.showToast这个API,在事件方法中,加入如下代码:

wx.showToast({
      // 使用三元表达式来判断状态
      title: postCollected ? '收藏成功!' :'取消成功!',
      // 设置图标停留的时间,单位是毫秒
      duration: 1000,
      // icon可以设置图标,默认就是success
      icon: "success",
});

运行效果:
分分快三计划 20
分分快三计划 21


从文章列表跳转到新闻详情页(组件自定义属性及获取属性)

在编写从文章列表跳转到新闻详情页的代码之前,先来修改一下之前的页面,之前我们编写了两个模板文件,但是还有两个细节没有完善好,一个是post.wxss中的.post-container样式没有移植到模板文件中,另一个是wxml模板文件中每句数据绑定代码都需要通过item这个子元素去调用属性,显得有点麻烦,我们可以使用一种语法去解决这个问题。

1.首先将post.wxss中的.post-container样式移植到模板文件中

2.解决item重复的问题,在post.wxml中将之前的 template 代码修改为以下内容:

<template is="postItem" data="{{...item}}" />

然后模板文件中的数据绑定代码就不需要重复使用item子元素进行属性的调用了:

<!-- 模板文件需要使用template标签包围 -->
<template name="postItem">
  <view class='post-container'>
      <view class='post-author-date'>
        <image src='{{avatar}}' class='post-author'></image>
        <text class="post-date">{{date}}</text>
      </view>
      <text class='post-title'>{{title}}</text>
      <image class='post-image' src='{{imgSrc}}'></image>
      <text class='post-content'>{{content}}</text>
      <view class='post-like'>
        <image src='../..icon/chat.png' class='post-like-img'></image>
        <text class='post-like-font'>{{reading}}</text>
        <image src='../..icon/view.png' class='post-like-img'></image>
        <text class='post-like-font'>{{collection}}</text>
      </view>
    </view>
</template>

完成以上的修改后,就可以开始编写新闻详情页的代码了:

1.构建目录文件结构:
分分快三计划 22

2.由于我们需要实现点击一个文章列表中的文章就跳转到该文章的详情页面,所以我们还得给每一个文章做一个标识符,不然谁知道你点的是哪篇文章。这个标识符可以写在数据文件中,作为一个属性存在,所以需要在数据文件中为每一个文章数据都加上一个属性,我定义的属性名称是postId:

// 将数据整合成数组类型
var local_database = [
  {
    date: "Jan 06 2018",
    title: "正是虾肥蟹壮时",
    imgSrc: "post/crab.png",
    avatar: "vatar/1.png",
    content: "“山明水净夜来霜,数树深红出浅黄。试上高楼清入骨,岂如春色嗾人狂。”金秋时节,天高云淡,秋风送爽,气候宜人。秋风秋阳中,硕果坠挂枝头,玉米抚须含笑,高粱引颈高歌,豆荚饱满圆润。",
    reading: "112",
    collection: "96",
    postId:0,
  },
  {
    date: "Jan 03 2018",
    title: "比利·林恩的中场战事",
    imgSrc: "post/bl.png",
    avatar: "vatar/2.png",
    content: "伊拉克战争时期,来自美国德州的19岁技术兵比利·林恩(乔·阿尔文 Joe Alwyn 饰)因为一段偶然拍摄的视频而家喻户晓。那是一次规模不大却激烈非常的遭遇战,战斗中林恩所在的B班班长(范·迪塞尔 Vin Diesel 饰)遭到当地武装分子的伏击和劫持,而林恩为了营救班长不惜铤而走险冲锋陷阵。",
    reading: "92",
    collection: "65",
    postId: 1,
  },
  {
    date: "Jan 05 2018",
    title: "肖申克的救赎",
    imgSrc: "post/xs.jpg",
    avatar: "vatar/3.png",
    content: "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。",
    reading: "92",
    collection: "65",
    postId: 2,
  },
  {
    date: "Jan 01 2018",
    title: "霸王别姬",
    imgSrc: "post/bj.jpg",
    avatar: "vatar/4.png",
    content: "段小楼(张丰毅)与程蝶衣(张国荣)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。",
    reading: "92",
    collection: "65",
    postId: 3,
  },
  {
    date: "Jan 08 2018",
    title: "这个杀手不太冷",
    imgSrc: "post/ss.jpg",
    avatar: "vatar/5.png",
    content: "里昂(让·雷诺饰)是名孤独的×××,受人雇佣。一天,邻居家小姑娘马蒂尔达(纳塔丽·波特曼饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包×××而遭恶警(加里·奥德曼饰)杀害全家的惩罚。",
    reading: "92",
    collection: "65",
    postId: 4,
  },
  {
    date: "Jan 04 2018",
    title: "阿甘正传",
    imgSrc: "post/ag.jpg",
    avatar: "vatar/1.png",
    content: "阿甘(汤姆·汉克斯 饰)于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75,然而他的妈妈是一个性格坚强的女性,她常常鼓励阿甘“傻人有傻福”,要他自强不息。",
    reading: "92",
    collection: "65",
    postId: 5,
  },
]

// 设置一个数据出口
module.exports = {
  // 输出的是一个Array对象
  postList: local_database,
}

3.在post.wxml文件中增加一个view标签,把template标签包围起来,因为template标签只是相当于一个占位符,编译之后这个标签是不存在的。然后给这个view标签注册一个tap事件,并且自定义一个属性进行postId的数据绑定:

<!-- 自定义属性必须以 data- 为前缀,后面连接的单词可以自定义 -->
<view catchtap="onPostTap" data-postId="{{item.postId}}">
      <template is="postItem" data="{{...item}}"/>
</view>

4.然后到post.js文件中增加一个事件函数:

onPostTap: function(event){
    // 从事件源中获取postId数据
    var postId = event.currentTarget.dataset.postid;
    console.log("on post id is "   postId);
 },

event是事件源对象,是默认会传入的参数,currentTarget是当前被触发事件的目标对象,dataset是数据集对象,数据要从数据集对象中获取。至于为什么是通过postid获取数据而不是通过data-postId或者postId获取,请参考下图:
分分快三计划 23

如图,可以看到,自定义属性被编译之后的名称并不是原本的名称,所以我们通过dataset数据集对象获取自定义属性的数据时需要以编译后的名称为准。

5.接着就可以开始实现页面跳转了,先在post-detail.wxml中编写一段话:

<text>
  这是文章详情页面
</text>

然后再app.json中加上一段post-detail的信息配置,每当我们新增页面都需要如此进行配置:

"pages/posts/post-detail/post-detail"

回到post.js中,编写页面跳转代码:

  onPostTap: function(event){
    // 从事件源中获取postId数据
    var postId = event.currentTarget.dataset.postid;
    wx.navigateTo({
      url: 'post-detail/post-detail',
    })
  },

完成以上操作编译之后现在点击文章列表中的文章就可以跳转到文章详情页面了:
分分快三计划 24


音乐播放基本实现

以上我们已经完成了文章详情页的大部分内容,现在还剩一个音乐播放功能还未实现,官方也提供了一个audio组件可以实现音乐播放。除了组件之外还有相关的API可以使用,在这里我们使用API来实现音乐播放功能,因为使用API的方式比较方便于自定义。

官方文档:

API:
组件:

我们需要使用到两个API,playBackgroundAudio以及pauseBackgroundAudio。前者是用于音乐的播放,后者是用于音乐的暂停。

先给音乐图标添加一个事件,并且使用三元运算符来判断图标是显示暂停图标还是启动图标:

<image catchtap='onMusicTap' class='audio' src='{{isPlayingMusic ? "music/music-stop.png" : "music/music-start.png"}}'></image>

事件方法实现代码如下:

data: {
    isPlayingMusic: false
},

onMusicTap: function (event) {
    // 获取数据文件中的数据
    var currentPostId = this.data.currentPostId;
    var postData = postsData.postList[currentPostId];

    // 使用变量来记录音乐的状态
    var isPlayingMusic = this.data.isPlayingMusic;
    if (isPlayingMusic) {
      // 暂停音乐
      wx.pauseBackgroundAudio();
      // 改变状态
      this.setData({
        isPlayingMusic : false
      });

    } else {
      wx.playBackgroundAudio({
        // 流媒体文件的URL,目前支持的格式有 m4a, aac, mp3, wav
        dataUrl: postData.music.url,
        // 音乐标题
        title: postData.music.title,
        // 音乐封面URL
        coverImgUrl: postData.music.coverImg,
      })
      // 改变状态
      this.setData({
        isPlayingMusic: true
      });
    }
  },

注:dataUrl只能够是引用流媒体文件,不能够使用本地的音乐文件,coverImgUrl也是如此,因为小程序的大小限制是1M,一个音乐文件都不止1M了,所以只能使用流媒体文件的URL形式引入音乐。coverImgUrl引入的图片只有在真机上才能看到得到。

数据文件内容如下,增加了音乐连接、音乐标题、音乐图片属性:

// 将数据整合成数组类型
var local_database = [
  {
    date: "Jan 06 2018",
    title: "正是虾肥蟹壮时",
    imgSrc: "post/crab.png",
    avatar: "vatar/1.png",
    content: "“山明水净夜来霜,数树深红出浅黄。试上高楼清入骨,岂如春色嗾人狂。”金秋时节,天高云淡,秋风送爽,气候宜人。秋风秋阳中,硕果坠挂枝头,玉米抚须含笑,高粱引颈高歌,豆荚饱满圆润。",
    reading: "112",
    collection: "96",
    headImgSrc: "post/crab.png",
    author: "zero",
    dataTime:"24小时前",
    detail: "“山明水净夜来霜,数树深红出浅黄。试上高楼清入骨,岂如春色嗾人狂。”金秋时节,天高云淡,秋风送爽,气候宜人。秋风秋阳中,硕果坠挂枝头,玉米抚须含笑,高粱引颈高歌,豆荚饱满圆润。棉桃鼓胀欲裂,水稻灌浆初熟,世间万物经过春的孕育,夏的生长,即将抵达收获的季节。在这瓜果飘香、稻黍起舞的召唤声中,又是一度蟹肥虾壮时。地处黄海之滨的小城,在秋风的抚慰、秋阳的光照下,瞬间也喧嚣起来。任意走进城中的每一个菜市场,在显眼的位置上,冲入耳际的是此起彼伏的吆喝声,映入眼帘的是那些小商小贩们抢占有利地势将一只只塑料箱一字排开的情景。浅箱中,健壮的对虾、竹节虾在水中跳跃,舒展着弯曲的身体;深箱中,一贯横行霸道的螃蟹拥挤在狭小的空间里,相互肆意践踏,有些不甘蜗居的螃蟹,顺着笔直的箱壁艰难地攀爬着,虽经百般努力,终以失败而告终。那些聚集在网兜里的螃蟹,更是不甘寂寞,身体被束缚着无法动弹,便利用可以自由呼吸的嘴巴,于窸窸窣窣中不停地吐着一串串泡沫,以示抗议,也以此证明自己是个活物。特别是那些个头较大的螃蟹,仿佛知道自己的身价不菲,为此,更是气宇轩昂,自以为是。也许,它们是得到垂青和恩宠的一类吧,受到了特别的眷顾,活动的空间相对较大,所以也更加肆无忌惮。只要有人试图靠近,便会举着那两只肥硕的大螯向你示威,仿佛在警告你:别碰我,否则休怪我无礼!",
    postId: 0,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100003507bR0gDKBm.m4a?fromtag=38",
      title: "夜夜夜夜-齐秦",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000001TEc6V0kjpVC.jpg?max_age=2592000"
    }
  },
  {
    date: "Jan 03 2018",
    title: "比利·林恩的中场战事",
    imgSrc: "post/bl.png",
    avatar: "vatar/2.png",
    content: "伊拉克战争时期,来自美国德州的19岁技术兵比利·林恩(乔·阿尔文 Joe Alwyn 饰)因为一段偶然拍摄的视频而家喻户晓。那是一次规模不大却激烈非常的遭遇战,战斗中林恩所在的B班班长(范·迪塞尔 Vin Diesel 饰)遭到当地武装分子的伏击和劫持,而林恩为了营救班长不惜铤而走险冲锋陷阵。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/bl.png",
    dataTime: "一天前",
    author: "妮可",
    detail: "伊拉克战争时期,来自美国德州的19岁技术兵比利·林恩(乔·阿尔文 Joe Alwyn 饰)因为一段偶然拍摄的视频而家喻户晓。那是一次规模不大却激烈非常的遭遇战,战斗中林恩所在的B班班长(范·迪塞尔 Vin Diesel 饰)遭到当地武装分子的伏击和劫持,而林恩为了营救班长不惜铤而走险冲锋陷阵。视频公布于世让他成为全美民众所崇拜的英雄,然而却鲜有人理解他和战友们所经历的一切。为了安葬班长,B班得到了短暂的休假,因此他们得以受邀参加一场在德州举行的橄榄球比赛。林恩的姐姐因某事件深感愧疚,她希望弟弟能借此机缘回归普通生活。而周围的经纪人、球迷、大老板、普通民众则对战争、卫国、士兵有着各种各样想当然的理解。球场上的庆典盛大开幕,林恩和战友们的心却愈加沉重与焦躁…… ",
    postId: 1,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100003GdCmG4NkEOR.m4a?fromtag=38",
      title: "鬼迷心窍-李宗盛",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000002xOmp62kqSic.jpg?max_age=2592000"
    }
  },
  {
    date: "Jan 05 2018",
    title: "肖申克的救赎",
    imgSrc: "post/xs.jpg",
    avatar: "vatar/3.png",
    content: "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/xs.jpg",
    dataTime: "两天前",
    author: "John",
    detail: "20世纪40年代末,小有成就的青年银行家安迪(蒂姆·罗宾斯 Tim Robbins 饰)因涉嫌杀害妻子及她的情人而锒铛入狱。在这座名为肖申克的监狱内,希望似乎虚无缥缈,终身监禁的惩罚无疑注定了安迪接下来灰暗绝望的人生。未过多久,安迪尝试接近囚犯中颇有声望的瑞德(摩根·弗里曼 Morgan Freeman 饰),请求对方帮自己搞来小锤子。以此为契机,二人逐渐熟稔,安迪也仿佛在鱼龙混杂、罪恶横生、黑白混淆的牢狱中找到属于自己的求生之道。他利用自身的专业知识,帮助监狱管理层逃税、洗黑钱,同时凭借与瑞德的交往在×××中间也渐渐受到礼遇。表面看来,他已如瑞德那样对那堵高墙从憎恨转变为处之泰然,但是对自由的渴望仍促使他朝着心中的希望和目标前进。而关于其罪行的真相,似乎更使这一切朝前推进了一步…… ",
    postId: 2,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100004HLusI2lLjZy.m4a?fromtag=38",
      title: "女儿情-万晓利",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000004Wv5BO30pPc0.jpg?max_age=2592000"
    }
  },
  {
    date: "Jan 01 2018",
    title: "霸王别姬",
    imgSrc: "post/bj.jpg",
    avatar: "vatar/4.png",
    content: "段小楼(张丰毅)与程蝶衣(张国荣)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/bj.jpg",
    dataTime: "三天前",
    author: "Jack",
    detail: "段小楼(张丰毅)与程蝶衣(张国荣)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。",
    postId: 3,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100002mWVx72p8Ugp.m4a?fromtag=38",
      title: "恋恋风尘-老狼",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000001VaXQX1Z1Imq.jpg?max_age=2592000",
    }
  },
  {
    date: "Jan 08 2018",
    title: "这个杀手不太冷",
    imgSrc: "post/ss.jpg",
    avatar: "vatar/5.png",
    content: "里昂(让·雷诺饰)是名孤独的×××,受人雇佣。一天,邻居家小姑娘马蒂尔达(纳塔丽·波特曼饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包×××而遭恶警(加里·奥德曼饰)杀害全家的惩罚。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/ss.jpg",
    dataTime: "四天前",
    author: "Bill",
    detail: "里昂(让·雷诺饰)是名孤独的×××,受人雇佣。一天,邻居家小姑娘马蒂尔达(纳塔丽·波特曼饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包×××而遭恶警(加里·奥德曼饰)杀害全家的惩罚。马蒂尔达得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。女孩想着去×××,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免…… ",
    postId: 4,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100000Zn0vS4fKKo8.m4a?fromtag=38",
      title: "沉默是金-张国荣",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000003at0mJ2YrR2H.jpg?max_age=2592000"
    }
  },
  {
    date: "Jan 04 2018",
    title: "阿甘正传",
    imgSrc: "post/ag.jpg",
    avatar: "vatar/1.png",
    content: "阿甘(汤姆·汉克斯 饰)于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75,然而他的妈妈是一个性格坚强的女性,她常常鼓励阿甘“傻人有傻福”,要他自强不息。",
    reading: "92",
    collection: "65",
    headImgSrc: "post/ag.jpg",
    dataTime: "五天前",
    author: "Tony",
    detail: "阿甘(汤姆·汉克斯 饰)于二战结束后不久出生在美国南方阿拉巴马州一个闭塞的小镇,他先天弱智,智商只有75,然而他的妈妈是一个性格坚强的女性,她常常鼓励阿甘“傻人有傻福”,要他自强不息。阿甘像普通孩子一样上学,并且认识了一生的朋友和至爱珍妮(罗宾·莱特·潘 饰),在珍妮和妈妈的爱护下,阿甘凭着上帝赐予的“飞毛腿”开始了一生不停的奔跑。阿甘成为橄榄球巨星、越战英雄、乒乓球外交使者、亿万富翁,但是,他始终忘不了珍妮,几次匆匆的相聚和离别,更是加深了阿甘的思念。有一天,阿甘收到珍妮的信,他们终于又要见面了……",
    postId: 5,
    music: {
      url: "http://ws.stream.qqmusic.qq.com/C100002I8eGJ28BI17.m4a?fromtag=38",
      title: "朋友-谭咏麟",
      coverImg: "http://y.gtimg.cn/music/photo_new/T002R150x150M000004eGsCN3SUheO.jpg?max_age=2592000"
    }
  },
]

// 设置一个数据出口
module.exports = {
  // 输出的是一个Array对象
  postList: local_database,
}

运行效果:
播放:
分分快三计划 25

暂停:
分分快三计划 26


本文由分分快三计划发布,转载请注明来源

关键词: 分分快三计划 移动开发 Vue 小程序 微信开发