uni-app项目之电影预告

一、创建项目

开发工具:HBuilderX
Vue版本:Vue3

1.新建项目

在这里插入图片描述

2.构建基础页面

1.在pages目录下创建search、me页面
在这里插入图片描述

3.构建tabBar

tabBar主要在page.json里配置

"tabBar": {
		"color": "#bfbfbf",
		"selectedColor": "#515151",
		"backgroundColor": "#ffffff",
		"borderStyle": "black",
		"list": [
			{
				"pagePath": "pages/index/index",
				"text": "首页",
				"iconPath": "static/tabBarIco/index.png",
				"selectedIconPath": "static/tabBarIco/index_sel.png"
			},
			{
				"pagePath": "pages/search/search",
				"text": "搜索",
				"iconPath": "static/tabBarIco/search.png",
				"selectedIconPath": "static/tabBarIco/search_sel.png"
			},
			{
				"pagePath": "pages/me/me",
				"text": "我的",
				"iconPath": "static/tabBarIco/me.png",
				"selectedIconPath": "static/tabBarIco/me_sel.png"
			}
		]
	}

二、构建首页-轮播图

在App.vue里配置首页公共样式

<style>
	/*每个页面公共css */
	.page{
		width: 100%;
		height: 100%;
		background-color: #f7f7f7;
	}
</style>

1.使用轮播组件

<template>
	<view class="page">
		
		<swiper :indicator-dots="true" :autoplay="true" :circular="true" class="carousel">
			<swiper-item>
				<image src="/static/carousel/寒战.png" class="carousel"></image>
			</swiper-item>
			<swiper-item>
				<image src="../../static/carousel/你的婚礼.png" class="carousel"></image>
			</swiper-item>
			<swiper-item >
				<image src="../../static/carousel/无限战争.png" class="carousel"></image>
			</swiper-item>
		</swiper>
	</view>
</template>

效果图:
在这里插入图片描述

2.禁用原生导航栏达到页面全屏化

在pages.json里通过配置 navigationStyle为custom,或titleNView为false,来禁用原生导航栏。

{
			"path": "pages/index/index",
			"style": {
				// "navigationBarTitleText": ""
				"app-plus": {
					"titleNView": false
				}
			}
		}

去掉原生导航栏前:
在这里插入图片描述

去掉原生导航栏后:
在这里插入图片描述

3.引用组件实现全局变量

  1. 创建common目录,新增common.js文件
const serverUrl_dev = "http://192.168.31.218:8091";
const serverUrl_prod = "https://www.baidu.com";


export default {
	serverUrl_dev
}
  1. 引用组件
<script>
	import common from "../../common/common.js"
	
	export default {
		data() {
			return {
				carouselList: []
			}
		},
		onLoad() {
			
			var me = this;
			
			//获取common.js中的服务器地址
			var serverUrl = common.serverUrl_dev;
			
			// var serverUrl = me.serverUrl;
			
			uni.request({
				url: serverUrl + '/index/carousel/list',
				method: 'GET',
				success: (res) => {
					var data = res.data;
					console.log(data);
					if(data.status==200){
						var carouselList = data.content;
						me.carouselList = carouselList;
						
					}
					
				}
			})
		},
		methods: {

		}
	}
</script>

三、构建首页-热门电影

3.1 使用scoll-view组件

  1. template
<scroll-view scroll-x="true" class="page-block hot">
			
			<view class="single-poster" v-for="hotMovie in hotMovieList">
				<view class="poster-wrapper">
					<image :src="hotMovie.poster" class="poster"></image>
					<view class="movie-name">{{hotMovie.name}}</view>	
					<view class="movie-score-wrapper">
						<image src="../../static/icons/star-yellow.png" class="star-ico"></image>
						<image src="../../static/icons/star-yellow.png" class="star-ico"></image>
						<image src="../../static/icons/star-yellow.png" class="star-ico"></image>
						<image src="../../static/icons/star-yellow.png" class="star-ico"></image>
						<image src="../../static/icons/star-white.png" class="star-ico"></image>
						<view class="movie-score">{{hotMovie.score}}</view>
					</view>
				</view>
			</view>

		</scroll-view>
		<!-- 热门电影 end -->
  1. js
//获取热门电影start
	uni.request({
		url:serverUrl + '/index/movie/selByType/hot',
		method:"GET",
		success: (res) => {
			var data = res.data;
			if(data.status == 200) {
				var hotMovieList = data.content;
				me.hotMovieList = hotMovieList;
			}
		}
	})
//获取热门电影end
  1. css
/* 热门电影 start */
.movie-hot{
	margin-top: 12rpx;
	padding: 20rpx;
}

.hot-title-wrapper{
	display: flex;   /*flex布局*/
	flex-direction: row;
}

.hot-ico{
	width: 40rpx;
	height: 40rpx;
	margin-top: 5rpx;
}

.hot-title{
	font-size: 20px;
	margin-left: 15rpx;
	font-weight: bold;
}

.hot{
	width: 100%;
	white-space: nowrap;  /*文字不换行*/
}

.single-poster{
	display: inline-block;  /*内联块元素,从左到右依次排列,可以设定宽高*/
	margin-left: 20rpx;
}

.poster-wrapper{
	display: flex;
	flex-direction: column;
}

.poster{
	width: 200rpx;
	height: 270rpx;
}

.movie-name{
	width: 200rpx;
	margin-top: 10rpx;
	font-size: 14px;
	font-weight: bold;
	/* 名字超出则省略start */
	white-space: nowrap;
	overflow: hidden;   /*内容被修剪,多余的内容被隐藏*/
	text-overflow: ellipsis;  /*显示省略符号 ... 来代表被修剪的文本*/
	/* 名字超出则省略end */
}

.movie-score-wrapper{
	display: flex;
	flex-direction: row;
	width: 200rpx;
}

.star-ico{
	width: 30rpx;
	height: 30rpx;
	margin-top: 10rpx;
}

.movie-score{
	font-size: 14px;
	font-weight: thin;
	margin-top: 6rpx;
	margin-left: 2rpx;
	color: grey;  /*字体颜色*/
}

/* 热门电影 end */

3.2 开发自定义组件

  1. 在commpents目录下创建一个组件helloComp
    在这里插入图片描述

  2. 引入组件

	import helloComp from "../../components/helloComp.vue"   //引用组件
  1. 注册组件
components:{    //注册组件
			helloComp
		}
  1. 使用组件
<!-- 使用组件 -->
<helloComp></helloComp>

3.3 父组件向自定义组件传递值

  1. 定义组件内部使用的属性
<template name="helloComp">    <!-- 定义组件的名称为helloComp -->
	<view>
		{{msg}}
		<view>
			<input type="text" :value="myVal" class="txt" />
		</view>
	</view>
</template>

<script>
	export default {
		
		// 定义组件的名称为helloComp
		name:"helloComp",
		
		data() {
			return {
				"msg": "你好,这是自定义组件~~~"
			};
		},
		// 定义组件内部使用的属性
		props: {
			// 自定义一个变量,用于接受父组件(首页或其他页面)传入的参数值 
			myVal: {
				type: String   //定义这个参数的类型
			}
		}
	}
</script>
  1. 父组件使用参数会传值
<!-- 使用组件 -->
<helloComp myVal="hello,电影预告"></helloComp>

在这里插入图片描述

3.4 完成评分自定义组件

1.在components目录新增评分组件score.vue

<template name="score">
	<view class="movie-score-wrapper">
		<view v-if="showNumber == 1" class="starScore">
			
			<image  v-for="yellow in yellowStars"
			src="../static/icons/star-yellow.png" class="star-ico"></image>
			
			<image  v-for="white in whiteStars"
			src="../static/icons/star-white.png" class="star-ico"></image>
			
			<view class="movie-score">{{innerScore}}</view>
		</view>
	</view>
</template>

<script>
	export default {
		
		name:"score",
		
		data() {
			return {
				yellowStars: 0,
				whiteStars: 5
			};
		},
		props: {
			// innerScore: {
			// 	type: Number
			// },
			// showNum: {
			// 	type: Number
			// }
			innerScore: 0,    //接收父组件传递的值,默认为0
			showNumber: 0    //是否显示评分,1为显示,0为不显示
		},
		created() {
			var tempScore = 0;
			// debugger;
			if (this.innerScore !== null && this.innerScore !== undefined && this.innerScore > 0){
				tempScore = this.innerScore;
			}
			var yellowStars = parseInt(tempScore/2);
			var whiteStars = 5 - yellowStars;
			this.yellowStars = yellowStars;
			this.whiteStars = whiteStars;
		}
	}
</script>

<style>
.movie-score-wrapper{
	display: flex;
	flex-direction: row;
	width: 200rpx;
}

.star-ico{
	width: 30rpx;
	height: 30rpx;
	margin-top: 10rpx;
}

.movie-score{
	font-size: 14px;
	font-weight: thin;
	margin-top: 6rpx;
	margin-left: 2rpx;
	color: grey;  /*字体颜色*/
}

.starScore{
	display: flex;
	flex-direction: row;
}
</style>
  1. 导入score组件
	import score from "../../components/score.vue"
  1. 注册组件
components: {    //注册组件
			helloComp,
			score
		}
  1. 使用组件
<score :innerScore="hotMovie.score" showNumber="1"></score>

在这里插入图片描述

构建首页-热门预告

构建首页-猜你喜欢

1. 编写flex布局

template:

<!-- 猜你喜欢start -->
<view class="page-block movie-hot">
	<view class="hot-title-wrapper">
		<image src="../../static/icons/guess-like.png" class="hot-ico"></image>
		<view class="hot-title">猜你喜欢</view>
	</view>
</view>

<view class="page-block guess-like">
	<view class="guess-like-wrapper">
		 <image src="../../static/poster/信条.png" class="guess-like-poster"></image>
		 
		 <view class="guess-like-info">
			<view class="guess-movie-name">我的祖国我的祖国我的祖国我的祖国</view>
			<score :innerScore="9.1" showNumber="0"></score>
			<view class="guess-movie-type">2018 / 美国 / 科幻 / 超级英雄</view>
			<view class="guess-movie-time">上映:2022-11-17(中国大陆)</view>
			<view class="guess-movie-author"> 张国荣/张丰毅/巩俐/葛优/英达 </view>
		 </view >
			 
		 <view class="guess-like-praise">
		 </view>
	</view>
</view>
<!-- 猜你喜欢end -->

css:

/* 猜你喜欢start */
.guess-like{
	display: flex;
	flex-direction: column;
}
.guess-like-wrapper{
	display: flex;
	flex-direction: row;
	padding: 30rpx 20rpx;
	justify-content: space-between;
}
.guess-like-poster{
	width: 180rpx;
	height: 240rpx;
	border-radius: 3%;
}
.guess-like-info{
	width: 340rpx;
	display: flex;
	flex-direction: column;
}
.guess-movie-name{
	/* font-size: 14px; */
	font-weight: bold;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.guess-movie-type{
	font-size: 14px;
	color: #808080;
}
.guess-movie-time{
	font-size: 14px;
	color: #808080;
}
.guess-movie-author{
	font-size: 14px;
	color: #808080;
}
/* 猜你喜欢end */

2. 实现点赞动画效果

template:

<view class="guess-like-praise"  @click="praiseMe">
	 <image src="../../static/icons/praise.png" class="praise-ico"></image>
	 <view class="praise-me">点赞</view>
	 <view class="praise-me praise-me-opacity" :animation="animationData">+1</view>
</view>

css:

.guess-like-praise {
	width: 140rpx;
	display: flex;
	flex-direction: column;
	justify-content: center;
	
	border-left: 2px dashed lightgrey;
}
.praise-ico {
	width: 50rpx;
	height: 50rpx;
	align-self: center;
}
.praise-me {
	font-size: 14px;
	align-self: center;
	color: orange;
}
.praise-me-opacity{
	font-weight: bold;
	opacity: 0;
}

js:在methods里面添加

//实现点赞动画效果start
praiseMe(){
	//构建动画数据,并且通过step来表示这组动画的完成
	this.animation.translateY(-60).opacity(1).step({
		duration: 400
	});
	
	//导出动画数据到view组件,实现组件的动画效果
	this.animationData = this.animation.export();
	
	//还原动画
	setTimeout(function(){
		this.animation.translateY(0).opacity(0).step({
			"timingFunction": "step-start"
		});
		
		this.animationData = this.animation.export();
	}.bind(this),500)
},
//实现点赞动画效果end

3.还原动画

//还原动画
setTimeout(function(){
	this.animation.translateY(0).opacity(0).step({
		"timingFunction": "step-start"
	});
	
	this.animationData = this.animation.export();
}.bind(this),500)

4.动态渲染列表

template

<!-- 猜你喜欢start -->
<view class="page-block movie-hot">
	<view class="hot-title-wrapper">
		<image src="../../static/icons/guess-like.png" class="hot-ico"></image>
		<view class="hot-title">猜你喜欢</view>
	</view>
</view>

<view class="page-block guess-like" >
	<view class="guess-like-wrapper" v-for="(guessLike,gIndex) in guessLikeList" :key="guessLike.id">
		 <image :src="guessLike.cover" class="guess-like-poster"></image>
		 
		 <view class="guess-like-info">
			<view class="guess-movie-name">{{guessLike.name}}</view>
			<score :innerScore="guessLike.score" showNumber="0"></score>
			<view class="guess-movie-type">{{guessLike.baseInfo}}</view>
			<view class="guess-movie-time">{{guessLike.releaseDate}}</view>
		 </view >
			 
		 <view class="guess-like-praise" :data-gIndex="gIndex"  @click="praiseMe">
			 <image src="../../static/icons/praise.png" class="praise-ico"></image>
			 <view class="praise-me">点赞</view>
			 <view class="praise-me praise-me-opacity" :animation="animationDataArr[gIndex]">+1</view>
		 </view>
	</view>
</view>
<!-- 猜你喜欢end -->

js:方法写在methods里面

// 猜你喜欢start
guessLike(){
	var me = this;
	
	//显示loading
	uni.showLoading({
		mask: true
	});
	//显示导航栏的loading
	uni.showNavigationBarLoading();
	
	var serverUrl = common.serverUrl_dev;
	uni.request({
		url:serverUrl + '/index/movie/guessLike',
		method:"GET",
		success: (res) => {
			var data = res.data;
			if(data.status == 200) {
				var guessLikeList = data.content;
				me.guessLikeList = guessLikeList;
			}
		},
		complete() {
			uni.hideLoading();   //隐藏loading
			uni.hideNavigationBarLoading();  //隐藏导航条的loading
			uni.stopPullDownRefresh();   //停止刷新
		}
	})
},
// 猜你喜欢end

5. 实现动画数组(修复多个电影同时点赞问题)

js

//实现点赞动画效果start
praiseMe(e){
	// console.log(e);
	var gIndex = e.currentTarget.dataset.gindex;
	
	//构建动画数据,并且通过step来表示这组动画的完成
	this.animation.translateY(-60).opacity(1).step({
		duration: 400,
		timingFunction: "ease"
	});
	
	//导出动画数据到view组件,实现组件的动画效果
	// this.animationData = this.animation.export();
	this.animationDataArr[gIndex] = this.animation.export();
	
	//还原动画
	setTimeout(function(){
		this.animation.translateY(0).opacity(0).step({
			timingFunction: "step-start"
		});
		
		// this.animationData = this.animation.export();
		this.animationDataArr[gIndex] = this.animation.export();
	}.bind(this),500)
	
},
//实现点赞动画效果end

template

<view class="guess-like-praise" :data-gIndex="gIndex"  @click="praiseMe">
	 <image src="../../static/icons/praise.png" class="praise-ico"></image>
	 <view class="praise-me">点赞</view>
	 <view class="praise-me praise-me-opacity" :animation="animationDataArr[gIndex]">+1</view>
</view>

下拉刷新

  1. 在pages.json里开启下拉刷新功能
{
	"path": "pages/index/index",
	"style": {
		"enablePullDownRefresh": true,  //开启下拉刷新功能
		// "navigationBarTitleText": ""
		"app-plus": {
			// "titleNView": false    .//禁用原生导航栏
			"titleNView": {
				"type": "transparent"
			}
		}
	}
}
  1. 监听下拉刷新动作
onPullDownRefresh() {
	// console.log('refresh');
	//下拉刷新猜你喜欢数据start
	this.guessLike();
	//下拉刷新猜你喜欢数据end
},

构建搜索页面

静态搜索页面及搜索框固定在顶部

1.template

<template>
<view class="page">
	
	<view clas="search-block page-block">
		
		<view class="search-ico-wrapper">
			<image src="../../static/icons/search.png" class="search-ico"></image>
			<input class="search-text" type="text" maxlength="10" placeholder="请输入电影名" focus/>
		</view>
		
	</view>
	
	<view class="movie-list page-block">
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
		<view class="movie-wrapper">
			<image src="http://192.168.31.218:8091/images/movie/1/cover/1.png" class="moive-poster"></image>
		</view>
		
	</view>

	
</view>
</template>

2.css

/* 搜索框start */

.search-block {
	display: flex;
	flex-direction: row;
	padding: 0rpx 20rpx 20rpx 20rpx;
	
}
.search-ico-wrapper {
	background-color: #eaeaea;
	display: flex;
	flex-direction: row;
	justify-content: center;
	align-items: center;
	padding: 0rpx 10rpx;
	width: 680rpx;
	margin-left: 20rpx;
	/* 搜索框悬浮 */
	position: fixed;
	top: 100;
	z-index: 2000;
}
.search-ico {
	width: 40rpx;
	height: 40rpx;
	/* display: flex;
	flex-direction: column;
	justify-content: center; */
}
.search-text {
	font-size: 14px;
	background-color: #eaeaea;
	
	height: 60rpx;
	width: 680rpx;
	margin-left: 15rpx;
	/* flex: 1; */
}

/* 搜索框end */


/* 电影列表start */
.page-block {
	background-color: #ffffff;
}
.movie-list {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: flex-start;
	padding: 80rpx 10rpx 10rpx 10rpx;
}
.movie-wrapper {
	/* margin: 10rpx; */
	padding: 10rpx 20rpx;
	
}
.moive-poster {
	width: 200rpx;
	height: 270rpx;
}
/* 电影列表end */

动态获取电影列表

script

let me = this;
			
			let pageNum = 1;
			
			me.pageNum = pageNum;
			
			uni.showLoading({
				mask:true,
				title: 'onShow加载中...',
			});
			uni.showNavigationBarLoading();
			
			let serverUrl = common.serverUrl_dev;
			
			// 搜索页获取电影列表start
			uni.request({
				url: serverUrl + '/search/queryPageList',
				data: {
					name: me.name,
					pageNum: me.pageNum,
					pageSize: me.pageSize
				},
				success: (res) => {
					console.log('onshow加载...')
					let data = res.data;
					if (data.code == 200) {
						me.movieList = data.data.rows;
						me.total = data.data.total;
					}
				},
				complete() {
					uni.hideLoading();
					uni.hideNavigationBarLoading();
				}
			})
			// 搜索页获取电影列表start 

template

<template>
	<view class="page">

		<view clas="search-block page-block">
			<view class="search-ico-wrapper">
				<image src="../../static/icons/search.png" class="search-ico"></image>
				<input 
					class="search-text" 
					confirm-type="search" 
					maxlength="10" 
					placeholder="请输入电影名" 
					@confirm="searchMovie"/>
			</view>
		</view>

		<view class="movie-list page-block">
			<view class="movie-wrapper" v-for="movie in movieList">
				<image :src="movie.cover" class="moive-poster"></image>
			</view>
		</view>
	</view>
</template>

分页查询

script

onReachBottom() {
	let me = this;
	let pageNum = me.pageNum + 1;
	let name = me.name;
	let totalPages = (me.total%me.pageSize) > 0 ? (me.total/me.pageSize)+1 : (me.total/me.pageSize);
	
	console.log('totlal:'+ me.total + ',pageSize:' + me.pageSize + ',totalPages:' + totalPages + ',pageNum:' + pageNum)
	if (pageNum > totalPages) {
		return;
	}
	
	me.queryPageList(name, pageNum, me.pageSize);
}
queryPageList(name, pageNum, pageSize) {
	var me = this;
	
	// 显示loading条
	uni.showLoading({
		mask: true,
		title: '加载中...'
	})
	uni.showNavigationBarLoading();
	
	var url = common.serverUrl_dev;
	
	// 搜索页获取电影列表start
	uni.request({
		url: url + "/search/queryPageList",
		data: {
			name: name,
			pageNum: pageNum,
			pageSize: pageSize
		},
		method: 'GET',
		success: (res) => {
			var data = res.data;
			if (200 == res.data.code) {
				
				let tempList = data.data.rows;
				me.movieList = me.movieList.concat(tempList);
				me.total = data.data.total;   //获取总记录数
				me.pageNum = pageNum;     //当前页数
			}
		},
		complete() {
			uni.hideLoading();
			uni.hideNavigationBarLoading();
		}
	})
	// 搜索页获取电影列表end
}

在这里插入图片描述

页面路由api与传参

template:

<view class="movie-wrapper" v-for="movie in movieList">
	<image 
		:src="movie.cover" 
		:data-movieId="movie.id"
		@click="movieDetail"
		class="moive-poster">
	</image>
</view>

script:

// 电影详情页start
movieDetail(e) {
	console.log(e);
	let movieId = e.currentTarget.dataset.movieid;
	uni.navigateTo({
		url:"/pages/movieDetail/movieDetail?id=" + movieId
	})
},
// 电影详情页end