第四章:豆瓣项目之首页
使用功能技巧
获取当前定位
- 打开小程序官网文档:API->位置,可以查看使用示例:
wx.getLocation
-
注意:使用定位需要在app.json添加permission字段
"permission": { "scope.userLocation": { "desc": "你的位置信息将用于获取影院信息" } }
豆瓣API
- 豆瓣官方的 API 已经不对外开放,但有第三方代理
- 每个代理限流:10000 次 / 1 小时(豆瓣官方的限流),所有人共享这 10000 次
- API 文档:https://douban-api-docs.zce.me/
- 电影 API 文档:https://douban-api-docs.zce.me/movie.html
百度地图逆地理编码使用
- 创建百度地图应用、申请AK
- 首先要申请一个 AK :http://lbsyun.baidu.com/apiconsole/key/create
- 点击“创建应用”
- 输入“应用名称”(随便写,比如:CoderZHTestProject)
- 应用类型选择“浏览器端”
- 启用服务一定要勾选“逆地理编码”
- Referer白名单:输入“*”,代表什么请求网址都可以
- 点击“提交”
- 根据这个应用拿到AK值
- 首先要申请一个 AK :http://lbsyun.baidu.com/apiconsole/key/create
- 逆地理编码 API
- 文档
- API地址
-
点击”服务文档”,可以看见使用方法:
http://api.map.baidu.com/reverse_geocoding/v3/?ak=您的ak&output=json&coordtype=wgs84ll&location=31.225696563611,121.49884033194 //GET请求
- 即地址固定,只需要传入:AK与location经纬度即可
- 文档
微信小程序的网络请求配置
- 微信小程序中如果要请求一个新的地址,那么首先需要将这个地址域名在微信后台注册
- 打开微信小程序官网->开发->开发设置->服务器域名->
request合法域名
- 然后在这里面把域名添加进去,比如:
https://api.map.baidu.com
,就可以网络请求了 - 注意:如果配置了仍然不能访问:
- 看是否是https
- “微信开发者工具”重新登录一次
微信小程序弹框提示封装
- 打开小程序官网文档:API->界面->交互->wx.showToast
- 封装系统的toast
-
因为是全局使用所以放在app.js文件中
//app.js App({ // 小程序启动完毕调用 onLaunch: function () { // 创建一个db对象 wx.db={}; this.initToast(); wx.db.url = (url)=>{ // 字符串拼接 return `https://api.douban.com/${url}`; } }, initToast:function(){ // type = 0; 提示 // type = 1, 成功 // type = 2 失败 let commonToast =(title,type,duration=1500)=>{ let options ={ title: title, icon: 'none', duration: duration, image: '' }; if(type == 1){ options.icon ='success'; }else if(type == 2){ options.image = '/assets/imgs/upsdk_cancel_normal.png' } wx.showToast(options); }; wx.db.toast = (title,duration)=>{ commonToast(title,0,duration); }; // 2. 封装一个失败提示 wx.db.toastError = (title,duration)=>{ commonToast(title,1,duration); }; // 3. 封装成功提示 wx.db.toastSuccess = (title,duration)=>{ commonToast(title,2,duration); }; /* const tempDuration = 1500; // 1. 文本提示 wx.db.toast = (title,duration=tempDuration)=>{ wx.showToast({ title: title, icon: 'none', duration: duration }); }; // 2. 封装一个失败提示 wx.db.toastError = (title,duration=tempDuration)=>{ wx.showToast({ title: title, icon: 'none', image: '/assets/imgs/upsdk_cancel_normal.png', duration: duration }); }; // 3. 封装成功提示 wx.db.toastSuccess = (title,duration=tempDuration)=>{ wx.showToast({ title: title, icon: 'success', duration: duration }); }; */ }, globalData: { userInfo: null } })
数据缓存
- 打开官方API文档,找到数据缓存
- 数据的缓存、获取、删除都有同步和异步之分
- 缓存后的数据可以通过“微信开发者工具”->调试器->storage 查看当前应用缓存的数据
清除授权缓存
- 场景:一旦授权,下次再如何才能再次弹框?
- 解决办法:打开“微信开发者工具” 点击上面的清缓存按钮->清除授权数据
- 再次刷新,就会重新弹出授权框
首页开发
效果如下:
首页展示
- 初始化home模块:
- 用“微信开发者工具”打开,在pages目录下右击“新建目录”创建home文件夹,然后右击新建Page命名为home
- 在app.json文件中,将pages下的home提到第一个,即启动显示home页面
home.wxml
<!--pages/home/home.wxml-->
<view>
<view class="search-wrapper">
<view class="search">
<image src="/assets/imgs/ic_search.png" />
搜索
</view>
</view>
<view class="main">
<view wx:for="*{allMovies}}" wx:for-item="rowMovie" wx:key="unique" class="row">
<view class="title-wrapper">
<view class="title">*{rowMovie.title}}</view>
<view class="more" data-index="*{index}}" bind:tap="viewMore">查看更多 <view class="arrow"></view></view>
</view>
<!--
查看官方文档scroll-view的使用方法
然后下载官方demo,查看使用方法
设置横向滚动: scroll-x="true"
-->
<scroll-view class="items" scroll-x="true">
<view class="item" wx:for="*{rowMovie.movies}}" wx:for-item="movie" wx:key="unique">
<image class="photo" src="*{movie.images.large}}" />
<view class="title">*{movie.title}}</view>
<view class="score">
<block wx:if="*{movie.stars}}">
<view class="stars">
<!-- 展示星星 -->
<!-- 方法1: stars为基本数据类型,数组 -->
<!--block 仅仅用来循环,不显示控件 -->
<!-- 根据条件判断是否显示控件 -->
<!-- <block wx:for="*{movie.stars}}" wx:key="unique" wx:for-item="star">
<image class="star" wx:if="*{star == 1}}" src="/assets/imgs/rating_star_small_on.png" />
<image class="star" wx:elif="*{star == 0.5}}" src="/assets/imgs/rating_star_small_half.png" />
<image class="star" wx:else="*{star == 0.5}}" src="/assets/imgs/rating_star_small_off.png" />
</block> -->
<!-- 方法2:stars为对象 -->
<image class="star" wx:for="*{movie.stars.on}}" wx:key="unique" src="/assets/imgs/rating_star_small_on.png" />
<image class="star" wx:for="*{movie.stars.half}}" wx:key="unique" src="/assets/imgs/rating_star_small_half.png" />
<image class="star" wx:for="*{movie.stars.off}}" wx:key="unique" src="/assets/imgs/rating_star_small_off.png" />
</view>
*{movie.rating.average}}
</block>
<block wx:else> 暂无评分 </block>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
home.wxss
/* pages/home/home.wxss */
.search-wrapper{
background-color: #42bd55;
padding: 15rpx;
height: 60rpx;
margin-bottom: 30rpx;
}
.search {
background-color: white;
border-radius: 10rpx;
/* 让当前控件的高度填充父类,否则该控件的高度为包裹内容高度 */
height: 100%;
/* 设置内容居中 */
display: flex;
align-items: center;
justify-content: center;
}
.search image{
/* 快速输入方法: w32rpx+h32rpx */
height: 32rpx;
width: 32rpx;
margin-right: 10rpx;
}
.title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
margin-bottom: 40rpx;
}
.title-wrapper .title {
color: black;
font-weight: bold;
font-size: 32rpx;
}
.title-wrapper .more {
color: #0db019;
display: flex;
align-items: center;
}
.title-wrapper .arrow {
width: 15rpx;
height: 15rpx;
border: 4rpx solid #0db019;
border-left: none;
border-top: none;
transform: rotate(-45deg);
margin-left: 6rpx;
}
.item .photo{
width: 100%;
height: 223rpx;
margin-bottom: 10rpx;
border-radius: 10rpx;
border: 4rpx solid #f2f2f2;
}
.item .title {
/* 限制文字的宽度 */
width: 100%;
/* 超出部分隐藏 */
overflow: hidden;
/* 超出部分用...显示 */
text-overflow: ellipsis;
font-weight: bold;
margin-bottom: 6rpx;
}
.score {
display: flex;
align-items: center;
/* background-color: coral; */
color: gray;
}
.score .stars {
display: flex;
margin-right: 4rpx;
}
.stars .star {
width: 24rpx;
height: 24rpx;
}
.items {
/* 实现方法1:父容器为View */
/* display: flex;
//web的方法设置滚动
overflow-x: auto;
*/
/* padding-left: 30rpx; */
/* padding-right: 30rpx; */
/* 实现方法2:父容器为scroll-view */
/* 这个属性非常重要,让item横向显示: 不换行 */
white-space: nowrap;
}
.items .item {
/* margin: 0 15rpx; */
/* display: inline-block; */
display: inline-flex;
flex-direction: column;
margin-left: 30rpx;
width: 160rpx;
}
/* 拿到items的最后一个item */
.items .item:last-of-type {
margin-right: 30rpx;
}
.row {
margin-bottom: 50rpx;
/* 占位高度,图片没加载出来之前的高度 */
height: 382rpx;
}
home.js
// pages/home/home.js
Page({
/**
* 页面的初始数据
*/
data: {
// 数据源
// movies :[],
allMovies:[
{
title: '影院热映',
url: 'v2/movie/in_theaters',
movies:[]
},
{
title: '新片榜',
url: 'v2/movie/new_movies',
movies:[]
},
{
title: '口碑榜',
url: 'v2/movie/weekly',
movies:[]
},
{
title: '北美票房榜',
url: 'v2/movie/us_box',
movies:[]
},
{
title: 'Top250',
url: 'v2/movie/top250',
movies:[]
},
],
},
/**
* 生命周期函数--监听页面加载
* 页面加载完毕
*/
onLoad: function (options) {
// 1. 获取当前城市信息
// 传参为函数
/*
this.loadCity((city)=>{
console.log('获取城市信息:'+city);
this.loadData(city);
});
*/
// 等价于上面
// 影院热映
// this.loadCity(this.loadData);
//1. 先加载本地数据
this.loadLocalData();
//2. 再去请求网络数据
// this.loadCity((city)=>{
// this.loadNewData(0,city);
// })
// // 新片榜
// this.loadNewData(1);
// // 口碑榜
// this.loadNewData(2);
// // 北美票房榜
// this.loadNewData(3);
// // top250
// this.loadNewData(4);
},
loadLocalData: function () {
for (let index = 0; index < this.data.allMovies.length; index++) {
// 同步获取缓存数据
let obj = this.data.allMovies[index];
obj.movies = wx.getStorageSync(obj.title);
}
this.setData(this.data);
},
loadNewData: function (idx,para){
var param = {
start: 0,
count: 25,
apikey:'0df993c66c0c636e29ecbb5344252a4a'
};
if(idx == 0){
param.city = para;
}
const obj = this.data.allMovies[idx];
var reqTask = wx.request({
url: wx.db.url(obj.url),
data: param,
header: { 'content-type':'json'},
success: (res) => {
console.log(res);
const movies = res.data.subjects;
// 清空数组
obj.movies = [];
for (let index = 0; index < movies.length; index++) {
// 因为这几条数据返回的结构不一样,所以这做适配
// 经典写法,如果左边存在,直接计算左边,不用计算右边
let movie = movies[index].subject || movies[index];
this.updateMovie(movie);
// 更新数据源
obj.movies.push(movie);
}
// 缓存数据
// 同步存储数据
// wx.setStorageSync(key, data);
// 异步存储数据,将movies缓存到本地
wx.setStorage({
key: obj.title,
data: obj.movies,
success: (result) => {
},
fail: () => {}
});
// 刷新页面
this.setData(this.data);
},
fail: () => {
wx.db.toast(`获取${obj.title}失败`);
}
});
},
/**
* 获取当前城市
* success就是一个函数参数
* 类型由调用者决定
*/
loadCity: function (success){
/**
* 1. 获取经纬度
* 打开小程序官网文档:API->位置
* 使用定位需要在app.json添加permission字段
*/
wx.getLocation({
type: 'wgs84',
success: (res) => {
// 获取经纬度
console.log(res.latitude,res.longitude);
/**
* 2. 通过百度地图API的逆地理编码拿到城市
* 打开小程序官网文档:API->网络
* 输入wx-request回车
* 查看百度官方文档,如何使用逆地理编码
* 到微信后台注册该域名
*/
var reqTask = wx.request({
url: 'https://api.map.baidu.com/reverse_geocoding/v3/',
data: {
ak: 'nfAoebyL0zLMfhW0F7snbTrYP8p3V4DD',
output:'json',
coordtype:'wgs84ll',
location: res.latitude + ',' + res.longitude
},
header: {'content-type':'application/json'},
method: 'GET',
dataType: 'json',
responseType: 'text',
success: (res) => {
// console.log(res.data.result.addressComponent.city);
// 郑州市
let city = res.data.result.addressComponent.city;
// 截掉字符串中的“市”
city = city.substring(0,city.length-1);
// 将城市回调出去
// if(success) success(city);
// 等价于if,经典写法
success && success(city);
},
fail: () => {
wx.db.toastError('获取城市失败');
},
complete: () => {}
});
},
fail: () => {
console.log("获取位置失败");
wx.db.toastError('获取位置失败');
}
});
},
// 废弃
loadData: function(city){
var reqTask = wx.request({
// url: 'https://api.douban.com/v2/movie/in_theaters',
url: wx.db.url('v2/movie/in_theaters'),
data: {
start: 0,
count: 25,
city: city,
apikey:'0df993c66c0c636e29ecbb5344252a4a'
},
// 注意这里必须设置为json,不能为application/json
header: { 'content-type':'json'},
success: (res) => {
console.log(res.data.subjects);
//数据中间加工处理,stars
let movies = res.data.subjects;
for (let index = 0; index < movies.length; index++) {
this.updateMovie(movies[index]);
}
this.data.allMovies[0].movies = movies;
this.setData(this.data);
// this.setData({ movies : movies});
/**
* 将获取的数据设置到数据源
* this.setData() 这个函数本质就是刷新UI页面
* ()内部就是数据源
* {} 使用来包装表达式的
*/
// 方式1:
// 2. 渲染页面
// this.setData({
// //1. 设置数据源
// movies: res.data.subjects,
// });
//方式2:
// 1. 设置数据源
// this.data.movies = res.data.subjects;
// 2. 渲染页面
// this.setData(this.data);
},
fail: () => {
console.log("获取热映失败");
wx.db.toastError('获取热映失败');
},
complete: () => {}
});
},
// 数据中间加工处理
updateMovie :function (movie) {
// 将stars字符串转为整数
let stars = parseInt(movie.rating.stars);
// 展示星星数据整合
// movie模型本来没有starts属性,给movie(model)添加辅助属性stars,跟iOS一样 !!!!!
// 方式1; stars为基本数据类型,数组
// movie.stars = [1,1,1,0.5,0];
// 方式2: stars为对象
// stars为对象 的on、half、off的3个属性分别代表满星几个,半星几个,无星几个
if(stars == 0) return;
movie.stars = {};
// movie.stars.on = 3;
// movie.stars.half = 1;
// movie.stars.off = 1;
// 转整形
movie.stars.on = parseInt(stars/10);
movie.stars.half = (stars - (movie.stars.on)*10 ) >0 ? 1 : 0;
movie.stars.off = 5 - movie.stars.on - movie.stars.half;
},
// 点击查看更多
viewMore: function (evt){
const index = evt.currentTarget.dataset.index;
const obj = this.data.allMovies[index];
// 查看官方API路由,找到navigateTo,查看传参:path?key=value&key2=value2
wx.navigateTo({
url: `/pages/list/list?title=${obj.title}&url=${obj.url}`
});
}
})
详情列表
list.wxml
<!--pages/list/list.wxml-->
<view class="container">
<view class="item" wx:for="*{movies}}" wx:for-item="movie" wx:key="unique">
<image class="photo" src="*{movie.images.large}}" />
<view class="title">*{movie.title}}</view>
<view class="score">
<block wx:if="*{movie.stars}}">
<view class="stars">
<!-- 方法2:stars为对象 -->
<image class="star" wx:for="*{movie.stars.on}}" wx:key="unique" src="/assets/imgs/rating_star_small_on.png" />
<image class="star" wx:for="*{movie.stars.half}}" wx:key="unique" src="/assets/imgs/rating_star_small_half.png" />
<image class="star" wx:for="*{movie.stars.off}}" wx:key="unique" src="/assets/imgs/rating_star_small_off.png" />
</view>
*{movie.rating.average}}
</block>
<block wx:else> 暂无评分 </block>
</view>
</view>
<!-- 空模块填充 -->
<view wx:if="*{movies.length%3 > 0 }}" class="item hide" >
</view>
<view wx:if="*{movies.length%3 ==1 }}" class="item hide" >
</view>
</view>
list.wxss
.container {
display: flex;
justify-content: space-around;
/* 换行 */
flex-wrap: wrap;
padding-top: 30rpx;
}
/* pages/list/list.wxss */
.item .photo{
width: 100%;
height: 223rpx;
margin-bottom: 10rpx;
border-radius: 10rpx;
border: 4rpx solid #f2f2f2;
}
.item .title {
/* 限制文字的宽度 */
width: 100%;
/* 超出部分隐藏 */
overflow: hidden;
/* 超出部分用...显示 */
text-overflow: ellipsis;
font-weight: bold;
margin-bottom: 6rpx;
text-align: center;
/* 不换行 */
white-space: nowrap;
}
.score {
display: flex;
align-items: center;
/* background-color: coral; */
color: gray;
}
.score .stars {
display: flex;
margin-right: 4rpx;
}
.stars .star {
width: 24rpx;
height: 24rpx;
}
.item {
display: inline-flex;
flex-direction: column;
width: 160rpx;
margin: 0 30rpx 30rpx;
}
/* 拿到items的最后一个item */
.item:last-of-type {
margin-right: 30rpx;
}
.row {
margin-bottom: 30rpx;
/* 占位高度,图片没加载出来之前的高度 */
height: 382rpx;
}
.hide {
visibility: hidden;
}
list.js
// pages/list/list.js
Page({
/**
* 页面的初始数据
*/
data: {
movies:[]
},
/**
* 生命周期函数--监听页面加载
* options: 其他页面传过来的参数
*/
onLoad: function (options) {
console.log(options);
//1. 动态置导航栏,查看API文档,界面-导航栏
wx.setNavigationBarTitle({
title: options.title
});
//2. 取缓存
wx.getStorage({
key: options.title,
success: (result) => {
console.log(result.data);
this.setData({
movies: result.data
});
},
fail: () => {}
});
}
})