仿淘票票 —— 微信小程序

一、需求分析

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;
}
  • 4
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iGma_e

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值