基础知识

什么是小程序

  1. 2017 年度百度百科十大热词之一
  2. 微信小程序,简称小程序,英文名 Mini Program,是一种不需要下载安装即可使用的应用(张小龙对其的定义是无需安装,用完即走,实际上是需要安装的,只不过小程序的体积特别小,下载速度很快,用户感觉不到下载的过程)
  3. 小程序刚发布的时候要求压缩包的体积不能大于1M,,否则无法通过,在2017年4月做了改进,由原来的1M提升到 2M
  4. 2017年1月9日0点,万众瞩目的微信第一批小程序正式低调上线。

小程序的优点

  1. 同 App 进行互补,提供同 app 类型的功能,比 app 使用方便简洁
  2. 通过扫一扫或者在微信搜索即可下载
  3. 用户使用频率不高,但又不得不用的功能软件,目前看来小程序是首选
  4. 连接线上线下
  5. 开发门槛低, 成本低

小程序开发

相关资料

  1. 官网:https://mp.weixin.qq.com/
  2. 微信开发工具
  3. 下载地址:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=2018315

注册小程序账号

https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E7%94%B3%E8%AF%B7%E5%B8%90%E5%8F%B7

开发小程序储备知识

Flex布局

简介

  1. Flex 是 Flexible Box 的缩写,意为“弹性布局”,用来为盒状模型提供最大的灵活性
  2. 任何一个容器都可以指定为 Flex 布局
  3. display: ‘flex’

flex属性

  • flex-direction:
    • row(默认值):主轴为水平方向,起点在左端
    • row-reverse:主轴为水平方向,起点在右端
    • column:主轴为垂直方向,起点在上沿
    • column-reverse:主轴为垂直方向,起点在下沿

学习地址

http://www.runoob.com/w3cnote/flex-grammar.html

移动端相关知识

物理像素

  1. 屏幕的分辨率
  2. 设备能控制显示的最小单元,可以把物理像素看成是对应的像素点

设备独立像素 & css像素

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用并控制的虚拟像素(比如:CSS 像素,只是在 android 机中 CSS 像素就不叫“CSS像素”了,而是叫“设备独立像素”),然后由相关系统转换为物理像素。

dpr比 & DPI & PPI

  1. dpr:设备像素比,物理像素/设备独立像素 = dpr, 一般以 iphon6 的 dpr 为准 dpr = 2
  2. PPI:一英寸显示屏上的像素点个数
  3. DPI:最早指的是打印机在单位面积上打印的墨点数,墨点越多越清晰

移动端适配方案

viewport适配

  1. 为什么做 viewport 适配

    a) 手机厂商在生产手机的时候大部分手机默认页面宽度为 980px

    b) 手机实际视口宽度都要小于 980px,如: iphone6 为 375px

    c) 开发需求: 需要将 980 的页面完全显示在手机屏幕上且没有滚动条

  2. 实现:

1
<meta name="viewport" content="width=device-width,initial-scale=1.0">

rem适配

  1. 为什么做 rem 适配

    a) 机型太多,不同的机型屏幕大小不一样

    b) 需求: 一套设计稿的内容在不同的机型上呈现的效果一致,根据屏幕大小不同的变化,页面中的内容也相应变化

  2. 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function remRefresh() {
let clientWidth = document.documentElement.clientWidth;
// 将屏幕等分 10 份
let rem = clientWidth / 10;
document.documentElement.style.fontSize = rem + 'px';
document.body.style.fontSize = '12px';
}

window.addEventListener('pageshow', () => {
remRefresh()
})
// 函数防抖
let timeoutId;
window.addEventListener('resize', () => {
timeoutId && clearTimeout(timeoutId);
timeoutId = setTimeout(() =>{
remRefresh()
}, 300)
})
  1. 第三方库实现:lib-flexible + px2rem-loader

小程序特点

概述

  1. 没有 DOM

  2. 组件化开发:具备特定功能效果的代码集合

  3. 体积小,单个压缩包体积不能大于 2M,否则无法上线

  4. 小程序的四个重要的文件

    a) *.js

    b) *.wxml —> view 结构 —-> html

    c) *.wxss —> view 样式 —–> css

    d) *. json —-> view 数据 —–> json 文件

  5. 小程序适配方案:rpx(responsive pixel 响应式像素单位)

    a) 小程序适配单位: rpx

    b) 规定任何屏幕下宽度为 750rpx

    c) 小程序会根据屏幕的宽度不同自动计算 rpx 值的大小

    d) Iphone6 下:1rpx = 1 物理像素 = 0.5px

小程序配置

全局配置: app.json

作用:用于为整个应用进行选项设置

链接https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html

页面配置:页面名称.json

作用:用于为指定的页面进行配置

链接https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html

注意!页面配置的优先级高于全局配置

sitemap配置: sitemap.json

作用:用于被微信搜索爬取页面

链接:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html

小程序框架接口

App

  1. 全局 app.js 中执行App()
  2. 生成当前应用的实例对象
  3. getApp()获取全局应用实例

Page

  1. 页面.js 中执行Page()
  2. 生成当前页面的实例
  3. 通过getCurrentPages获取页面实例

wxml语法

数据绑定

初始化数据

页面.js 的 data 选项中

1
2
3
4
5
Page({
data: {
msg: '初始化测试数据'
}
})

使用数据

  1. 模板结构中使用双大括号 {{msg}}
  2. 注意事项: 小程序中为单项数据流 model —> view
1
<text class="userName"> {{msg}} </text>

修改数据

  1. this.setData({message: ‘修改之后的数据’}, callback)

  2. 特点:

    a) 同步修改: this.data 值被同步修改

    b) 异步更新: 异步将 setData 函数用于将数据从逻辑层发送到视图层(异步)

1
2
3
4
5
onLoad() {
this.setData({
msg: '小新'
})
}

总结

  1. 小程序

    1. data中初始化数据
    2. 修改数据: this.setData()
      • 修改数据的行为始终是同步的
    3. 数据流:
      • 单向: Model —> View
  2. Vue

    1. data中初始化数据

    2. 修改数据: this.key = value

    3. 数据流:

      • Vue是单向数据流: Model —> View

      • Vue中实现了双向数据绑定: v-model

  3. React

    1. state中初始化状态数据

    2. 修改数据: this.setState()

      • 自身钩子函数中(componentDidMount)异步的

      • 非自身的钩子函数中(定时器的回调)同步的

    3. 数据流:

      • 单向: Model —> View

事件绑定

事件分类

  1. 冒泡事件

  2. 非冒泡事件

绑定事件

  1. bind绑定:事件绑定不会阻止冒泡事件向上冒泡(绑定冒泡事件)
  2. catch绑定:事件绑定可以阻止冒泡事件向上冒泡(绑定非冒泡事件)
1
2
3
<view class="goStudy" bindtap="handleParent">
<text catchtap="handleChild">Hello World!</text>
</view>

事件流的三个阶段

  1. 捕获: 从外向内
  2. 执行目标阶段
  3. 冒泡: 从内向外

向事件对象传参

  • 语法:data-key = value
  • 获取:event.target.dataset.key || event.currentTarget.dataset.key
  • event.targetevent.currentTarget 的区别
    • event.target 是触发事件的对象,但不一样是绑定事件的对象,如: 事件委托,冒泡
    • currentTarget 触发时间的对象一定是绑定事件的对象, 没有事件委托

生命周期

对应阶段说明

  1. onLoad(Object query)
  • 页面加载时触发。一个页面只会调用一次可以在 onLoad 的参数中获取打开当前页面路径中的参数
  • 参数:
名称 类型 说明
query Object 打开当前页面路径中的参数
  1. onShow()
  • 页面显示/切入前台时触发
  • 会执行多次
  1. onReady()
  • 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当, 可以和视图层进行交互。
  1. onHide()
  • 页面隐藏/切入后台时触发。 如 wx.navigateTo 或底部 tab 切换到其他页面,小程序切入后台等。
  1. onUnload()
  • 页面卸载时触发。如 wx.redirectTo 或 wx.navigateBack 到其他页面时。

官网图示说明

官网对应地址

https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/page-life-cycle.html

列表渲染

语法说明

  • wx:for='{{arr}}'
  • wx:key='{{唯一值}}'

注意事项

  • 默认的个体:item
  • 默认的下标:index
  • 自定义个体变量名称:wx:for-item='myItem'
  • 自定义下标变量名称:wx:for-index='myIndex'

条件渲染

语法说明

  • wx:if = '条件'
  • wx:elif = '条件'
  • wx:else

wx:if VS hidden

  • hidden 用法:<view hidden='{{true}}'></view>
  • wx:if 等同于 v-if,条件为 false 的时候不加载,条件切换的时候决定元素销毁或者重新加载渲染
  • hidden 等同于 v-show,始终加载元素,条件切换的时候决定元素的显示和隐藏

模版使用

定义模板

引入模版

  1. 引入模板结构: <import src='模板结构相对路径'/>
  2. 引入模板样式: @Import '模板样式路径'

使用模板

小程序API

API使用说明

  1. 小程序提供了很多实用的方法供开发者使用
  2. 小程序全局对象是: wx
  3. 所有的 API 都保存在 wx 对象中

常用API

  1. 界面交互

    • 显示消息提示框: wx.showToast()
    • 显示消息加载框: wx.showLoading()
    • 关闭消息提示框: wx.hideToast()
    • 关闭消息加载框: wx.hideLoading()
  2. 路由跳转

    • wx.navigateTo()
    • wx.redirectTo()
    • wx.switchTab()
  3. 网络请求

    • wx.request()
  4. 本地存储

    • wx.setStorage()
    • wx.setStorageSync()
    • wx.getStorage()
    • wx.getStorageSync()
  5. 媒体

    • wx.getBackgroundAudioManager()
    • wx.playVoice()

前后端交互

  1. 语法: wx.request()

  2. 注意点:

    • 协议必须是https协议

    • 一个接口最多配置20个域名

    • 并发限制上限是10个

    • 开发过程中设置不校验合法域名: 开发工具 —> 右上角详情 —-> 本地设置 —> 不校验

事件委托

  1. 什么是事件委托
    • 将子元素的事件委托(绑定)给父元素
  2. 事件委托的好处
    • 减少绑定的次数
    • 后期新添加的元素也可以享用之前委托的事件
  3. 事件委托的原理
    • 冒泡
  4. 触发事件的是谁
    • 子元素
  5. 如何找到触发事件的对象
    • event.target
  6. currentTarget VS target
    • currentTarget要求绑定事件的元素一定是触发事件的元素
    • target绑定事件的元素不一定是触发事件的元素

本地存储

存入数据

  1. 语法: wx.setStorage() || wx.setStorageSync() || .....

  2. 注意点:

    • 建议存储的数据为json数据

    • 单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB

    • 属于永久存储,同H5的localStorage一样

读取数据

  • wx.getStorage()异步
  • wx.getStorageSync()同步

删除数据

  • wx.removeStorage()异步
  • wx.removeStroageSync()同步

清空数据

  • wx.clearStorage()异步
  • wx.clearStorageSync()同步

定义事件相关

  1. 分类

    • 标准DOM事件
    • 自定义事件
  2. 标准DOM事件

    • 举例: click,input。。。
    • 事件名固定的,事件由浏览器触发
  3. 自定义事件

    • 绑定事件

      • 事件名
      • 事件的回调
      • 订阅方: PubSub.subscribe(事件名,事件的回调)
      • 订阅方是接受数据的一方
    • 触发事件

      • 事件名
      • 提供事件参数对象, 等同于原生事件的event对象
      • 发布方: PubSub.publish(事件名,提供的数据)
      • 发布方是提供数据的一方

页面通信

路由传参

  1. 传参方式
    • 路由地址中 + query 传参数
    • 示例: url?a=123
  2. 获取参数:跳转目标页面的 onLoad 函数中的 options 实参中获取

消息订阅发布

  1. 使用第三方库: pubsub-js
  2. 安装:npm install pubsub-js
  3. 使用:
    • import PubSub from ‘pubsub-js’
    • 订阅消息: PubSub.subscribe('eventName', callback)
    • 发布消息: PubSub.publish('eventName', data)
    • 取消订阅: PubSub.unsubscribe('eventName')

eventChannel事件通道

  1. 订阅事件:wx.navigateTo()跳转的时候在 events 选项中定义事件名及事件对应的回调
  1. 获取事件总线对象
    • 目标页面中通过:实例.getOpenerEventChannel()
    • 示例: const eventChannel = this.getOpenerEventChannel()
  2. 触发事件:eventChannel.emit('事件名', data)

自定义组件

创建组件

  1. 开发工具中右键新建组件
  2. 组件对应的 json 文件中设置: component: true

使用组件

使用组件的页面的 json 文件中注册使用组件

使用npm包

  1. 初始化 package.json:npm init
  2. 勾选允许使用npm
  3. 下载npm包:npm install packageName
  4. 构建npm
    • 开发工具 —> 工具 —> 构建 npm
    • 会将 node_modules 中的包打包到 miniprogram_npm 中

获取用户唯一标识(openId)

官网图解

获取流程

  1. wx.login(),拿到code
  2. 发送 code 给服务器端
  3. 服务器端发送请求携带参数(code, appSecret, appId)给微信服务器获取 openId
  4. appSecret,appId 在小程序首页获取
  5. 服务器获取 openId 后进行加密返回给前端

小程序分包

为什么要分包

  1. 小程序要求压缩包体积不能大于 2M,否则无法发布
  2. 实际开发中小程序体积如果大于 2M 就需要使用分包机制进行发布上传
  3. 分包后可解决 2M 限制,并且能分包加载内容,提高性能
  4. 分包后单个包的体积不能大于 2M
  5. 分包后所有包的体积不能大于 16M

分包形式

  1. 常规分包
  2. 独立分包
  3. 分包预下载

常规分包

  1. 开发者通过在 app.json 中,用 subpackages 字段声明项目分包结构
  2. 特点:
    • 加载小程序的时候先加载主包,当需要访问分包的页面时候才加载分包内容
    • 分包的页面可以访问主包的文件,数据,图片等资源
    • 主包:
      • 主包来源: 除了分包以外的内容都会被打包到主包中
      • 通常放置启动页 /tabBar 页面

独立分包

  1. 设置 independent 为 true
  2. 特点:
    • 独立分包可单独访问分包的内容,不需要下载主包
    • 独立分包不能依赖主包或者其他包的内容
  3. 使用场景
    • 通常某些页面和当前小程序的其他页面关联不大的时候可进行独立分包
    • 如:临时加的广告页 || 活动页

分包预下载

  1. 配置

    • app.json 中设置 preloadRule 选项
    • key(页面路径): {packages: [预下载的包名 || 预下载的包的根路径])}
  2. 特点

    • 在加载当前包的时候可以设置预下载其他的包
    • 缩短用户等待时间,提高用户体验

官网对应地址

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages.html

小程序转发分享

分享实现

  1. Button 组件设置 open-type 为 share
  2. <button open-type='share'></button>

自定义分享内容

生命周期回调中 onShareAppMessage 回调中 return 对象设置自定义内容

小程序支付流程

支付流程官网图解

详细说明

  1. 用户在小程序客服端下单(包含用户及商品信息)

  2. 小程序客户端发送下单支付请求给商家服务器

  3. 商家服务器同微信服务器对接获取唯一标识 openID

  4. 商家服务器根据 openId 生成商户订单(包含商户信息)

  5. 商家服务器发送请求调用统一下单 API 获取预支付订单信息

  6. 商家对预支付信息签名加密后返回给小程序客户端

  7. 用户确认支付(鉴权调起支付)

    • API: wx.requestPayment()
  8. 微信服务器返回支付结果给小程序客户端

  9. 微信服务器推送支付结果给商家服务器端

官网对应地址

https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

实战:TenCloudMusic

主页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<view class="indexContainer">
<!-- 轮播图 -->
<swiper class="banners" indicator-dots="true" indicator-color="ivory" indicator-active-color="#d43c33">
<swiper-item wx:for="{{bannerList}}" wx:key="bannerId">
<image src="{{item.pic}}"></image>
</swiper-item>
</swiper>
<!-- 五个图标导航区域 -->
<view class="navContainer">
<view class="navItem" bindtap="toRecommendSong">
<text class="iconfont icon-meirituijian"></text>
<text>每日推荐</text>
</view>
<view class="navItem">
<text class="iconfont icon-gedan1"></text>
<text>歌单</text>
</view>
<view class="navItem">
<text class="iconfont icon-icon-ranking"></text>
<text>排行榜</text>
</view>
<view class="navItem">
<text class="iconfont icon-diantai"></text>
<text>电台</text>
</view>
<view class="navItem">
<text class="iconfont icon-zhiboguankanliangbofangsheyingshexiangjixianxing"></text>
<text>直播</text>
</view>
</view>
<!-- 推荐歌曲区域 -->
<view class="recommendContainer">
<!-- 头部区 -->
<NavHeader title="推荐歌曲" nav="为你精心推荐"></NavHeader>
<!-- 内容区 -->
<scroll-view class="recommendScroll" enable-flex="true" scroll-x="true">
<view class="scrollItem" wx:for="{{recommendList}}" wx:key="id">
<image src="{{item.picUrl}}"></image>
<text>{{item.name}}</text>
</view>
</scroll-view>
</view>
<!-- 排行榜 -->
<view class="topList">
<!-- 头部区 -->
<NavHeader title="排行榜" nav="热歌风向标"></NavHeader>
<!-- 内容区 -->
<swiper class="topListSwiper" next-margin="50rpx">
<swiper-item wx:for="{{topList}}" wx:key="name">
<view class="swiperItem">
<view class="title">{{item.name}}</view>
<view class="musicItem" wx:for="{{item.tracks}}" wx:key="id" wx:for-item="musicItem">
<image src="{{musicItem.al.picUrl}}"></image>
<text class="count">{{index + 1}}</text>
<text class="musicName">{{musicItem.name}}</text>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
.banners {
width: 100%;
height: 300rpx;
}
.banners image {
width: 100%;
height: 100%;
}
.navContainer {
display: flex;
}
.navItem {
display: flex;
flex-direction: column;
align-items: center;
width: 20%;
}
.navItem .iconfont {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
text-align: center;
line-height: 100rpx;
background: rgb(240, 19, 19);
font-size: 50rpx;
color: #fff;
margin: 20rpx 0;
}
.navItem text {
font-size: 26rpx;
}
.recommendContainer {
padding: 20rpx;
}
.recommendContainer .header {
padding-bottom: 20rpx;
}
.recommendContainer .header .title {
font-size: 32rpx;
line-height: 80rpx;
color: #666;
}
.recommendContainer .header .more {
float: right;
border: 1rpx solid #333;
padding: 10rpx 20rpx;
font-size: 24rpx;
border-radius: 30rpx;
}
.recommendScroll {
display: flex;
height: 300rpx;
}
.scrollItem {
width: 200rpx;
margin-right: 20rpx;
}
.scrollItem image {
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
.scrollItem text {
font-size: 26rpx;
/* 单行文本溢出隐藏 省略号代替*/
/* display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis; */

/* 多行文本溢出隐藏 省略号代替*/
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical; /* 设置对齐模式*/
-webkit-line-clamp: 2; /* 设置多行的行数*/
}
.topList {
padding: 20rpx;
}
.topListSwiper {
height: 400rpx;
}
.swiperItem {
width: 96%;
background: #fbfbfb;
}
.swiperItem .title {
font-size: 30rpx;
line-height: 80rpx;
}
.musicItem {
display: flex;
margin-top: 10rpx;
}
.musicItem image {
width: 80rpx;
height: 80rpx;
border-radius: 6rpx;
}
.musicItem .count {
width: 100rpx;
height: 100rpx;
text-align: center;
line-height: 100rpx;
font-size: 30rpx;
}
.musicItem .musicName {
height: 100rpx;
line-height: 100rpx;
max-width: 400rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 30rpx;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import request from "../../utils/request"
Page({

/**
* 页面的初始数据
*/
data: {
bannerList:[],
recommendList:[],
topList:[]
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: async function() {
let bannerResult = await request('/banner',{type:2},'GET');
let recommendResult = await request('/personalized',{limit:30},'GET');
this.setData({
bannerList: bannerResult.banners,
recommendList: recommendResult.result,
})
let index = 0;
let topDetail = await request('/toplist','','GET');
let resultArray = [];
while (index < 3){
let topId = topDetail.list[index].id
let topListResult = await request('/top/list',{id:topId},'GET');
let topListItem = {
name: topListResult.playlist.name,
tracks: topListResult.playlist.tracks.slice(0,3)
}
resultArray.push(topListItem);
this.setData({
topList: resultArray
})
index++;
}
},

//跳转至每日推荐歌曲页面
toRecommendSong(){
wx.navigateTo({
url:'/songPackage/pages/recommendSong/recommendSong'
})
},
})
1
2
3
4
5
{
"usingComponents": {
"NavHeader": "/components/NavHeader/NavHeader"
}
}

组件

1
2
3
4
5
6
7
<view class="header">
<text class="title">{{title}}</text>
<view>
<text>{{nav}}</text>
<text class="more">查看更多</text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.header {
padding-bottom: 20rpx;
}
.header .title {
font-size: 32rpx;
line-height: 80rpx;
color: #666;
}
.header .more {
float: right;
border: 1rpx solid #333;
padding: 10rpx 20rpx;
font-size: 24rpx;
border-radius: 30rpx;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: '我是title的默认值'
},
nav: {
type: String,
value: '我是nav的默认值'
}
}
})
1
2
3
4
{
"component": true,
"usingComponents": {}
}

登陆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<view class="container">
<view class="wrapper">
<view class="left-top-sign">L O G I N</view>
<view class="welcome">
欢迎回来!
</view>
<view class="input-content">
<view class="input-item">
<text class="tit">手机号码</text>
<input type="text" placeholder="请输入手机号码" id="phone" bindinput="handleInput"/>
</view>
<view class="input-item">
<text class="tit">密码</text>
<input type="password" placeholder="请输入密码" id="password" bindinput="handleInput"/>
</view>
</view>
<button class="confirm-btn" bindtap="login">登录</button>
<view class="forget-section">
忘记密码?
</view>
</view>
<view class="register-section">
还没有账号?
<text >马上注册</text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
.wrapper{
position:relative;
z-index: 90;
padding-bottom: 40rpx;
}
.left-top-sign{
font-size: 120rpx;
color: #f8f8f8;
position:relative;
left: -16rpx;
}
.welcome{
position:relative;
left: 50rpx;
top: -90rpx;
font-size: 46rpx;
color: #555;
}
.input-content{
padding: 0 60rpx;
}
.input-item{
display:flex;
flex-direction: column;
align-items:flex-start;
justify-content: center;
padding: 0 30rpx;
background:#f8f6fc;
height: 120rpx;
border-radius: 4px;
margin-bottom: 50rpx;
}
.input-item:last-child{
margin-bottom: 0;
}
.input-item .tit{
height: 50rpx;
line-height: 56rpx;
font-size: 34rpx;
color: #424446;
}
.input-item input{
height: 60rpx;
font-size: 30rpx;
color: #95989e;
width: 100%;
}
.confirm-btn{
width: 630rpx!important;
height: 76rpx;
line-height: 76rpx;
border-radius: 50rpx;
margin-top: 70rpx;
background: #d43c33;
color: #fff;
font-size: 32rpx;
padding: 0;
}
.confirm-btn2:after{
border-radius: 100px;
}
.forget-section{
font-size: 28rpx;
color: #4399fc;
text-align: center;
margin-top: 40rpx;
}
.register-section{
position:absolute;
left: 0;
bottom: 50rpx;
width: 100%;
font-size: 28rpx;
color: #606266;
text-align: center;
}
.register-section text{
color: #4399fc;
margin-left: 10rpx;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import request from "../../utils/request";

Page({

/**
* 页面的初始数据
*/
data: {
phone:'',
password:''
},

/**
* 生命周期函数--监听页面加载
*/
onLoad() {

},
handleInput(event){
let type = event.currentTarget.id;
this.setData({
[type]:event.detail.value
})
},
//登录
async login(){
let {phone, password} = this.data;
if(!phone){
wx.showToast({
title:'手机号不能为空',
icon:'error'
})
}
//定义正则表达式
let phoneReg = /^1(3|4|5|6|7|8|9)\d{9}$/;
if(!phoneReg.test(phone)){
wx.showToast({
title:'手机号格式错误',
icon:'error'
})
return
}
if(!password){
wx.showToast({
title:'密码不能为空',
icon:'error'
})
}

let loginResult = await request('/login/cellphone',{phone,password,isLogin:true},'GET')
if(loginResult.code === 200){
wx.showToast({
title:'登录成功',
icon:'success'
})
//将用户的信息存储至本地
wx.setStorageSync('userInfo',JSON.stringify(loginResult.profile))
//跳转至个人中心页
wx.reLaunch({
url:'/pages/personal/personal'
})
}else if (loginResult.code === 400){
wx.showToast({
title:'手机号错误',
icon:'error'
})
}else if (loginResult.code === 502){
wx.showToast({
title:'密码错误',
icon:'error'
})
}else {
wx.showToast({
title:'登录失败',
icon:'error'
})
}
}
})
1
2
3
4
{
"usingComponents": {},
"navigationBarTitleText": "登录中心"
}

个人中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<view class="personalContainer">
<view class="user-section">
<image class="bg" src="http://qn.liuwqtech.top/008vxvgGly1h8l0cu5ik5j31930u077v.jpg"></image>
<view class="user-info-box" bindtap="toLogin">
<view class="portrait-box">
<image class="portrait" src='{{userInfo.avatarUrl?userInfo.avatarUrl:"/static/images/personal/missing-face.png"}}'></image>
</view>
<view class="info-box">
<text class="username">{{userInfo.nickname?userInfo.nickname:'游客'}}</text>
</view>
</view>
<view class="vip-card-box">
<image class="card-bg" src="/static/images/personal/vip-card-bg.png" mode=""></image>
<view class="b-btn">
立即开通
</view>
<view class="tit">
<!-- 会员图标-->
<text class="iconfont icon-huiyuan-"></text>
十点云音乐会员
</view>
<!-- <text class="e-m">SDCloudMusic Union</text> -->
<!-- <text class="e-b">开通黑胶会员, 静享极致体验</text> -->
<text class="e-b">开通黑胶会员, 静享极致体验</text>
</view>
</view>

<view
class="cover-container"
bindtouchstart="handleTouchStart"
bindtouchmove="handleTouchMove"
bindtouchend="handleTouchEnd"
style="transform: {{coverTransform}}; transition: {{coverTransition}};"
>
<image class="arc" src="/static/images/personal/arc.png"></image>
<!-- 个人中心导航 -->
<view class="nav-section">
<view class="nav-item" hover-class="common-hover" hover-stay-time="50">
<text class="iconfont icon-xiaoxi"></text>
<text>我的消息</text>
</view>
<view class="nav-item" hover-class="common-hover" hover-stay-time="50">
<text class="iconfont icon-myRecommender"></text>
<text>我的好友</text>
</view>
<view class="nav-item" hover-class="common-hover" hover-stay-time="50">
<text class="iconfont icon-gerenzhuye"></text>
<text>个人主页</text>
</view>
<view class="nav-item" hover-class="common-hover" hover-stay-time="50">
<text class="iconfont icon-gexingzhuangban"></text>
<text>个性装扮</text>
</view>
</view>

<!-- 个人中心列表 -->
<view class="personalContent">
<view class="recentPlayContainer">
<text class="title">最近播放</text>
<!-- 最近播放记录 -->
<scroll-view wx:if="{{recentPlayList.length}}" scroll-x class="recentScroll" enable-flex>
<view class="recentItem" wx:for="{{recentPlayList}}" wx:key="id">
<image src="{{item.song.al.picUrl}}"></image>
</view>
</scroll-view>
<view wx:else class="recentPlayText">暂无播放记录</view>
</view>

<view class="cardList">
<view class="card-item">
<text class="title">我的音乐</text>
<text class="more"> > </text>
</view>
<view class="card-item">
<text class="title">我的收藏</text>
<text class="more"> > </text>
</view>
<view class="card-item">
<text class="title">我的电台</text>
<text class="more"> > </text>
</view>
</view>
</view>
</view>

</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
.personalContainer {
width: 100%;
height: 100%;
}
.personalContainer .user-section {
height: 520rpx;
position: relative;
padding: 100rpx 30rpx 0;
}
.user-section .bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0.7;
filter: blur(1px);
}
.user-info-box{
height: 180rpx;
display:flex;
align-items:center;
position:relative;
z-index: 1;
}
.user-info-box .portrait{
width: 130rpx;
height: 130rpx;
border:5rpx solid #fff;
border-radius: 50%;
}
.user-info-box .username{
font-size: 24;
color: #303133;
margin-left: 20rpx;
}
/* vip-box */
.vip-card-box {
position: relative;
display: flex;
flex-direction: column;
/* background: linear-gradient(left, red, black); */
background: rgba(0, 0, 0, .7);
height: 240rpx;
color: #f7d680;
border-radius: 16rpx 16rpx 0 0;
padding: 20rpx 24rpx;
}
.vip-card-box .card-bg{
position:absolute;
top: 20rpx;
right: 0;
width: 380rpx;
height: 260rpx;
}
.vip-card-box .b-btn{
position: absolute;
right: 20rpx;
top: 16rpx;
width: 132rpx;
height: 40rpx;
text-align: center;
line-height: 40rpx;
font-size: 22rpx;
color: #36343c;
border-radius: 20px;
background: #f9e6af;
z-index: 1;
}
.vip-card-box .b-btn{
position: absolute;
right: 20rpx;
top: 16rpx;
width: 132rpx;
height: 40rpx;
text-align: center;
line-height: 40rpx;
font-size: 22rpx;
color: #36343c;
border-radius: 20px;
/*background: linear-gradient(left, #f9e6af, #ffd465);*/ /*渐变不生效*/
background: #f9e6af;
z-index: 1;
}
.vip-card-box .tit {
font-size: 22rpx;
color: #f7d680;
margin-bottom: 28rpx;
}
.vip-card-box .tit .iconfont{
color: #f6e5a3;
margin-right: 16rpx;
}




.vip-card-box .e-m{
font-size: 34rpx;
margin-top: 10rpx;
}
.vip-card-box .e-b{
font-size: 24rpx;
color: #d8cba9;
margin-top: 10rpx;
}


.cover-container{
margin-top: -150rpx;
padding: 0 30rpx;
position:relative;
background: #f5f5f5;
padding-bottom: 20rpx;
}

.cover-container .arc{
position:absolute;
left: 0;
top: -34rpx;
width: 100%;
height: 36rpx;
}


/* 导航部分 */
.cover-container .nav-section {
display: flex;
background: #fff;
padding: 20rpx 0;
border-radius: 15rpx;
}


.nav-section .nav-item {
width: 25%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
}

.nav-section .nav-item .iconfont {
font-size: 50rpx;
color: #d43c33;
line-height: 70rpx;
}

.nav-section .nav-item text:last-child {
font-size: 22rpx;

}


/* 个人中心列表 */
.personalContent {
background: #fff;
margin-top: 20rpx;
}

/* 最近播放 */
.personalContent .scrollView {
display: flex;
height: 160rpx;
}
.personalContent .recentPlay {
display: flex;
}
.recentPlayContainer .title {
padding-left: 20rpx;
font-size: 26rpx;
color: #333;
line-height: 80rpx;
}
.personalContent .recentPlay image {
width: 160rpx;
height: 160rpx;
margin-left: 20rpx;
border-radius: 20rpx;
}
.cardList {
margin-top: 20rpx;
}
.cardList .card-item{
border-top: 1rpx solid #eee;
height: 80rpx;
line-height: 80rpx;
padding: 10rpx;
font-size: 26rpx;
color: #333;
}
.cardList .card-item .more {
float: right;
}
/* 最近播放记录 */
.recentScroll {
display: flex;
height: 200rpx;
}
.recentItem {
margin-right: 20rpx;
}
.recentScroll image{
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
.recentPlayText {
margin-left: 20rpx;
font-size: 32rpx;
color: #333;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import request from "../../utils/request";

let startY = 0; //手指起始的坐标
let moveY = 0; //手指移动的坐标
let moveDistance = 0; //手指移动的距离

Page({

/**
* 页面的初始数据
*/
data: {
coverTransform: 'translateY(0)',
coverTransition: '',
userInfo:{
userId:''
}, //用户信息
recentPlayList:[] //用户播放记录
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function() {
//读取用户基本信息
let userInfo = wx.getStorageSync('userInfo')
if(userInfo){
this.setData({
userInfo: JSON.parse(userInfo)
})
//获取用户的播放记录
this.getUserRecentPlayList(this.data.userInfo.userId)
}
},
async getUserRecentPlayList(userId){
let recentPlayListData = await request('/user/record',{uid: userId,type:0},'GET')
let index = 0
let recentPlayList = recentPlayListData.allData.splice(0,10).map((item) => {
item.id = index++
return item
})
this.setData({
recentPlayList
})
},
handleTouchStart(event){
this.setData({
coverTransition: ''
})
startY = event.touches[0].clientY;
},
handleTouchMove(event){
moveY = event.touches[0].clientY;
moveDistance = moveY - startY;
if(moveDistance <= 0){
return;
}
if(moveDistance >= 80){
moveDistance = 80;
}
//动态更新coverTransform的状态值
this.setData({
coverTransform: `translateY(${moveDistance}rpx)`
})
},
handleTouchEnd(){
this.setData({
coverTransform: 'translateY(0)',
coverTransition: 'transform 0.5s linear'
})
},
//跳转至login登陆页面的回调
toLogin(){
wx.navigateTo({
url:'/pages/login/login'
})
}
})

搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<view class="searchContainer">
<!-- 头部搜索区域 -->
<view class="header">
<view class="searchInput">
<text class="iconfont icon-search1 searchIcon"></text>
<input type="text" value="{{searchContent}}" placeholder="{{placeholderContent}}" placeholder-class="placeholder" bindinput="handleInput"/>
<text class="clear" bindtap="clearSearchContent" hidden="{{!searchContent}}">X</text>
</view>
<text class="cancle">取消</text>
</view>
<block wx:if="{{searchList.length}}">
<!-- 搜索内容展示区域 -->
<view class="showSearchContent">
<view class="searchContent">搜索内容:{{searchContent}}</view>
<view class="searchList">
<view class="searchItem" wx:for="{{searchList}}" wx:key="id">
<text class="iconfont icon-search1"></text>
<text class="content">{{item.name}}</text>
</view>
</view>
</view>
</block>
<block wx:else>
<!-- 搜索历史记录 -->
<view class="history" wx:if="{{historyList.length}}">
<view class="title">历史</view>
<view class="historyItem" wx:for="{{historyList}}" wx:key="{{item}}">{{item}}</view>
<!-- 删除 -->
<text class="iconfont icon-shanchu delete" bindtap="deleteSearchHistory"></text>
</view>
<!-- 热搜榜 -->
<view class="hotContainer">
<view class="title">热搜榜</view>
<!-- 热搜列表 -->
<view class="hotList">
<view class="hotItem" wx:for="{{hotList}}" wx:key="searchWord">
<text class="order">{{index + 1}}</text>
<text>{{item.searchWord}}</text>
<image class="iconImage" wx:if="{{item.iconUrl}}" src="{{item.iconUrl}}"></image>
</view>
</view>
</view>
</block>

</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
.searchContainer {
padding: 0 20rpx;
}
/* 头部搜索区域 */
.header {
display: flex;
height: 60rpx;
line-height: 60rpx;
padding: 10rpx;
}
.searchInput {
position: relative;
flex: 1;
background: rgba(237, 237, 237, 0.3);
border-radius: 30rpx;
}
.searchIcon {
position: absolute;
left: 15rpx;
}
.searchInput input {
margin-left: 50rpx;
height: 60rpx;
}
.placeholder {
color: #d43c33;
font-size: 28rpx;
margin-left: 20rpx;
}
.clear {
position: absolute;
right: 30rpx;
z-index: 10;
top: 0;
font-size: 28rpx;
}
.cancle {
width: 100rpx;
text-align: center;
font-size: 30rpx;
}

/* 热搜榜 */
.hotContainer {
margin-top: 20rpx;
}
.hotContainer .title {
font-size: 28rpx;
height: 80rpx;
line-height: 80rpx;
border-bottom: 1rpx solid #eee;
}
.hotList {
display: flex;
flex-wrap: wrap;
}
.hotItem {
width: 50%;
height: 80rpx;
line-height: 80rpx;
font-size: 26rpx;
}
.hotItem .order {
margin: 0 10rpx;
}
.iconImage {
width: 35rpx;
height: 20rpx;
margin-left: 10rpx;
}

/* 搜索内容展示区域 */
.searchContent {
color: #d43c33;
height: 80rpx;
line-height: 80rpx;
font-size: 24rpx;
border-bottom: 1rpx solid #d43c33;
}
.searchItem {
height: 80rpx;
line-height: 80rpx;
display: flex;
}
.searchItem .content {
flex: 1;
margin-left: 20rpx;
border-bottom: 1rpx solid #eee;
font-size: 26rpx;
}

/* 搜索历史记录 */
.history {
position: relative;
display: flex;
flex-wrap: wrap;
line-height: 50rpx;
margin: 20rpx 0;
}
.history .title {
font-size: 28rpx;
height: 50rpx;
}
.historyItem {
height: 50rpx;
font-size: 26rpx;
background: #ededed;
margin-left: 20rpx;
padding: 0 30rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
}
.history .delete {
position: absolute;
bottom: 10rpx;
right: 15rpx;
font-size: 36rpx;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import request from '../../utils/request'

//用于函数节流
let isSend = false
Page({

/**
* 页面的初始数据
*/
data: {
placeholderContent:'', //默认搜索内容
hotList:[], //热搜榜数据
searchContent:'', //用户输入的标单项的数据
searchList:'', //关键字模糊匹配的数据
historyList: [], //搜索历史记录
},

/**
* 生命周期函数--监听页面加载
*/
onLoad() {
// 获取初始化数据
this.getInitData()
// 获取本地搜索历史记录
this.getSearchHistory()
},

//获取本地历史搜索记录的函数
getSearchHistory(){
let historyList = wx.getStorageSync('searchHistory')
if(historyList){
this.setData({
historyList
})
}
},

// 获取初始化数据
async getInitData(){
let placeholderData = await request('/search/default','','GET')
let hotListData = await request('/search/hot/detail','','GET')
this.setData({
placeholderContent: placeholderData.data.showKeyword,
hotList: hotListData.data
})
},
//标单项发生改变后的回调
handleInput(event){
this.setData({
searchContent: event.detail.value.trim()
})
if(isSend){
return
}
isSend = true
this.getSearchList()
// 函数节流
setTimeout(async() => {
isSend = false
},300)
},

// 获取搜索数据的函数
async getSearchList(){
if(!this.data.searchContent){
this.setData({
searchList: []
})
return
}
let {searchContent, historyList} = this.data
//发请求获取关键字模糊匹配数据
let searchListData = await request('/search',{keywords:searchContent, limit:10},'GET')
this.setData({
searchList: searchListData.result.songs
})
//将搜索的关键字添加到搜索历史记录中
if(historyList.indexOf(searchContent) !== -1){
historyList.splice(historyList.indexOf(searchContent),1)
}
historyList.unshift(searchContent)
wx.setStorageSync('searchHistory', historyList)
},

// 清空搜索内容
clearSearchContent(){
this.setData({
searchContent: '',
searchList: []
})
},

// 删除历史记录
deleteSearchHistory(){
wx.showModal({
content: '确认删除历史记录吗?',
success: (res) => {
if(res.confirm){
this.setData({
historyList:[]
})
wx.removeStorageSync('searchHistory')
}
}
})
}
})
1
2
3
4
{
"usingComponents": {},
"navigationBarTitleText": "发现音乐"
}

视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<view class="videoContainer">
<!-- 头部区域 -->
<view class="header">
<image src="/static/images/video/video.jpg"></image>
<view class="search" bindtap="toSearch">搜索音乐</view>
<image src="/static/images/logo.png"></image>
</view>

<!-- 导航区域 -->
<scroll-view
scroll-x
class="navScroll"
scroll-into-view="{{'scroll' + navId}}"
scroll-with-animation
enable-flex>
<view id="{{'scroll' + item.id}}" class="navItem" wx:for="{{videoGroupList}}" wx:key="id">
<view class="navContent {{navId===item.id?'active':''}}" bindtap="changeNav" id="{{item.id}}">{{item.name}}</view>
</view>
</scroll-view>

<!-- 视频列表区域 -->
<scroll-view
scroll-y
refresher-enabled
bindrefresherrefresh="handleRefresh"
refresher-triggered="{{isTriggered}}"
bindscrolltolower="handleToLower"
class="videoScroll">
<view class="videoItem" wx:for="{{videoList}}" wx:key="id" wx:for-index="index">
<view class="footer">
<image class="avatar" src="{{item.data.creator.avatarUrl}}"></image>
<text class="nickName">{{item.data.creator.nickname}}</text>
<view class="comments_praised">
<text class="item">
<text class="iconfont icon-buoumaotubiao15"></text>
<text class="count">{{item.data.praisedCount}}</text>
</text>
<text class="item">
<text class="iconfont icon-pinglun1"></text>
<text class="count">{{item.data.commentCount}}</text>
</text>
<button open-type="share" class="item btn">
<text class="iconfont icon-gengduo"></text>
</button>
</view>
<video
class="common"
object-fit="cover"
src="{{videoUrl[index]}}"
poster="{{item.data.coverUrl}}"
bindplay="handlePlay"
bindtimeupdate="handleTimeUpdate"
bindended="handleEnded"
id="{{item.data.vid}}"
wx:if="{{videoId === item.data.vid}}">
</video>
<!-- 性能优化:使用image图片代替video标签 -->
<image wx:else class="common" bindtap="handlePlay" id="{{item.data.vid}}" src="{{item.data.coverUrl}}"></image>
<view class="content">{{item.data.title}}</view>
</view>
</view>
</scroll-view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
.videoContainer .header {
display: flex;
padding: 10rpx;
}
.videoContainer .header image {
width: 60rpx;
height: 60rpx;
}
.videoContainer .header .search {
border: 1rpx solid #eee;
border-radius: 20rpx;
flex: 1;
margin: 0 20rpx;
font-size: 26rpx;
text-align: center;
line-height: 60rpx;
color: #d43c33;
}
/* 导航区域 */
.navScroll {
display: flex;
white-space: nowrap;
height: 60rpx;
}
.navScroll .navItem {
padding: 0 30rpx;
font-size: 28rpx;
height: 60rpx;
line-height: 60rpx;
}
.navScroll .navContent {
height: 60rpx;
box-sizing: border-box;
}
.navItem .active {
border-bottom: 1rpx solid #d43c33;
}
/* 视频列表 */
.videoScroll {
margin-top: 10rpx;
/* calc: 可以动态计算css的宽高, 运算符左右两侧必须加空格,否则计算会失效 */
/* 视口单位: vh vw 1vh = 1%的视口高度 1vw = 1%的视口宽度*/
height: calc(100vh - 152rpx);
/*height: calc(100vh - 100rpx); 用来测试页面上拉触底*/
}
.videoItem {
padding: 0 3%;
}
.videoItem .common {
width: 100%;
height: 360rpx;
margin-top: 10rpx;
border-radius: 14rpx;
}
.videoItem .content {
font-size: 26rpx;
height:50rpx;
line-height: 50rpx;
max-width: 500rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* footer */
.footer {
border-top: 1rpx solid #eee;
padding: 24rpx 0;
}
.footer .avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
vertical-align: middle;
}
.footer .nickName {
font-size: 26rpx;
vertical-align: middle;
margin-left: 20rpx;
}
.footer .comments_praised {
float: right;
}
.comments_praised .btn {
display: inline;
padding: 0;
background-color: transparent;
border-color: transparent;
}
.comments_praised .btn:after {
border: none;
}
.comments_praised .item {
margin-left: 50rpx;
position: relative;
}
.comments_praised .item .count {
position: absolute;
top: -20rpx;
font-size: 20rpx;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import request from "../../utils/request"

Page({

/**
* 页面的初始数据
*/
data: {
videoGroupList:[], //导航标签数据
navId: '', //导航的标识
videoList:[], //视频列表数据
videoUrl:[], //视频url
videoId:'', //视频id标识
videoUpdateTime:[],//记录video播放的时长
isTriggered: false, //标识下拉刷新是否被触发
offset:0,//分页参数
},

/**
* 生命周期函数--监听页面加载
*/
onLoad() {
//获取导航数据
this.getVideoGroupListData();
},

// 获取导航数据
async getVideoGroupListData(){
let videoGroupListData = await request('/video/group/list','','GET')
this.setData({
videoGroupList: videoGroupListData.data.slice(0,14),
navId: videoGroupListData.data[0].id
})
//获取视频列表数据
this.getVideoList(this.data.navId,this.data.offset)
},

//点击切换导航的回调
changeNav(event){
let navId = event.currentTarget.id
this.setData({
navId: Number(navId),
videoList:[],
videoUrl:[],
offset:0
})
// 显示正在加载
wx.showLoading({
title: '加载中',
})
// 动态获取当前导航的视频数据
this.getVideoList(this.data.navId,this.data.offset)
},

//获取视频列表数据
async getVideoList(navId,offset){
if(!navId){
return
}
let videoListData = await request('/video/group',{id:navId,offset:offset},'GET')
// 关闭加载提示框
wx.hideLoading()

let index = 0
let videoList = videoListData.datas.map(item => {
item.id = index++
return item
})
this.setData({
videoList,
isTriggered: false // 关闭下拉刷新
})
let temp = new Array()
for(let i = 0; i < videoList.length; i++){
temp.push(videoList[i].data.vid)
}
let videoTemp = new Array()
for(let i = 0; i < temp.length; i++){
let video = await request('/video/url',{id:temp[i],t:i},'GET')
videoTemp.push(video.urls[0].url)
}
this.setData({
videoUrl: videoTemp
})
},

// 点击播放/继续播放的回调
handlePlay(event){
/**
* 问题:多个视频同时播放的问题
* 需求:
* 1、在点击播放的事件中需要找到上一个播放的视频
* 2、在播放新的视频之前关闭上一个正在播放的视频
* 关键:
* 1、如何找到上一个视频的实例对象
* 2、如何确认点击播放的视频和正在播放的视频不是同一个视频
*/

let vid = event.currentTarget.id
// 关闭上一个播放的视频
// this.vid !== vid && this.videoContext && this.videoContext.stop()
// this.vid = vid
// 更新data中videoId的状态数据
this.setData({
videoId: vid
})
// 创建创建video标签的实例对象
this.videoContext = wx.createVideoContext(vid)
// 判断当前的视频是否之前播放过,是否有播放记录,如果有,跳转至指定的播放位置
let {videoUpdateTime} = this.data
let videoItem = videoUpdateTime.find(item => item.vid === vid)
if(videoItem){
this.videoContext.seek(videoItem.currentTime)
}
this.videoContext.play()
},

// 监听视频播放进度的回调
handleTimeUpdate(event){
let videoTimeObj = {
vid: event.currentTarget.id,
currentTime: event.detail.currentTime
}
let {videoUpdateTime} = this.data
/**
* 思路:判断记录播放时长的videoUpdateTime数组中是否有当前视频的播放记录
* 1、如果有,在原有的播放记录中修改播放时间为当前的播放时间
* 2、如果没有,需要在数组中添加当前视频的播放对象
*/
let videoItem = videoUpdateTime.find(item => item.vid === videoTimeObj.vid)
if(videoItem){
videoItem.currentTime = videoTimeObj.currentTime
}else {
videoUpdateTime.push(videoTimeObj)
}
// 更新videoUpdateTime的状态
this.setData([
videoUpdateTime
])
},

// 视频播放结束调用的回调
handleEnded(event){
let {videoUpdateTime} = this.data
videoUpdateTime.splice(videoUpdateTime.findIndex(item => item.vid === event.currentTarget.id),1)
this.setData({
videoUpdateTime
})
},

// 自定义下拉刷新的回调:scroll-view
handleRefresh(){
//再次发请求,获取最新的数据
this.getVideoList(this.data.navId,this.data.offset)
},

// 自定义上拉触底的回调:scroll-view
handleToLower(){
// 数据的分页: 1、后端分页 2、前端分页
// 网易云api暂时没有提供分页的api
this.setData({
offset: this.data.offset + 1
})
this.getVideoList(this.data.navId,this.data.offset)
},

// 跳转至搜索界面
toSearch(){
wx.navigateTo({
url: '/pages/search/search',
})
},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function({from}) {
let PageObject = getCurrentPages()
if(from === 'button'){
return {
title: '十点云音乐',
page: PageObject.route[0],
imageUrl: ''
}
}else {
return {
title: '十点云音乐',
page: PageObject.route[0],
imageUrl: ''
}
}
}
})
1
2
3
4
{
"usingComponents": {},
"navigationBarTitleText": "视频页"
}

每日推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<view class="recommendSongContainer">
<!-- 头部 -->
<view class="header">
<image src="/static/images/recommendSong/recommendSong.jpg"></image>
<view class="date">
<text class="day">{{day}} / </text>
<text class="month">{{month}}</text>
</view>
</view>

<!-- 列表区域 -->
<view class="ListContainer">
<view class="listHeader">
<text>播放全部</text>
<text class="changeMore">多选</text>
</view>
<!-- 内容区 -->
<scroll-view scroll-y class="listScroll">
<view class="scrollItem" wx:for="{{recommendSongList}}" wx:key="id" data-index="{{index}}" data-song="{{item}}" bindtap="toSongDetail">
<image src="{{item.al.picUrl}}"></image>
<view class="musicInfo">
<text class="musicName">{{item.name}}</text>
<text class="author">{{item.ar[0].name}}</text>
</view>
<text class="iconfont icon-gengduo"></text>
</view>
</scroll-view>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/* 头部 */
.recommendSongContainer .header {
position: relative;
width: 100%;
height: 300rpx;
}
.recommendSongContainer .header image {
width: 100%;
height: 100%;
}
.recommendSongContainer .header .date {
position: absolute;
left: 50%;
top: 50%;
margin-left: -130rpx;
margin-top: -35rpx;

width: 300rpx;
height: 100rpx;
color: #fff;
text-align: center;
line-height: 100rpx;
}
.header .date .day {
font-size: 45rpx;
}

/* 列表区域 */
.ListContainer {
position: relative;
top: -20rpx;
padding: 0 20rpx;
border-radius: 30rpx;
background: #fff;
}
.listHeader {
height: 80rpx;
line-height: 80rpx;
}
.listHeader text {
font-size: 32rpx;
}
.listHeader .changeMore{
float: right;
}
/* 内容区 */
.listScroll {
height: calc(100vh - 380rpx);
}
.scrollItem {
display: flex;
position: relative;
margin-bottom: 20rpx;
}
.scrollItem image {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
}
.musicInfo {
display: flex;
flex-direction: column;
margin-left: 20rpx;
}
.musicInfo text {
height: 40rpx;
line-height: 40rpx;
font-size: 26rpx;
max-width: 400rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scrollItem .iconfont {
position: absolute;
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
right: 0;
text-align: right;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import PubSub from 'pubsub-js'

import request from '../../../utils/request'
Page({

/**
* 页面的初始数据
*/
data: {
day: '', //天
month: '', //月
recommendSongList: [], //每日推荐歌曲列表
index: 0, //点击音乐的下标
},

/**
* 生命周期函数--监听页面加载
*/
onLoad() {
// 判断用户是否登陆
let userInfo = wx.getStorageSync('userInfo')
if(!userInfo){
wx.showToast({
title: '请先登录',
icon: 'none',
success: () => {
wx.reLaunch({
url: '/pages/login/login',
})
}
})
}
// 更新一下日期的状态
this.setData({
day: new Date().getDate(),
month: new Date().getMonth() + 1
})
// 获取每日推荐歌曲
this.getRecommedSong()
// 订阅来自songDetail页面发布的消息
PubSub.subscribe('switchType',(msg,data) => {
let {recommendSongList,index} = this.data
if(data === 'pre'){
(index === 0) && (index = recommendSongList.length)
index -= 1
}else {
(index === recommendSongList.length - 1) && (index = -1)
index += 1
}
//更新下标
this.setData({
index
})
let musicId = recommendSongList[index].id
// 将音乐id回传给songDetail
PubSub.publish('musicId',musicId)
})
},

//获取每日推荐歌曲
async getRecommedSong(){
let recommendSongData = await request('/recommend/songs','','GET')
this.setData({
recommendSongList: recommendSongData.data.dailySongs
})
},
//跳转至songDetail页面
toSongDetail(event){
let {song,index} = event.currentTarget.dataset
this.setData({
index
})
//路由跳转传参:query参数
wx.navigateTo({
url: '/songPackage/pages/songDetail/songDetail?musicId=' + song.id,
})
}
})
1
2
3
4
5
{
"usingComponents": {},
"navigationBarBackgroundColor": "#91CDF6",
"navigationBarTitleText": "每日推荐"
}

歌曲页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<view class="songDetailContainer">
<view class="author">{{song.ar[0].name}}</view>
<view class="circle"></view>
<image class="needle {{isPlay && 'needleRotate'}}" src="/static/images/song/needle.png"></image>
<view class="discContainer {{isPlay && 'discAnimation'}}">
<image class="disc" src="/static/images/song/disc.png"></image>
<image class="musicImage" src="{{song.al.picUrl}}"></image>
</view>
<!-- 进度条控制区域 -->
<view class="progressControl">
<text>{{currentTime}}</text>
<!-- 总进度条 -->
<view class="barControl">
<!-- 实时进度条 -->
<view class="audio-currentTime-Bar" style="width: {{currentWidth + 'rpx'}};">
<!-- 小圆球 -->
<view class="audio-circle"></view>
</view>
</view>
<text>{{durationTime}}</text>
</view>
<!-- 底部控制播放区域 -->
<view class="musicControl">
<text class="iconfont icon-iconsMusicyemianbofangmoshiShuffle"></text>
<text class="iconfont icon-shangyishou" id="pre" bindtap="handleSwitch"></text>
<text class="iconfont {{isPlay?'icon-zanting':'icon-bofang'}} big" bindtap="handleMusicPlay"></text>
<text class="iconfont icon-next" id="next" bindtap="handleSwitch"></text>
<text class="iconfont icon-iconsMusicyemianbofangmoshiPlayList"></text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
.songDetailContainer {
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
}
/* 底座 */
.circle {
position: relative;
z-index: 100;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: #fff;
margin: 10rpx 0;
}
/* 摇杆 */
.needle {
position: relative;
z-index: 99;
top: -40rpx;
left: 60rpx;
width: 192rpx;
height: 274rpx;
transform-origin: 40rpx 0;
transform: rotate(-20deg);
transition: transform 1s;
}
.needleRotate {
transform: rotate(0deg);
}
/* 磁盘 */
.discContainer {
position: relative;
top: -170rpx;
width: 598rpx;
height: 598rpx;
}
.discAnimation {
animation: disc 5s linear infinite;
animation-delay: 1s;
}
/* 设置动画帧
1、from to:使用简单的动画,只有起始帧和结束帧
2、百分比: 多用于复杂的动画,动画不止两帧
*/
@keyframes disc {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.disc {
width: 598rpx;
height: 598rpx;
}
.musicImage {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 370rpx;
height: 370rpx;
border-radius: 50%;
}
/* 底部控制区域 */
.musicControl {
position: absolute;
bottom: 40rpx;
left: 0;
border-top: 1rpx solid #fff;
width: 100%;
display: flex;
}
.musicControl text {
width: 20%;
height: 120rpx;
line-height: 120rpx;
text-align: center;
color: #fff;
font-size: 50rpx;
}
.musicControl text.big {
font-size: 80rpx;
}

/* 进度条控制区域 */
.progressControl {
position: absolute;
bottom: 200rpx;
width: 640rpx;
height: 80rpx;
line-height: 80rpx;
display: flex;
}
.progressControl text {
font-size: 36rpx;
color: rgba(0, 0, 0, 0.7);
}
.barControl {
position: relative;
width: 420rpx;
height: 4rpx;
background: rgba(0, 0, 0, 0.4);
margin: auto;
}
.audio-currentTime-Bar {
position: absolute;
top: 0;
left: 0;
z-index: 1;
height: 4rpx;
background: red;
}
.audio-circle {
position: absolute;
right: -12rpx;
top: -5rpx;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #fff;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import PubSub from 'pubsub-js'
import Moment from 'moment'

import request from '../../../utils/request'

//获取全局实例
const appInstance = getApp()

Page({

/**
* 页面的初始数据
*/
data: {
isPlay: false, //标识音乐是否在播放
song:{}, //歌曲详情对象
musicId:'', //音乐id
musicLink:'', //音乐的链接
currentTime: '00:00', //实时时间
durationTime: '00:00', //总时长
currentWidth: 0, //实时进度条长度
},

/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let musicId = options.musicId
this.setData({
musicId
})
//获取音乐详情
this.getMusicInfo(musicId)

/**
* 问题:如果用户操作系统的控制音乐播放/暂停的按钮,页面不知道,导致页面显示是否播放的状态和真实的音乐播放状态不一致
* 解决方案:
* 1、通过控制音频的实例 backgroundAudioManager 去监视音乐的播放/暂停
*/
// 判断当前页面音乐是否在播放
if(appInstance.globalData.isMusicPlay && appInstance.globalData.musicId === musicId){
//修改当前页面音乐播放的状态为true
this.changePlayState(true)
}
// 创建控制音乐播放的实例对象
this.backgroundAudioManager = wx.getBackgroundAudioManager()
// 监视音乐播放和暂停和停止
this.backgroundAudioManager.onPlay(() => {
this.changePlayState(true)
appInstance.globalData.musicId = musicId
})
this.backgroundAudioManager.onPause(() => {
this.changePlayState(false)
})
this.backgroundAudioManager.onStop(() => {
this.changePlayState(false)
})
//监听音乐实时播放的进度
this.backgroundAudioManager.onTimeUpdate(() => {
let currentTime = Moment(this.backgroundAudioManager.currentTime * 1000).format('mm:ss')
let currentWidth = (this.backgroundAudioManager.currentTime / this.backgroundAudioManager.duration) * 450
this.setData({
currentTime,
currentWidth
})
})
//监听音乐播放自然结束
this.backgroundAudioManager.onEnded(() => {
//自动切换至下一首音乐,并自动播放
//发布消息给recommendSong页面
PubSub.publish('switchType','next')
//将实时进度条还原为0
this.setData({
currentWidth: 0,
currentTime: '00:00'
})
})
},

//修改播放状态的功能函数
changePlayState(isPlay){
this.setData({
isPlay
})
//修改全局音乐播放的状态
appInstance.globalData.isMusicPlay = isPlay
},

// 获取音乐详情
async getMusicInfo(musicId){
let songData = await request('/song/detail',{ids:musicId},'GET')
this.setData({
song: songData.songs[0],
durationTime: Moment(songData.songs[0].dt).format('mm:ss')
})
//动态修改窗口标题
wx.setNavigationBarTitle({
title: this.data.song.name,
})
},

// 点击播放/暂停的回调
handleMusicPlay(){
let isPlay = !this.data.isPlay
// this.setData({
// isPlay
// })
let {musicId,musicLink} = this.data
this.musicControl(isPlay,musicId,musicLink);
},

//控制音乐播放/暂停的功能函数
async musicControl(isPlay,musicId,musicLink){
if(isPlay){
if(!musicLink){
// 获取音乐的播放链接
let musicLinkData = await request('/song/url',{id:musicId})
musicLink = musicLinkData.data[0].url
this.setData({
musicLink
})
}
this.backgroundAudioManager.src = musicLink
this.backgroundAudioManager.title = this.data.song.name
}else {
this.backgroundAudioManager.pause()
}
},

//点击切歌
handleSwitch(event){
//获取切歌的类型
let type = event.currentTarget.id
//关闭当前播放的音乐
this.backgroundAudioManager.stop()
//订阅来自recommendSong页面
PubSub.subscribe('musicId',(msg,data) => {
//获取音乐详情
this.getMusicInfo(data)
//自动播放当前音乐
this.musicControl(true,data)
//取消订阅
PubSub.unsubscribe('musicId')
})
//发布消息给recommendSong页面
PubSub.publish('switchType',type)
}
})

config.js

1
2
3
4
// 配置服务器相关信息
export default {
host: 'https://jk.zhongwangtx.com'
}

request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//发送ajax请求
/**
* 1.封装功能函数
* - 功能点明确
* - 函数内部应该保留固定代码(静态的)
* - 将动态的数据抽取成形参,由使用者根据自身的情况动态传入实参
* - 一个良好的功能函数应该设置形参的默认值(ES6的形参默认值)
* 2.封装功能组件
* - 功能点明确
* - 组件内部应该保留静态代码
* - 将动态的数据抽取成props参数,由使用者根据自身的情况以标签属性的形式动态传入props数据
* - 一个良好的组件应该设置组件的必要性及数据类型
*/

import config from './config'

export default (url, data={}, method) => {
return new Promise((resolve, reject) => {
wx.request({
url: config.host + url,
data,
method,
header: {
cookie: wx.getStorageSync('cookies')?wx.getStorageSync('cookies').find(item => item.indexOf('MUSIC_U') !== -1):''
},
success:(res) => {
if(data.isLogin){
wx.setStorage({
key: 'cookies',
data: res.cookies
})
}
resolve(res.data)
},
fail:(err) => {
reject(err)
}
})
})
}

app.js

1
2
3
4
5
6
App({
globalData: {
isMusicPlay: false, //标识是否有音乐在播放
musicId: '', //音乐id
}
})

app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"pages": [
"pages/index/index",
"pages/search/search",
"pages/video/video",
"pages/login/login",
"pages/personal/personal"
],
"window": {
"navigationBarBackgroundColor": "#d43c33",
"navigationBarTitleText": "十点云音乐",
"navigationBarTextStyle": "white"
},
"tabBar": {
"color": "#333",
"selectedColor": "#d43c33",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "主页",
"iconPath": "/static/images/tabs/tab-home.png",
"selectedIconPath": "/static/images/tabs/tab-home-selected.png"
},
{
"pagePath": "pages/video/video",
"text": "视频",
"iconPath": "/static/images/tabs/tab-video.png",
"selectedIconPath": "/static/images/tabs/tab-video-selected.png"
},
{
"pagePath": "pages/personal/personal",
"text": "个人中心",
"iconPath": "/static/images/tabs/tab-my.png",
"selectedIconPath": "/static/images/tabs/tab-my-selected.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"requiredBackgroundModes": [
"audio"
],
"subpackages": [
{
"root": "songPackage",
"pages": [
"pages/recommendSong/recommendSong",
"pages/songDetail/songDetail"
]
}
],
"preloadRule": {
"pages/index/index":{
"packages": ["songPackage"]
}
},
"lazyCodeLoading": "requiredComponents"
}

app.wxss

1
2
3
4
5
@import "/static/iconfont/iconfont.wxss";

page {
height: 100%;
}