一、需求分析
1、页面需求:
- 影片列表页
- 影片详情页
- 个人中心页
2、配置 tabBar
- 项
- 影片模块
- 我的模块
- 配置
- 未选中时字体颜色为黑色,选中时字体颜色为(#fb4e64)
- 选中 or 未选中显示不同颜色的图标。
- 当前显示页面为已选中模块首页或该模块的子页。
- 在非 tabBar 页面中不显示 tabBar
3、影片模块(tabBar - 1)
- 页面最顶部设置 ”正在热映“ 和 ”即将上映“ 的页签,点击页签显示不同影视列表。
- 正在热映
- 页签底部设计一组轮播图。
- 轮播图下方设计影片列表。
- 影片列表中显示影片海报、影片名称、导演、主演及上映时间等信息。
- 点击列表可进入该影片的详细信息页。
- 即将上映
- 页签底部设计星推荐影片列表,横线滚动显示。
- 星推荐列表中包括影片名称以及影片的上映时间。
- 星推荐底部设计影片列表,该列表与正在热映的列表相同。
- 影片详情页
- 详情页共有影片信息、影片标签、影片热评三处信息展示区。
- 影片信息中包括影片名称、影片时长、上映时间、类型、导演及主演。
- 影片标签中显示该影片对应的标签列表,将该列表渲染到页面中。
- 影片热评中显示群众对该影片的评论,在这一区块中显示用户头像,用户昵称,评论时间及评论内容。
4、个人中心模块(tabBar - 2)
- 在本模块中,首页负责显示用户登录状态、活动选项卡及各功能入口列表。
- 登录状态
- 根据用户是否授权显示头像及昵称。
- 未授权时,显示默认头像;文本显示 ”点击登录“。点击 ”点击登录“ 按钮,弹出授权窗口获取用户信息。成功获取信息后,将信息存入项目缓存中,下次进入小程序时,判断缓存中是否存在用户数据。
- 已授权时,显示用户头像及昵称。
- 功能选项卡
- 水平分布
- 选项列表包括:电影票、演出票、优惠卷、影城卡。
- 自行设计数据结构,图标自取。
- 功能入口列表
- 列表左侧显示入口名称、右侧显示进入入口的指向图标。
- 自信设计数据结构。
二、设计思路
1、全局配置(app.json)
-
配置页面
"pages":[ "pages/movie/movie", "pages/my/my", "pages/datails/datails" ],
-
配置 tabBar
"tabBar": { "selectedColor": "#fb5268", "list": [ { "pagePath": "pages/movie/movie", "text": "电影", "iconPath": "./icon/movie2.png", "selectedIconPath": "./icon/movie1.png" }, { "pagePath": "pages/my/my", "text": "我的", "iconPath": "./icon/me2.png", "selectedIconPath": "./icon/me1.png" } ] },
2、电影模块
- 首页(电影列表页)
- 在页面初始数据中设计导航数组和导航选择类数组。
- 使用 flex 布局,将导航数组渲染到页面中。
- 水平排布页签,每个页签宽度占总宽度的 50%。
- 设计标签选中时样式:字体颜色与下边框颜色均为 #fb4e64。
- 给标签添加单击事件,单击标签时将标签对应的数组下标传入事件内部。
- 单击事件中接收数据,根据数据改变导航选择类数组。
- 根据导航选择类数组控制页签的选中 or 未选中。以及页面的显示内容。
- 正在上映页签
- 轮播图
- 在初始化数据中设计轮播图路径数组。
- 使用 swiper 组件和 swiper-item 组件设计图片的轮播效果。
- 影片列表
- 页面加载时,请求网络接口,获取影片列表数据。
- 将请求的数据存入页面初始化数据中。
- 使用 wx:for 语句将列表数据渲染到页面中。
- 在列表中启用 flex 布局,将列表横向分为图片(image)、信息(info)、按钮(button)三个区域。
- 图片区域:显示影片海报。
- 信息区域:显示影片名称、导演、主演、上映时间等信息。
- 按钮区域:显示购票按钮。
- 给列表绑定单击事件,单击列表后调转到列表详情页,并将被单击的影片 id 传到详情页中。
- 轮播图
- 即将上映
- 星推荐
- 页面加载时,请求网络接口,获取星推荐影片列表。
- 将请求的数据存入页面初始化数据中。
- 设计星推荐标题
- 文本内容
- 字体颜色
- 左边框大小及颜色
- 标题内外边距
- 利用 scroll-view 组件设置横向滚动列表效果。
- 将数据渲染到列表中。
- 影片列表:同正在上映页签中的页签列表
- 星推荐
- 影片详情页
- 页面加载时接收首页传递过来的参数,参数为某影片的影片 id
- 根据这个 id 发起网络请求,请求该影片的详细数据、标签数据以及热评数据。
- 页面中分为详细信息、标签信息、热评信息三个区块。
- 详细信息
- 利用 flex 布局将影片详细信息以图文并茂的形式渲染到页面中。
- 数据包括:影片名、影片时长、上映事件、类型、导演、主演列表。
- 其中,主演列表中利用 scroll-view 组件设置横向滚动列表效果,并将演员头像及姓名显示到列表中。
- 标签信息
- 将页面加载时请求的标签数据渲染到页面中。
- 设置每个标签的内边距及背景色,使其美观一些。
- 设置每个标签的外边距,使标签与标签之间存在间隔。
- 热评信息
- 将页面加载时请求的热评数据渲染到页面中。
- 数据中包括热评内容,热评时间以及热评的用户信息。
- 利用 flex 布局,用户信息及热评信息展示出来。
3、个人中心模块
- 对于个人中心模块来说,仅有首页一个页面。在这个页面中包括登录、活动、功能入口三大区块。
- 登录区块
- 页面加载时查看缓存数据,若缓存中有相关用户数据,说明该用户以对小程序授权,把用户信息渲染到登录区块中,其中包括用户头像及用户昵称。
- 若缓存中无任何数据,则表示该用户为对小程序赋予公开信息的权限,此时登录区块显示默认状态。
- 默认状态点击 “点击登录” 按钮,页面中会弹出授权窗口,引导用户授权,授权后将用户数据存入缓存中。
- 活动区块
- 在页面初始化数据中添加数据,数据结构在下方对应模块中详细展示。
- 将数据以列表的形式渲染到页面中。
- 在页面中,启用 flex 布局,将列表横向渲染到页面中。
- 每个列表项包括活动图标及活动名称。
- 功能入口区块
- 在页面初始化数据中添加数据,数据结构在下方对应模块中详细展示。
- 将数据以列表的形式渲染到页面中。
- 在页面中,启用 flex 布局,将入口名称及指向图标并排显示,且两端对齐。
三、页面目录结构
1、根目录
- icon(图标素材文件夹)
- image(图片素材文件夹)
- pages(页面文件夹)
- utils(模块管理文件夹)
- app.js(全局逻辑文件)
- app.json(全局配置文件)
- app.wxss(全局样式文件)
- project.config.json(配置项目对外信息展示的文件)
- sitemap.json(微信索引配置文件)
2、pages(页面文件夹)
- details(影片详情页)
- details.js(逻辑层文件)
- details.json(页面配置文件)
- details.wxml(渲染层框架文件)
- details.wxss(渲染层样式文件)
- movie(影片列表页)
- movie.js(逻辑层文件)
- movie.json(页面配置文件)
- movie.wxml(渲染层框架文件)
- movie.wxss(渲染层样式文件)
- my(个人中心页)
- my.js(逻辑层文件)
- my.json(页面配置文件)
- my.wxml(渲染层框架文件)
- my.wxss(渲染层样式文件)
四、开发阶段
1、影片列表页
(1)movie.js
// pages/movie/movie.js
Page({
// 页面的初始数据
data: {
navList:["正在热映","即将上映"],
select:[true,false],
imageList:[
"../../image/1.jpg",
"../../image/2.jpg",
"../../image/3.jpg",
"../../image/4.jpg"
]
},
// 切换顶部导航
navClick:function(e)
{
// 控制导航样式
let index = e.currentTarget.dataset.index;
let select = [false,false];
select[index] = true;
this.setData({
select:select
})
// 获取第二页面数据
if(index == 1)
{
let list = this.data.list;
let futureList = [];
for(let prop in list)
{
if(prop > 12)
{
futureList.push(list[prop])
}
}
this.setData({
futureList:futureList
})
}
},
// 跳转详情页
jumpDetails:function(e)
{
let id = e.currentTarget.dataset.id;
wx.navigateTo({
url: '../datails/datails?id='+id,
})
},
// 页面加载
onLoad: function (options) {
this.request()
},
// 封装函数,请求接口数据
request:function()
{
wx.request({
url: 'https://m.douban.com/rexxar/api/v2/subject_collection/movie_showing/items',
method:"GET",
success:(res) => {
let data = res.data.subject_collection_items;
let movieList = [];
for(let prop of data)
{
let movie = {};
movie.title = prop.title;
movie.image = prop.cover.url;
movie.director = this.toStr(prop.directors);
movie.actors = this.toStr(prop.actors);
movie.id = prop.id;
movie.year = prop.year + "年";
movie.type = prop.card_subtitle;
movieList.push(movie);
}
this.setData({
list:movieList
})
}
})
},
// 封装函数,将数组转为字符串
toStr:function(arr)
{
let str = "";
for(let prop of arr)
{
str += prop + " ";
}
return str;
}
})
(2)movie.wxml
<view class="model">
<!-- 顶部导航 -->
<view class="nav">
<view class="nav_list {{select[index] ? 'select' : ''}}" wx:for="{{navList}}" bindtap="navClick" data-index="{{index}}">{{item}}</view>
</view>
<!-- 正在热映页面 -->
<view class="now" wx:if="{{select[0]}}">
<!-- 轮播图 -->
<swiper indicator-dots autoplay circular indicator-color="rgba(255,255,255,.3)" indicator-active-color="#FFFFFF" interval="3000" class="swiper">
<swiper-item wx:for="{{imageList}}"><image src="{{item}}"></image></swiper-item>
</swiper>
<!-- 影片列表 -->
<view class="movie">
<view class="movie_list" wx:for="{{list}}" bindtap="jumpDetails" data-id="{{item.id}}">
<image src="{{item.image}}"></image>
<view class="movie_info">
<view class="title">{{item.title}}</view>
<view class="info_item">导演:{{item.director}}</view>
<view class="info_item">主演:{{item.actors}}</view>
<view class="info_item">上映时间:{{item.year}}</view>
</view>
<view class="purchase">购票</view>
</view>
</view>
</view>
<!-- 即将上映页面 -->
<view class="future" wx:if="{{select[1]}}">
<view class="type_title">星推荐</view>
<scroll-view scroll-x="{{true}}" class="scroll_view">
<block wx:for="{{futureList}}">
<view class="future_list">
<image src="{{item.image}}"></image>
<view class="future_info">
<view class="future_title">{{item.title}}</view>
<view>{{item.year}}</view>
</view>
</view>
</block>
</scroll-view>
<view class="type_title">影片列表</view>
<view class="movie">
<view class="movie_list" wx:for="{{list}}" bindtap="jumpDetails" data-id="{{item.id}}">
<image src="{{item.image}}"></image>
<view class="movie_info">
<view class="title">{{item.title}}</view>
<view class="info_item">导演:{{item.director}}</view>
<view class="info_item">主演:{{item.actors}}</view>
<view class="info_item">上映时间:{{item.year}}</view>
</view>
<view class="purchase">购票</view>
</view>
</view>
</view>
</view>
(3)movie.wxss
.model
{
width: 100vw;
height: auto;
padding: 10rpx 30rpx;
box-sizing: border-box;
}
.nav
{
width: 100%;
height: 100rpx;
line-height: 100rpx;
display: flex;
text-align: center;
}
.nav_list
{
width: 50%;
}
.select
{
border-bottom: 4rpx solid #fb4e64;
color: #fb4e64;
}
.now,
.future
{
width: 100%;
height: auto;
padding: 20rpx 0rpx;
}
.swiper
{
width: 100%;
height: 320rpx;
}
.swiper image
{
width: 100%;
height: 100%;
}
.movie
{
width: 100%;
height: auto;
padding: 20rpx 0rpx;
}
.movie_list
{
width: 100%;
height: auto;
padding: 30rpx 0rpx;
/* font-size: 20rpx; */
display: flex;
border-bottom: 2rpx solid #cacaca;
}
.movie_list image
{
width: 200rpx;
height: 300rpx;
margin-right: 30rpx;
}
.movie_info
{
width: 380rpx;
}
.title
{
font-weight: bold;
}
.info_item
{
line-height: 60rpx;
}
.purchase
{
width: 100rpx;
height: 60rpx;
text-align: center;
line-height: 60rpx;
border: 2rpx solid #fb5368;
border-radius: 10rpx;
color: #fb5368;
margin-top: 120rpx;
margin-left: 10rpx;
}
.type_title
{
width: 100%;
line-height: 80rpx;
padding: 0rpx 20rpx;
box-sizing: border-box;
color: #fb5166;
border-left: 10rpx solid #fb5166;
}
.scroll_view
{
width: 100%;
height: 360rpx;
white-space: nowrap;
margin: 20rpx 0rpx;
}
.future_list
{
width: 210rpx;
height: 360rpx;
display: inline-block;
margin: 0rpx 20rpx;
}
.future_list image
{
width: 210rpx;
height: 260rpx;
border-radius: 10rpx;
}
.future_info
{
text-align: center;
}
.future_title
{
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
2、影片详情页
(1)details.js
// pages/datails/datails.js
Page({
// 页面的初始数据
data: {
},
// 页面加载
onLoad: function (options) {
let id = options.id;
let dataUrl = "https://m.douban.com/rexxar/api/v2/movie/"+id;
let labelUrl = "https://m.douban.com/rexxar/api/v2/movie/"+id+"/tags?count=8";
let commentUrl = "https://m.douban.com/rexxar/api/v2/movie/"+id+"/interests";
// 请求影片详细数据
wx.request({
url: dataUrl,
method:"GET",
success:(res) => {
let data = {
title:res.data.title,
timeLong:this.toStr(res.data.durations),
typeLabel:this.toStr(res.data.genres),
year:res.data.year,
dire:res.data.directors[0].name,
actors:res.data.actors,
image:res.data.cover.image.small.url
}
this.setData({
movieData:data
})
}
})
// 请求影片标签数据
wx.request({
url: labelUrl,
method:"GET",
success:(res) => {
this.setData({
labelData:res.data.tags
})
}
})
// 请求评论数据
wx.request({
url: commentUrl,
method:"GET",
success:(res) => {
this.setData({
commentData:res.data.interests
})
}
})
},
// 封装函数,将数组转为字符串
toStr:function(arr)
{
let str = "";
for(let prop of arr)
{
str += prop + " ";
}
return str;
}
})
(2)details.wxml
<view class="details">
<!-- 影片详细数据 -->
<view class="movie">
<view class="movie_pictex">
<view class="movie_text">
<view class="movie_title">{{movieData.title}}</view>
<view class="movie_info">影片时长:{{movieData.timeLong}}</view>
<view class="movie_info">上映时间:{{movieData.year}}</view>
<view class="movie_info">类型:{{movieData.typeLabel}}</view>
<view class="movie_info">导演:{{movieData.dire}}</view>
<view class="movie_info">演员列表:</view>
</view>
<image class="movie_pic" src="{{movieData.image}}"></image>
</view>
<!-- 演员列表 -->
<scroll-view scroll-x="{{true}}" class="scroll_view">
<block wx:for="{{movieData.actors}}">
<view class="actors">
<image src="{{item.cover_url}}"></image>
<view>{{item.name}}</view>
</view>
</block>
</scroll-view>
</view>
<!-- 影片标签数据 -->
<view class="label">
<view class="label_title">影片标签</view>
<view class="label_content">
<view class="label_item" wx:for="{{labelData}}">{{item}}</view>
</view>
</view>
<!-- 影片短评数据 -->
<view class="comment">
<view class="label_title">影片热评</view>
<view wx:for="{{commentData}}">
<view class="comment_list">
<image src="{{item.user.avatar}}" class="user_head"></image>
<view class="comment_info">
<view class="user_name">{{item.user.name}}</view>
<view class="create_time">{{item.create_time}}</view>
<view>{{item.comment}}</view>
</view>
</view>
<image src="../../image/分割线.png" class="line"></image>
</view>
</view>
</view>
(3)details.wxss
.details
{
width: 100vw;
height: auto;
padding: 10rpx 30rpx;
box-sizing: border-box;
}
.movie
{
width: 100%;
height: auto;
}
.movie_pictex
{
display: flex;
}
.movie_text
{
width: 390rpx;
}
.movie_pic
{
width: 300rpx;
height: 420rpx;
}
.movie_title
{
line-height: 100rpx;
font-weight: bold;
font-size: 54rpx;
}
.movie_info
{
line-height: 80rpx;
}
.scroll_view
{
width: 100%;
height: 210rpx;
white-space: nowrap;
margin: 20rpx 0rpx;
}
.actors
{
width: 200rpx;
height: 220rpx;
display: inline-block;
margin: 0rpx 20rpx;
}
.actors image
{
width: 100rpx;
height: 100rpx;
border-radius: 100rpx;
margin: 10rpx 50rpx;
}
.actors view
{
white-space: normal;
text-align: center;
}
.label_title
{
font-weight: bold;
line-height: 80rpx;
border-left: 10rpx solid #fb5166;
padding-left: 10rpx;
box-sizing: border-box;
margin: 20rpx 0rpx;
}
.label_content
{
display: flex;
flex-wrap: wrap;
}
.label_item
{
padding: 10rpx 20rpx;
margin: 10rpx;
background-color: #f0f0f0;
border-radius: 10rpx;
}
.comment_list
{
width: 100%;
height: auto;
display: flex;
margin-top: 20rpx;
}
.user_head
{
width: 100rpx;
height: 100rpx;
border-radius: 100rpx;
margin-right: 20rpx;
}
.comment_info
{
width: 550rpx;
}
.user_name
{
color: #fb5166;
font-weight: bold;
}
.create_time
{
margin: 10rpx 0rpx 30rpx 0rpx;
}
.line
{
width: 100%;
height: 10rpx;
}
3、个人中心页面
(1)my.js
Page({
// 页面的初始数据
data: {
modelList:[
{
text:"电影票",
icon:"../../icon/model1.png"
},
{
text:"演出票",
icon:"../../icon/model2.png"
},
{
text:"优惠卷",
icon:"../../icon/model3.png"
},
{
text:"影城卡",
icon:"../../icon/model4.png"
}
],
funList:[
["我的会员"],
["想看的电影","看过的电影"],
["帮助中心-咨询票小蜜","设置"],
["银行卡特惠"],
]
},
// 获取用户信息
getUserInfo:function(e)
{
let userInfo = e.detail.userInfo;
if(userInfo)
{
let user = {
userImage : userInfo.avatarUrl,
userName : userInfo.nickName
}
wx.setStorage({
data: user,
key: 'userInfo',
})
this.setData({
userInfo:user
})
}
},
onLoad:function()
{
wx.getSetting({
success:(res) => {
if(res.authSetting['scope.userInfo'])
{
wx.getStorage({
key: 'userInfo',
success:(res) => {
this.setData({
userInfo:res.data
})
}
})
}
}
})
}
})
(2)my.wxml
<view class="my">
<!-- 顶部登录 -->
<view class="login">
<image src="{{userInfo ? userInfo.userImage : '../../image/head.jpg'}}"></image>
<view class="user_name">
<button wx:if="{{!userInfo}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo" style="width: 530rpx;padding: 0rpx;">点击登录</button>
<view wx:else>{{userInfo.userName}}</view>
</view>
</view>
<!-- 活动模块 -->
<view class="model">
<view class="model_list" wx:for="{{modelList}}">
<image src="{{item.icon}}"></image>
<view>{{item.text}}</view>
</view>
</view>
<!-- 功能入口列表 -->
<view class="fun">
<view class="funlist" wx:for="{{funList}}">
<view class="item" wx:for="{{item}}" wx:for-item="itema">
<view>{{itema}}</view>
<image src="../../icon/right.png"></image>
</view>
</view>
</view>
</view>
(3)my.wxss
page
{
background-color: #f4f4f4;
}
.login
{
width: 100vw;
height: 200rpx;
display: flex;
margin-top: 20rpx;
background-color: white;
}
.login image
{
width: 120rpx;
height: 120rpx;
border-radius: 160rpx;
margin: 40rpx;
}
.user_name
{
line-height: 200rpx;
font-weight: bold;
}
.user_name button
{
text-align: left;
height: 200rpx;
line-height: 200rpx;
background-color: white;
}
.model
{
width: 100%;
height: auto;
margin-top: 20rpx;
display: flex;
justify-content: space-around;
background-color: white;
}
.model_list
{
width: 100rpx;
height: 140rpx;
}
.model_list image
{
width: 60rpx;
height: 60rpx;
margin: 10rpx 20rpx;
border-radius: 100rpx;
}
.fun
{
width: 100%;
height: auto;
margin-top: 20rpx;
}
.funlist
{
width: 100%;
height: auto;
margin-bottom: 20rpx;
background-color: white;
}
.item
{
width: 100%;
height: 100rpx;
line-height: 100rpx;
padding: 0rpx 20rpx;
box-sizing: border-box;
border-bottom: 2rpx solid #f4f4f4;
display: flex;
justify-content: space-between;
}
.item image
{
width: 60rpx;
height: 60rpx;
margin: 20rpx;
}