前文:
微信小程序学习之旅–第一个页面的制作
微信小程序学习之旅–零基础制作自己的小程序–第二个页面的制作
微信小程序学习之旅–完善pages页面–字体图标,数据绑定,条件/列表渲染,事件/catch/bind以及路由的学习
微信小程序学习之旅–零基础制作自己的小程序-完成文章详情页–自定义属性/页面通信/缓存机制/异步API/async/await
微信小程序学习之旅–零基础制作自己的小程序-文章详情页音乐播放功能的实现-背景音频/全局App/页面的bug修改和优化
微信小程序入门之旅-第六天-自定义组件及request的使用-制作电影页面
微信小程序入门(七)–入门篇完结
实现电影页面按钮的更多功能
点击更多按钮,进入电影的更多页面。
所以我们接下来就先获取到请求的数据,来构建更多电影页面。
获取数据
data: {
movies:[]
},
onLoad: function (options) {
wx.request({
url: app.gBaseUrl + 'in_theaters',
method: "GET",
data: {
start: 0,
count: 12
},
success: (res) => {
console.log(res.data.subjects);
this.setData({
movies: res.data.subjects
})
}
});
},
more-movie页面布局
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 30rpx 28rpx;
justify-content: space-between;
}
.movie {
margin-bottom: 26rpx;
}
这里,我们可以发现,并没有用外部样式类来设置组件的样式,而是直接给组件添加样式类,设置的样式也生效。这里的生效原因应该是这个组件被view标签进行了包裹,所以设置的样式才会生效,如果没有被其他标签包裹,而是一个单独的标签,应该是设置样式不会生效,除非使用外部样式类。
像下面这种就不会生效。
<movie class="movie" movie="{{movie}}" />
movies页面更多按钮绑定事件
在每个电影列表上都绑定好当前所属的列表类型。用自定义属性进行绑定。
更多按钮的事件
通过event事件属性取到我们在当前元素上自定义的属性,进行页面跳转的时候,将电影类型一并传过去,方法更多电影页面发起请求。
onGoToMore(event) {
const type = event.currentTarget.dataset.type;
wx.navigateTo({
url: "/pages/more-movie/more-movie?type="+type
})
}
更多页面onLoad的更改
需要请求的具体电影列表,通过type属性进行接收。
onLoad: function (options) {
const type = options.type;
wx.request({
url: app.gBaseUrl + type,
method: "GET",
data: {
start: 0,
count: 12
},
success: (res) => {
console.log(res.data.subjects);
this.setData({
movies: res.data.subjects
})
}
});
},
实现movies电影页面的搜索功能
直接使用lin-ui小程序组件库进行开发。方便快捷,没什么写的必要。
注册组件
{
"usingComponents": {
"movie-list":"/components/movie-list/index",
"l-search-bar":"/miniprogram_npm/lin-ui/search-bar/index"
}
}
使用
<l-search-bar l-class="ex-search-bar" placeholder="请输入您想看的电影" />
.ex-search-bar {
height: 90rpx !important;
}
上滑加载更多电影数据
我们每次请求的数据,默认的条数都是12条,所以用户可能一会就滑到了最底部,其实,小程序的生命周期里面,提供的有关于滑到到页面底部的回调函数。
可以看见,只要你滑动到最底部,我们在这个函数里面写的代码就会被打印。说明该函数的确是执行了。
所以我们可以在这个函数里面书写继续发起请求电影列表的代码。
加载更多电影数据
将前面我们在onLoad方法里面拿到的type值,也就是电影列表的类型进行保存。
发起请求获取数据
代码逻辑都写在页面上啦触底事件的生命周期函数中。
onReachBottom: function () {
wx.request({
url: app.gBaseUrl + this.data._type,
method: "GET",
data: {
start: this.data.movies.length,
count: 12
},
success: (res) => {
this.setData({
movies: [...this.data.movies, ...res.data.subjects]
});
}
});
}
提示用户数据请求中
我们在发起请求获取新的电影数据的时候,很可能出现用户多次发起请求,但是此时网络情况并不好,这时候页面的刷新就比较慢,这时候我们就需要提醒用户页面数据正在刷新。用小程序的内置组件就可以完成这种轻提示。
我们的目的就是,数据请求过程中,页面还没刷新完毕的时候,提示用户正在加载,一旦数据请求成功并且页面开始刷新了,就不应该还继续提示用户正在加载数据。
只需要添加显示和隐藏加载动画的提示框即可。
onReachBottom: function () {
wx.showNavigationBarLoading();
wx.request({
url: app.gBaseUrl + this.data._type,
method: "GET",
data: {
start: this.data.movies.length,
count: 12
},
success: (res) => {
this.setData({
movies: [...this.data.movies, ...res.data.subjects]
});
wx.hideNavigationBarLoading()
}
});
},
下拉刷新数据
开启页面的下拉刷新
{
"usingComponents": {
"movie":"/components/movie/index"
},
"enablePullDownRefresh": true
}
如果在app.json里面配置这个的话,那么在所有的页面都可以进行下来是刷新了。
监听用户下拉动作
小程序默认给我们提供好的有小程序页面监听下拉动作的函数。
实现下拉刷新
这个就比较简单了,直接一段代码搞定。获取数据以后,调用wx.stopPullDownRefresh()方法,停止下拉的动作。
onPullDownRefresh: function () {
wx.request({
url: app.gBaseUrl + this.data._type,
data: {
start: 0,
count: 12
},
success: (res) => {
this.setData({
movies: res.data.subjects
})
wx.stopPullDownRefresh();
}
})
}
增强阅读页面效果
配置标题与动态配置标题
配置标题
**使用小程序全局的window对象里面的navigationBarTitleText属性,**设置导航栏的标题。
"navigationBarTitleText": "光与影"
但是这样配置的标题是死标题,而且,还不能动态的改变,对于组件来说,这样配置无疑是没什么用的。我们应该让这个标题是动态的。
动态配置标题
很明显,动态配置标题是不可获取的,对于这种常用的功能,小程序基本都帮我们提供好了。
使用小程序的wx.setNavigationBarTitle方法就可以动态的设置我们想设置的标题了。
我们将这部分动态设置标题的代码写在onReady生命周期中,页面只有第一次加载才需要设置标题的。
onReady: function () {
let title = "电影";
switch (this.data._type) {
case "in_theaters":
title = "正在热映";
break;
case "coming_soon":
title = "即将上映";
break;
case "top250":
title = "Top250";
break;
}
wx.setNavigationBarTitle({
title
})
}
电影详情页
组件的独立性
对post组件进行改进
开始,我们是将点击文章,去文章详情页的点击事件的回调函数,直接写死在了组件上,这肯定是不合理的。因为不可能每个人用组件,点击之后的操作都是同样的。所以回调函数的执行应该让外界传递。提高组件的复用性,让组件与事件的具体执行操作独立开来。这就是组件的独立性。
发射事件
当然,携带的参数也不一定非要通过当前事件的目标对象身上的属性来获取,毕竟我们组件上其实已经有这一篇文章的全部信息了。
接收事件
这种做功能一样还是正常实现,但是提高了组件的可复用性。
文章页面问题修正
虽然我们上面那种做法,提高了组件的复用性。但是现在带来了新的问题,那就是我们点击图片是无法完成跳转了。
方式一
最简单的方式当然是直接在写一个方法,按照之前的逻辑就完事。简单易懂。
方式二
我们可以还是在一个方法里面实现点击上面轮播图实现正常跳转。
onGoToDetail(event) {
let pid = event.currentTarget.dataset.postId;
if (!pid) {
pid = event.detail.pid;
}
wx.navigateTo({
url: "/pages/post-detail/post-detail?pid=" + pid,
})
}
电影详情页的制作
给自定义组件movie绑定事件
为了开发方便,这里我是讲业务代码封装了一部分在组件上,实际上一个好的组件不应该这样,这里是因为这是我们自己写的业务组件,纯粹是为了自己方便,所以可以这样做。
在点击每个电影,都可以去往电影详情页。而且携带了当前电影的id号。
根据id请求电影数据
根据mid,向服务器发起请求,获取当前电影的详情数据。
绑定数据
详情页的制作
详情页头部区域
前面我们已经获取到每个电影详情页的电影详情数据。接下来开始电影详情的头部制作。
骨架
<view class="container">
<image class="head-img" src="{{movie.images.large}}"></image>
<view class="head-image-hover">
<text class="main-title">{{movie.title}}</text>
<text class="sub-title">{{movie.countries[0] + " · " +movie.year}}</text>
<view class="like">
<text class="highlight-font">{{movie.wish_count}}</text>
<text class="plain-font">人喜欢</text>
<text class="highlight-font">{{movie.comments_count}}</text>
<text class="plain-font">条评论</text>
</view>
<image class="movie-image" src="{{movie.images.large}}"></image>
</view>
</view>
样式
.container {
display: flex;
flex-direction: column;
}
.head-img {
width: 100%;
height: 320rpx;
filter: blur(18px);
}
.head-image-hover{
display: flex;
flex-direction: column;
width: 100%;
height: 320rpx;
position: absolute;
}
.main-title{
font-size:38rpx;
color:#fff;
font-weight: bold;
letter-spacing: 2px;
margin-top: 50rpx;
margin-left: 40rpx;
}
.sub-title{
font-size: 28rpx;
color:#fff;
margin-top: 30rpx;
margin-left: 40rpx;
}
.like{
display: flex;
flex-direction: row;
margin-top: 30rpx;
margin-left: 40rpx;
}
.highlight-font{
color:#f21146;
font-size: 22rpx;
font-weight: 600;
margin-right: 10rpx;
}
.plain-font{
color:#666;
font-size: 22rpx;
margin-right: 30rpx;
}
.movie-image {
position: absolute;
top: 160rpx;
right: 30rpx;
height: 238rpx;
width: 175rpx;
}
效果
没错,是我要的。
头部电影海报(预览)
做小程序的海报放大方式,也就是我们所说的预览。小程序原生给我们提供的有这种方法。
onViewPost(event) {
wx: wx.previewImage({
urls: [this.data.movie.images.large],
current: this.data.movie.images.large
})
}
大部分功能基本上小程序都内置过了。想要做什么效果,建议第一件事是去文档查一下有没有。
图片的mode模式
图片的mode模式,懂的都懂
默认不设置的模式:
设置mode模式为aspectFill时:
很明显,可以看出图片没有被拉伸。
当然,mode模式的值很多,可以去官网多看看
电影详情页建议简介部分
数据的预处理
可以看见,我们获取到数据,很多都是数组,不便于页面的数据展示,所以需要对数据进行预处理。
封装一个工具函数
该函数用来处理导演和演员数组,拼接元素的name属性。
export function convertToCastString(casts) {
const names = [];
casts.forEach(item => {
names.push(item.name);
})
return names.join(" / ");
}
数据的处理
对数据进行预处理,方便页面进行更好的展示
import { convertToCastString } from "../../utils/util.js"
const app = getApp()
Page({
data: {
movie: {}
},
onLoad: function (options) {
wx.request({
url: app.gBaseUrl + "subject/" + options.mid,
method: "GET",
success: (res) => {
console.log(res.data);
this.processMovieData(res.data);
}
})
},
onViewPost(event) {
wx: wx.previewImage({
urls: [this.data.movie.image],
current: this.data.movie.image
})
},
processMovieData(movie) {
const movieData = {};
movieData.directorsStr = convertToCastString(movie.directors);
movieData.castsStr = convertToCastString(movie.casts);
movieData.image = movie.images.large;
movieData.title = movie.title;
movieData.sub_title = movie.countries[0] + " · " + movie.year;
movieData.wishCount = movie.wish_count;
movieData.commentsCount = movie.comments_count;
movieData.originalTitle = movie.original_title;
movieData.score = movie.rating.stars / 10;
movieData.average = movie.rating.average;
movieData.genresStr = movie.genres.join(" / ");
this.setData({
movie: movieData
})
}
})
页面数据展示
这里只截取了其中的电影简介部分。也就是我们当前写的这一部分。
<view class="summary">
<view class="orgin-title">
<text>{{movie.originalTitle}}</text>
</view>
<view class="flex-row">
<text class="mark">评分</text>
<view class="score-container">
<l-rate size="22" disabled="{{true}}" score="{{movie.score}}"></l-rate>
<text class="average">{{movie.average}}</text>
</view>
</view>
<view class="flex-row">
<text class="mark">导演</text>
<text>{{movie.directorsStr}}</text>
</view>
<view class="flex-row">
<text class="mark">影人</text>
<text>{{movie.castsStr}}</text>
</view>
<view class="flex-row">
<text class="mark">类型</text>
<text>{{movie.genresStr}}</text>
</view>
</view>
<view class="hr"></view>
电影简介的样式书写
.summary {
margin-left: 40rpx;
margin-top: 40rpx;
color: #777;
}
.orginal-title {
color: #1f3463;
font-size: 24rpx;
font-weight: bold;
margin-bottom: 40rpx;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: 10rpx;
}
.score-container {
display: flex;
flex-direction: row;
align-items: baseline;
}
.average{
margin-left: 20rpx;
}
.mark {
margin-right: 30rpx;
white-space: nowrap;
color: #999;
}
.hr{
width: 100%;
height: 1px;
margin-top: 50rpx;
background-color: #d9d9d9;
}
效果
电影剧情简介
没啥好说的,都简单了
结构
<view class="synopsis">
<text class="synopsis-font">剧情简介</text>
<text class="summary-content">{{movie.summary}}</text>
</view>
<view class="hr"></view>
样式
.synopsis{
margin-left: 40rpx;
display: flex;
flex-direction: column;
margin-top: 50rpx;
}
.synopsis-font{
font-size: 32rpx;
color:#999;
}
.summary-content{
margin-top: 20rpx;
margin-right: 40rpx;
line-height: 40rpx;
letter-spacing: 1px;
text-indent: 2em;
}
影人部分制作
这个部分其实有点难。好好看好好学
增加的数据预处理部分
export function convertToCastString(casts) {
const names = [];
casts.forEach(item => {
names.push(item.name);
})
return names.join(" / ");
}
export function convertToCastInfos(casts) {
const castsArray = [];
casts.forEach(item => {
castsArray.push({
img: item.avatars?.large,
name: item.name
})
})
return castsArray;
}
processMovieData(movie) {
const movieData = {};
movieData.directorsStr = convertToCastString(movie.directors);
movieData.castsStr = convertToCastString(movie.casts);
movieData.image = movie.images.large;
movieData.title = movie.title;
movieData.sub_title = movie.countries[0] + " · " + movie.year;
movieData.wishCount = movie.wish_count;
movieData.commentsCount = movie.comments_count;
movieData.originalTitle = movie.original_title;
movieData.score = movie.rating.stars / 10;
movieData.average = movie.rating.average;
movieData.genresStr = movie.genres.join(" / ");
movieData.summary = movie.summary;
movieData.castsInfo = convertToCastInfos(movie.casts);
this.setData({
movie: movieData
})
}
结构
<view class="casts">
<text class="cast-font">影人</text>
<view class="casts-container">
<block wx:for="{{movie.castsInfo}}" wx:key="index">
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
</block>
</view>
</view>
样式
.casts{
display: flex;
flex-direction: column;
margin-top: 50rpx;
margin-left: 40rpx;
}
.casts-container{
display: flex;
flex-direction: row;
}
.cast-container{
display: flex;
flex-direction: column;
align-items: center;
margin-right: 40rpx;
margin-bottom: 60rpx;
}
.cast-img{
width: 170rpx;
height: 210rpx;
margin-bottom: 12rpx;
}
.cast-font{
color:#999;
margin-bottom: 40rpx;
}
滑动效果
小程序的滑动组件
小程序提供的内置组件里面有scroll-view组件,可以用来替换view组件。
提供这个标签,可以进行横向的滑动,做成可滚动的视图区域。
你想要的scroll-view这都有
效果
电影详情页完整代码
结构
<view class="container">
<image mode="aspectFill" class="head-img" src="{{movie.image}}"></image>
<view class="head-image-hover">
<text class="main-title">{{movie.title}}</text>
<text class="sub-title">{{movie.sub_title}}</text>
<view class="like">
<text class="highlight-font">{{movie.wishCount}}</text>
<text class="plain-font">人喜欢</text>
<text class="highlight-font">{{movie.commentsCount}}</text>
<text class="plain-font">条评论</text>
</view>
<image bind:tap="onViewPost" class="movie-image" src="{{movie.image}}"></image>
</view>
<view class="summary">
<view class="orgin-title">
<text>{{movie.originalTitle}}</text>
</view>
<view class="flex-row">
<text class="mark">评分</text>
<view class="score-container">
<l-rate size="22" disabled="{{true}}" score="{{movie.score}}"></l-rate>
<text class="average">{{movie.average}}</text>
</view>
</view>
<view class="flex-row">
<text class="mark">导演</text>
<text>{{movie.directorsStr}}</text>
</view>
<view class="flex-row">
<text class="mark">影人</text>
<text>{{movie.castsStr}}</text>
</view>
<view class="flex-row">
<text class="mark">类型</text>
<text>{{movie.genresStr}}</text>
</view>
</view>
<view class="hr"></view>
<view class="synopsis">
<text class="synopsis-font">剧情简介</text>
<text class="summary-content">{{movie.summary}}</text>
</view>
<view class="hr"></view>
<view class="casts">
<text class="cast-font">影人</text>
<scroll-view enable-flex="{{true}}" scroll-x="{{true}}" class="casts-container">
<block wx:for="{{movie.castsInfo}}" wx:key="index">
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
<view class="cast-container">
<image class="cast-img" src="{{item.img}}"></image>
<text>{{item.name}}</text>
</view>
</block>
</scroll-view>
</view>
</view>
样式
.container {
display: flex;
flex-direction: column;
}
.head-img {
width: 100%;
height: 320rpx;
filter: blur(18px);
}
.head-image-hover {
display: flex;
flex-direction: column;
width: 100%;
height: 320rpx;
position: absolute;
}
.main-title {
font-size: 38rpx;
color: #fff;
font-weight: bold;
letter-spacing: 2px;
margin-top: 50rpx;
margin-left: 40rpx;
}
.sub-title {
font-size: 28rpx;
color: #fff;
margin-top: 30rpx;
margin-left: 40rpx;
}
.like {
display: flex;
flex-direction: row;
margin-top: 30rpx;
margin-left: 40rpx;
}
.highlight-font {
color: #f21146;
font-size: 22rpx;
font-weight: 600;
margin-right: 10rpx;
}
.plain-font {
color: #666;
font-size: 22rpx;
margin-right: 30rpx;
}
.movie-image {
position: absolute;
top: 160rpx;
right: 30rpx;
height: 238rpx;
width: 175rpx;
}
.summary {
margin-left: 40rpx;
margin-top: 40rpx;
color: #777;
}
.orginal-title {
color: #1f3463;
font-size: 24rpx;
font-weight: bold;
margin-bottom: 40rpx;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: 10rpx;
}
.score-container {
display: flex;
flex-direction: row;
align-items: baseline;
}
.average{
margin-left: 20rpx;
}
.mark {
margin-right: 30rpx;
white-space: nowrap;
color: #999;
}
.hr{
width: 100%;
height: 1px;
margin-top: 50rpx;
background-color: #d9d9d9;
}
.synopsis{
margin-left: 40rpx;
display: flex;
flex-direction: column;
margin-top: 50rpx;
}
.synopsis-font{
font-size: 32rpx;
color:#999;
}
.summary-content{
margin-top: 20rpx;
margin-right: 40rpx;
line-height: 40rpx;
letter-spacing: 1px;
text-indent: 2em;
}
.casts{
display: flex;
flex-direction: column;
margin-top: 50rpx;
margin-left: 40rpx;
}
.casts-container{
display: flex;
flex-direction: row;
height: 300rpx;
}
.cast-container{
display: flex;
flex-direction: column;
align-items: center;
margin-right: 40rpx;
margin-bottom: 40rpx;
}
.cast-img{
width: 170rpx;
height: 210rpx;
margin-bottom: 12rpx;
}
.cast-font{
color:#999;
margin-bottom: 40rpx;
}
行为
import { convertToCastString,convertToCastInfos } from "../../utils/util.js"
const app = getApp()
Page({
data: {
movie: {}
},
onLoad: function (options) {
wx.request({
url: app.gBaseUrl + "subject/" + options.mid,
method: "GET",
success: (res) => {
console.log(res.data);
this.processMovieData(res.data);
}
})
},
onViewPost(event) {
wx: wx.previewImage({
urls: [this.data.movie.image],
current: this.data.movie.image
})
},
processMovieData(movie) {
const movieData = {};
movieData.directorsStr = convertToCastString(movie.directors);
movieData.castsStr = convertToCastString(movie.casts);
movieData.image = movie.images.large;
movieData.title = movie.title;
movieData.sub_title = movie.countries[0] + " · " + movie.year;
movieData.wishCount = movie.wish_count;
movieData.commentsCount = movie.comments_count;
movieData.originalTitle = movie.original_title;
movieData.score = movie.rating.stars / 10;
movieData.average = movie.rating.average;
movieData.genresStr = movie.genres.join(" / ");
movieData.summary = movie.summary;
movieData.castsInfo = convertToCastInfos(movie.casts);
this.setData({
movie: movieData
})
},
onReady: function () {
},
onShow: function () {
},
onHide: function () {
},
onUnload: function () {
},
onPullDownRefresh: function () {
},
onReachBottom: function () {
},
onShareAppMessage: function () {
}
})
入门篇完结
小程序相对于vue或者react来说还是比较简单的。入门容易。
完整项目代码
完整项目代码放在csdn博客上,也可以去我的github上下载。
github地址
first_mini_program
|