微信小程序开发小结
V2.0版本
2016-12-28 更新到v2.0
更新日志
-
2016-11-20
- 1.添加下拉刷新功能
- 2.列表改为模板渲染
- 3.封装了api请求的代码提高可复用性
-
2016-11-21
- 1.添加人物的详情页
- 2.将电影详情和人物详情的网络请求进行了封装
-
2016-11-22
- 1.添加了消息通知组件
- 2.搜索页调整
- 3.调整了目录结构,整合静态资源(dist)和组件文件(component)
-
2016-11-25
- 1.将网络请求从wx.request改为fetch的方式
- 2.完成搜索功能
- 3.完成消息通知组件
- 4.删除了没有用到的util文件夹
-
2016-12-03
- 1.将电影列表的下拉刷新从scroll-view的bindscrolltolower改为Page的onReachBottom事件触发
- 2.将“我的”页面的文件补全,功能列表改为数据渲染,添加跳转。新增换肤
- 3.删除了tabBar中的搜索选项,添加“我的”选项
- 4.首页搜索栏(点击跳转到搜索页)添加轮播图
- 5.添加定位功能,在小程序载入时进行定位
- 6.添加浏览记录、收藏、相册、设置、摇一摇功能(都在开发中)
-
2016-12-04
- 1.将api列表 banner列表、搜索关键词列表、皮肤列表整合配置文件(config.js)
- 2.完成换肤、设置、个人资料(还差修改)、摇一摇功能(再次进入不能摇的问题还需解决)
- 3.添加util文件及文件夹,用于封装获取并格式化时间等工具类方法
- 4.消息组件修改,删除了成功、失败等情况,避免与wx.showToast重合,添加了网络不正常的提示
- 5.电影详情页面添加存储浏览历史的功能
-
2016-12-06
- 1.完成电影收藏和人物收藏功能
- 2.搜索页面添加为空时的提示页面并封装成组件
-
2016-12-14
- 1.完成相册功能和关于页面
- 2.添加定位功能(gps)
-
2016-12-24
- 1.相册的背景图片方式改成image标签的方式
- 2.添加摇一摇debug测试开关变量
github地址:https://github.com/yesifeng/wechat-weapp-movie
提示:v2.0代码会跟文档有一些出入,但大部分都是相同的
第一步 项目配置
一、编写app.json
对整个项目的公共配置
1、pages:配置页面路径(必须),列出所有的页面的路径,所有存在的页面都需要在此写出,否则在页面跳转的时候会报出找不到页面的错误
2、window:窗口配置,配置导航及窗口的背景色和文字颜色,还有导航文字和是否允许窗口进行下拉刷新
3、tabBar:tab栏配置,配置tab栏背景色及出现位置,上边框的颜色(目前只支持黑或白),文字颜色及文字选中颜色,最核心的配置是list即tab栏的列表,官方规定最少2个,最多5个,每个列表项目可配置页面路径、文字、图标及选中时图标的地址
4、network:网络配置,配置网络请求、上传下载文件、socket连接的超时时间
5、debug:调试模式,建议开发时开启(true),可以看到页面注册、页面跳转及数据初始化的信息,另外报错的错误信息也会比较详细
{
"pages": [
"pages/popular/popular",
"pages/coming/coming",
"pages/top/top",
"pages/search/search",
"pages/filmDetail/filmDetail",
"pages/personDetail/personDetail",
"pages/searchResult/searchResult"
],
"window": {
"navigationBarBackgroundColor": "#47a86c",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "电影推荐",
"backgroundColor": "#fff",
"backgroundTextStyle": "dark"
},
"tabBar": {
"color": "#686868",
"selectedColor": "#47a86c",
"backgroundColor": "#ffffff",
"borderStyle": "white",
"list": [{
"pagePath": "pages/popular/popular",
"iconPath": "dist/images/popular_icon.png",
"selectedIconPath": "dist/images/popular_active_icon.png",
"text": "热映"
}, {
"pagePath": "pages/coming/coming",
"iconPath": "dist/images/coming_icon.png",
"selectedIconPath": "dist/images/coming_active_icon.png",
"text": "待映"
},{
"pagePath": "pages/search/search",
"iconPath": "dist/images/search_icon.png",
"selectedIconPath": "dist/images/search_active_icon.png",
"text": "搜索"
},
{
"pagePath": "pages/top/top",
"iconPath": "dist/images/top_icon.png",
"selectedIconPath": "dist/images/top_active_icon.png",
"text": "口碑"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true
}
二、确定目录结构
根据UI图,提取组件和公共样式/脚本,以及page的目录
- comm - 公用的脚本及样式
- script - 公共脚本
- config.js 配置信息 (单页数据量,城市等)
- fetch.js 接口调用 (电影列表及详情,人物详情、搜索)
- style - 公共样式
- animation.wxss 动画
- script - 公共脚本
- component - 公用的组件
- filmList - 电影列表
- filmList.wxml - 组件结构
- filmList.wxss - 组件样式
- filmList - 电影列表
- dist - 静态资源
- images 本地图片,主要存导航的图标 (样式中不可引用本地图像资源)
- pages - 页面
- popular - 页面文件夹 ("popular"为自定义的页面名称,页面相关文件的文件名需与页面名相同)
- popular.js 页面逻辑
- popular.wxml 页面结构
- popular.wxss 页面样式
- popular.json 页面窗口配置 (可参考app.json中的window配置)
- popular - 页面文件夹 ("popular"为自定义的页面名称,页面相关文件的文件名需与页面名相同)
- app.js - 小程序整体逻辑 (初始化、显示、隐藏的事件,以及存放全局数据)
- app.json - 小程序公共配置
- app.wxss - 小程序公共样式
第二步 编写组件
电影列表
结构
玩命加载中…
暂无评分
{{filmItem.rating.average}}分
{{filmItem.title}}
{{filmTagItem}}
拼命加载中…
没有更多内容了
样式
import "../../comm/style/animation.wxss";
.film {
box-sizing: border-box;
width: 750rpx;
padding: 10rpx;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-around;
box-shadow: 0 0 40rpx #f4f4f4 inset;
}
.film-item {
width: 350rpx;
margin-bottom: 20rpx;
border-radius: 10rpx;
background-color: #fff;
border: 1px solid #e4e4e4;
box-shadow: 0 20rpx 40rpx #eee;
overflow: hidden;
animation: fadeIn 1s;
}
.film-cover, .film-cover-img {
width: 350rpx;
height: 508rpx;
}
.film-cover {
position: relative;
border-radius: 10rpx;
overflow: hidden;
}
.film-rating {
box-sizing: border-box;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 50rpx;
padding-right: 20rpx;
font-size: 12px;
text-align: right;
line-height: 50rpx;
background-color: rgba(0, 0, 0, .65);
color: #fff;
}
.file-intro {
padding: 16rpx;
margin-top: -8rpx;
}
.film-title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.film-tag {
width: 100%;
margin-top: 10rpx;
display: flex;
justify-content: flex-start;
}
.film-tag-item {
padding: 4rpx 6rpx;
margin-right: 10rpx;
font-size: 24rpx;
box-shadow: 0 0 0 1px #ccc;
border-top: 1px solid #fff;
border-radius: 10rpx;
background-color: #fafafa;
color: #666;
}
.loading-tip {
width: 100%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #ccc;
}
使用方法
以popular(热映)页面为例
在popular.wxml中插入以下代码引入组件结构:
在popular.wcss中插入一下代码引入组件样式:
import "../../component/filmList/filmList.wxss";
- import 引入组件(模板)
- template 使用组件(模板) data属性可以给模板传入数据
消息提示
结构
样式
@import "../../component/filmList/filmList.wxss";
.message-area {
position: fixed;
width: 100%;
height: 100%;
z-index: 99;
}
.message {
box-sizing: border-box;
position: fixed;
z-index: 999;
left: 50%;
top: 50%;
width: 200rpx;
height: 200rpx;
padding: 30rpx;
margin-top: -100rpx;
margin-left: -100rpx;
display: flex;
flex-wrap: wrap;
justify-content: center;
border-radius: 16rpx;
background-color: rgba(0, 0, 0, .75);
color: #fff;
animation: fadeIn .3s;
}
.message-icon {
height: 100rpx;
width: 100rpx;
background-position: center;
background-repeat: no-repeat;
background-size: 100rpx;
}
.message-icon-success {
background-image: url('http://139.196.214.241:8093/cdn/images/weui-success-icon.png');
}
.message-icon-warning {
background-image: url('http://139.196.214.241:8093/cdn/images/weui-warning-icon.png');
}
.message-icon-info {
background-image: url('http://139.196.214.241:8093/cdn/images/weui-info-icon.png');
}
.message-content {
margin-top: 15rpx;
text-align: center;
}
逻辑
module.exports = {
show: function(cfg) {
var that = this
that.setData({
message: {
content: cfg.content,
icon: cfg.icon,
visiable: true
}
})
if (typeof cfg.duration !== 'undefined') {
setTimeout(function(){
that.setData({
message: {
visiable: false
}
})
}, cfg.duration)
}
},
hide: function() {
var that = this
that.setData({
message: {
visiable: true
}
})
}
}
使用方法
以search(搜索)页面为例
在search.wxml中插入以下代码引入组件结构:
在search.wcss中插入一下代码引入组件样式:
@import "../../component/message/message.wxss";
在search.js中插入一下代码引入组件逻辑:
var message = require('../../component/message/message')
message.show.call(that,{
content: '请输入内容',
icon: 'info',
duration: 1500
})
- import 引入组件(模板)
- template 使用组件(模板) data属性可以给模板传入数据
- require 引入文件中 module.exports导出的数据或方法
- 调用方法:message.show.call(cfg)
第二步 编写公共脚本
请求接口
列表:
- fetchFilms:获取电影列表(热映、待映、排行、搜索结果页面)
- fetchFilmDetail:获取电影详情
- fetchPersonDetail:获取人物详情
- search:搜索关键词或是类型(返回的是电影列表)
var config = require('./config.js')
module.exports = {
fetchFilms: function(url, city, start, count, cb) {
var that = this
if (that.data.hasMore) {
fetch(url + '?city=' + config.city + '&start=' + start + '&count=' + count).then(function(response){
response.json().then(function(data){
if(data.subjects.length === 0){
that.setData({
hasMore: false,
})
}else{
that.setData({
films: that.data.films.concat(data.subjects),
start: that.data.start + data.subjects.length,
showLoading: false
})
}
typeof cb == 'function' && cb(res.data)
})
})
}
},
fetchFilmDetail: function(url, id, cb) {
var that = this;
fetch(url + id).then(function(response){
response.json().then(function(data){
that.setData({
showLoading: false,
filmDetail: data
})
wx.setNavigationBarTitle({
title: data.title
})
typeof cb == 'function' && cb(data)
})
})
},
fetchPersonDetail: function(url, id, cb) {
var that = this;
fetch(url + id).then(function(response){
response.json().then(function(data){
that.setData({
showLoading: false,
personDetail: data
})
wx.setNavigationBarTitle({
title: data.name
})
typeof cb == 'function' && cb(data)
})
})
},
search: function(url, keyword, start, count, cb){
var that = this
var url = decodeURIComponent(url)
if (that.data.hasMore) {
fetch(url + keyword + '&start=' + start + '&count=' + count).then(function(response){
response.json().then(function(data){
if(data.subjects.length === 0){
that.setData({
hasMore: false,
showLoading: false
})
}else{
that.setData({
films: that.data.films.concat(data.subjects),
start: that.data.start + data.subjects.length,
showLoading: false
})
wx.setNavigationBarTitle({
title: keyword
})
}
typeof cb == 'function' && cb(res.data)
})
})
}
}
}
项目配置
city:城市
count:数量
module.exports = {
city: '杭州',
count: 20
}
第三步 编写页面
popular(热映)页面
这里以热映页面为例
待映、口碑排行、搜索结果页面可以以此类推
- popular.json
配置窗口标题以及允许下拉刷新
{
"navigationBarTitleText": "正在热映",
"enablePullDownRefresh": true
}
- popular.wxml
直接引入电影列表组件 与coming(待映)页、top(口碑排行)页、搜索结果页相同
- popular.wxss
@import "../../component/filmList/filmList.wxss";
- popular.js
- 数据说明
- films:电影列表
- hasmore:上拉加载时是否还有更多数据
- showLoading:是否显示loading动画
- start:数据开始位置,用于上拉加载(每次累加)
- windowHeight: 获取窗口高度,用于给scroll-view加高度(小程序中样式好像不能获取到窗口高度,用100%也没用,所以用js获取)
- 页面事件
- onLoad:页面载入,通过引入的fetch请求网络接口获取电影列表
douban.fetchFilms.call(that, url, config.city, that.data.start, config.count)
- onShow:页面显示,通过
wx.getSystemInfo()
获取窗口高度- scroll:scroll-view滚动事件
- scrolltolower:滚动到底部触发的事件,即上拉加载更多数据。调用载入时请求的方法(此时start的值为20)
douban.fetchFilms.call(that, url, config.city, that.data.start, config.count)
- onPullDownRefresh:下拉刷新,初始化数据、显示加载动画并再次调用数据
- viewFilmDetail:查看电影详情,通过
e.currentTarget.dataset
获取标签中的data-*的数据,然后在路径中传递id给filmDetail页面- viewFilmByTag:点击标签(类型)时进入对应类型的搜索页
var douban = require('../../comm/script/fetch')
var config = require('../../comm/script/config')
var url = 'https://api.douban.com/v2/movie/in_theaters'
var searchByTagUrl = 'https://api.douban.com/v2/movie/search?tag='
Page({
data: {
films: [],
hasMore: true,
showLoading: true,
start: 0,
windowHeight: 0
},
onLoad: function() {
var that = this
douban.fetchFilms.call(that, url, config.city, that.data.start, config.count)
},
onShow: function() {
var that = this
wx.getSystemInfo({
success: function(res) {
that.setData({
windowHeight: res.windowHeight*2
})
}
})
},
scroll: function(e) {
console.log(e)
},
scrolltolower: function() {
var that = this
douban.fetchFilms.call(that, url, config.city, that.data.start, config.count)
},
onPullDownRefresh: function() {
var that = this
that.setData({
films: [],
hasMore: true,
showLoading: true,
start: 0
})
douban.fetchFilms.call(that, url, config.city, that.data.start, config.count)
},
viewFilmDetail: function(e) {
var data = e.currentTarget.dataset;
wx.navigateTo({
url: "../filmDetail/filmDetail?id=" + data.id
})
},
viewFilmByTag: function(e) {
var data = e.currentTarget.dataset
var keyword = data.tag
wx.navigateTo({
url: '../searchResult/searchResult?url=' + encodeURIComponent(searchByTagUrl) + '&keyword=' + keyword
})
}
})
filmDetail(电影详情)页面
这里以电影页面为例
人物详情页面可以以此类推
- filmDetail.json
配置窗口标题
{
"navigationBarTitleText": "电影详情"
}
- filmDetail.wxml
玩命加载中…
{{filmDetail.title}}
导演:{{filmDetail.directors[0].name}}
演员:
{{filmDetailCastItem.name}}
/
豆瓣评分:
暂无评分
{{filmDetail.rating.average}}分
上映年份:{{filmDetail.year}}年
{{filmDetail.collect_count}}
看过
{{filmDetail.wish_count}}
想看
{{filmDetail.ratings_count}}
评分人数
剧情简介
{{filmDetail.summary}}
导演/演员
{{filmDetail.directors[0].name}}
导演
{{filmDetailCastItem.name}}
演员
标签
{{filmDetailTagItem}}
- filmDetail.wxss
@import "../../comm/style/animation.wxss";
.fd-hd {
position: relative;
width: 100%;
height: 600rpx;
display: flex;
justify-content: center;
align-content: center;
overflow: hidden;
}
.fd-hd:before {
content: '';
display: block;
position: absolute;
z-index: 1;
width: 100%;
height: 600rpx;
background-color: rgba(0, 0, 0, .6);
}
.fd-hd-bg {
position: absolute;
z-index: 0;
width: 100%;
height: 600rpx;
background-size: cover;
background-position: center;
filter: blur(30rpx);
}
.fd-cover {
z-index: 2;
width: 300rpx;
height: 420rpx;
margin-top: 80rpx;
border-radius: 8rpx;
box-shadow: 0 30rpx 150rpx rgba(255, 255, 255, .3)
}
.fd-intro {
z-index: 2;
width: 320rpx;
margin-top: 80rpx;
margin-left: 40rpx;
color: #fff;
}
.fd-title {
margin-bottom: 30rpx;
font-size: 42rpx;
}
.fd-intro-txt {
margin-bottom: 18rpx;
color: #eee;
}
.fd-data {
display: flex;
height: 150rpx;
justify-content: space-around;
align-items: center;
border-bottom: 1px solid #f4f4f4;
}
.fd-data-item {
width: 33.33%;
text-align: center;
}
.fd-data-item {
border-left: 1px solid #eee;
}
.fd-data-item:first-child {
border-left: none;
}
.fd-data-num {
font-size: 40rpx;
font-weight: 100;
color: #444;
}
.fd-data-title {
color: #999;
}
.fd-bd {
padding: 0 40rpx 40rpx;
}
.fd-bd-title {
padding-left: 20rpx;
margin-top: 40rpx;
margin-bottom: 20rpx;
font-size: 32rpx;
font-weight: bold;
color: #444;
border-left: 10rpx solid #47a86c;
}
.fd-bd-intro {
text-align: justify;
line-height: 1.5;
color: #666;
}
.fd-bd-tag {
display: flex;
}
.fd-bd-tag-item {
padding: 5rpx 10rpx;
margin-right: 15rpx;
border: 1px solid #ccc;
border-radius: 10rpx;
color: #666;
}
.fd-bd-person {
display: flex;
width: 100%;
height: 480rpx;
overflow-x: scroll;
overflow-y: hidden;
}
.fd-bd-person-item {
margin-left: 20rpx;
text-align: center;
}
.fd-bd-person-item:first-child {
margin-left: 0;
}
.fd-bd-person-avatar {
width: 280rpx;
height: 400rpx;
}
.fd-bd-person-name {
color: #666;
}
.fd-bd-person-role {
color: #999
}
- filmDetail.js
- 数据说明
- filmDetail:电影详情数据
- showLoading:是否显示loading动画
- 页面事件
- onLoad:页面载入,获取从电影列表传入的id (在
options
中),通过fetch请求网络接口获取电影详情
douban.fetchFilmDetail.call(that, url, id)
- viewPersonDetail:查看人物详情,通过
e.currentTarget.dataset
获取标签中的data-*的数据,然后在路径中传递id给personDetail页面- viewFilmByTag:点击标签(类型)时进入对应类型的搜索页
var douban = require('../../comm/script/fetch')
var url = 'https://api.douban.com/v2/movie/subject/'
var searchByTagUrl = 'https://api.douban.com/v2/movie/search?tag='
Page({
data: {
filmDetail: {},
showLoading: true
},
onLoad: function(options) {
var that = this
var id = options.id
douban.fetchFilmDetail.call(that, url, id)
},
viewPersonDetail: function(e) {
var data = e.currentTarget.dataset;
wx.redirectTo({
url: '../personDetail/personDetail?id=' + data.id
})
},
viewFilmByTag: function(e) {
var data = e.currentTarget.dataset
var keyword = data.tag
wx.navigateTo({
url: '../searchResult/searchResult?url=' + encodeURIComponent(searchByTagUrl) + '&keyword=' + keyword
})
}
})
search(搜索)页面
- search.json
配置窗口标题
{
"navigationBarTitleText": "搜索"
}
- search.wxml
引用了message组件,当没有输入内容时给出提示
热门搜索
{{hotKeywordItem}}
热门标签
{{hotTagItem}}
- search.js
- 数据说明
- searchType:搜索类型 0为关键词,1为类型(标签)
- hotKeyword:热门关键词
- hotTag:热门标签
- 页面事件
- changeSearchType:改变搜索类型,通过
wx.showActionSheet
- search:搜索表单提交事件,如果没有输入内容,则自定义消息组件的show方法即
message.show.call(cfg)
来提示用户,有内容则通过e.detail.value
来获取表单的数据,并传递给searchDetail(搜索结果页)- searchByKeyword:点击关键词时进入对应类型的搜索页
- viewFilmByTag:点击标签(类型)时进入对应类型的搜索页
var message = require('../../component/message/message')
var douban = require('../../comm/script/fetch')
var url = ['https://api.douban.com/v2/movie/search?q=', 'https://api.douban.com/v2/movie/search?tag=']
Page({
data:{
searchType: 0,
hotKeyword: ['功夫熊猫', '烈日灼心', '摆渡人', '长城', '我不是潘金莲', '这个杀手不太冷', '驴得水', '海贼王之黄金城', '西游伏妖片', '我在故宫修文物', '你的名字'],
hotTag: ['动作', '喜剧', '爱情', '悬疑']
},
changeSearchType: function() {
var types = ['默认', '类型'];
var that = this
wx.showActionSheet({
itemList: types,
success: function(res) {
if (!res.cancel) {
that.setData({
searchType: res.tapIndex
})
}
}
})
},
search: function(e) {
var that = this
var keyword = e.detail.value.keyword
if (keyword == '') {
message.show.call(that,{
content: '请输入内容',
icon: 'info',
duration: 1500
})
return false
} else {
wx.navigateTo({
url: '../searchResult/searchResult?url=' + encodeURIComponent(url[that.data.searchType]) + '&keyword=' + keyword
})
}
},
searchByKeyword: function(e) {
var that = this
var keyword = e.currentTarget.dataset.keyword
wx.navigateTo({
url: '../searchResult/searchResult?url=' + encodeURIComponent(url[0]) + '&keyword=' + keyword
})
},
searchByTag: function(e) {
var that = this
var keyword = e.currentTarget.dataset.keyword
wx.navigateTo({
url: '../searchResult/searchResult?url=' + encodeURIComponent(url[1]) + '&keyword=' + keyword
})
}
})
附录
UI
- 页面加载
- 电影列表
- 下拉刷新
- 电影详情
- 任务详情
- 搜索
- 搜索结果 (关键词搜索)
- 搜索结果 (标签/类型搜索)
- 信息提示