阅读 605

用UNIAPP开发个倒计时小程序

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

前言

这是使用UNIAPP Uview 开发出来的可得计时小程序,是一款简约、方便的倒计时器微信小程序,您可以设置在上面设定未来的何时何分何秒后通知你。

我们先看看我们的成品效果图,是不是觉得很nice

codetimeDome.gif

扫码体验下哦

扫码_搜索联合传播样式-微信标准绿版.png

接下来,让我们来实现它

uniapp中创建一个新的uniapp项目,这里我就命名为codeTime

1.png

我们需要用到的uView里边的CountDown组件,所以我们需要先安装uView,在项目所在位置打开终端,执行 (因为我们的项目是通过hbx创建的,没有package.json文件)

npm init -y
复制代码
npm install uview-ui
复制代码

安装之后,需要在项目中配置下(引入uView框架)

// main.js
import uView from "uview-ui";
Vue.use(uView);
复制代码
# uni.scss
@import 'uview-ui/theme.scss';
# app.vue
<style lang="scss">
	@import "uview-ui/index.scss";
	/*每个页面公共css */
</style>
复制代码
// pages.json
"easycom": {
			"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
		},
复制代码

配置完后,我们可以使用console.log(this.$u.config.v);,查看版本,检测是否安装及配置成功

onShow: function() {
			console.log(this.$u.config.v);
		},
复制代码

2.png

我们还需要改下uView框架中的CountDown组件文件,因为需要我们这个项目需要用到暂停倒计时,但是该组件没有提供改方法

CountDown组件中的props对象中加入属性

pause: {
		type: Boolean,
		default: false
	}
复制代码

修改CountDown组件中的start方法,利用父组件传入的pause属性,来判断是否执行seconds --,也就即是否进行减秒操作

// 倒计时
start() {
	// 避免可能出现的倒计时重叠情况
	this.clearTimer();
	if (this.timestamp <= 0) return;
	this.seconds = Number(this.timestamp);
	this.formatTime(this.seconds);
	this.timer = setInterval(() => {
            if (!this.pause) {
                this.seconds--;
                  // 发出change事件
                    this.$emit('change', this.seconds);
                    if (this.seconds < 0) {
                            return this.end();
                    }
                    this.formatTime(this.seconds);
            } }, 1000);
	},
复制代码
CountDown组件说明文档: www.uviewui.com/components/…

我们把pages文件夹下面的index.vue文件整理下

<template>
	
</template>

<script>
	export default {
		data() {
			return {
				
			}
		},
		onLoad() {

		},
		methods: {

		}
	}
</script>

<style lang="scss">
	
</style>

复制代码

我们项目组件主体分为三大部分

  • 笔记组件
    • note.vue
  • 时间选择器组件
    • date-selector
  • 控制按钮组件
    • controller-area

现在我们就按这个顺序来书写我们各部分的组件,到后面我们将这些组件插入到index.vue中

note组件

note.vue,其实它只是一个简单的input组件,没有任何逻辑处理的组件,也是这个项目中最简单的一个组件,没有之一哈哈

<template>
	<view class="c-title">
		<input class="c-input" type="text" :value="note" placeholder="#添加计时内容" placeholder-class="c-input-pla" />
	</view>
</template>
复制代码
<script>
	export default {
		name:"note",
	}
</script>
复制代码
<style lang="scss">
.c-title {
		padding: 50rpx 20rpx 20rpx 20rpx;
		margin: 20rpx;
		color: #007aff;
		width: 90%;
		height: 60rpx;
		.c-input {
			text-align: center;
			font-size: 45rpx;
			line-height: 45rpx;
			height: 60rpx;
		}

		.c-input-pla {
			color: #e7e7e7;
		}
	}
</style>
复制代码

index.vue中引入它看看效果如何

<template>
	<view class="c-container">
		<note></note>
	</view>
</template>
复制代码
<style lang="scss" scoped>
	.c-container {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
	}
</style>
复制代码

3.png

date-selector组件

这是一个时间选择器组件,也就是我们用户选择时间用到的组件,这里用到的是uniapp自带的组件,叫做picker-view 嵌入页面的滚动选择器,该组件文档地址 uniapp.dcloud.io/component/p…

<template>
<view class="u-picker-body">
			<picker-view :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart"
				@pickend="pickend">
				<picker-view-column>
					<view class="u-column-item" v-for="(item, index) in hours" :key="index">
						{{ formatNumber(item) }}
						<text class="c-text" v-if="showTimeTag"></text>
					</view>
				</picker-view-column>
				<picker-view-column>
					<view class="u-column-item" v-for="(item, index) in minutes" :key="index">
						{{ formatNumber(item) }}
						<text class="c-text" v-if="showTimeTag"></text>
					</view>
				</picker-view-column>
				<picker-view-column>
					<view class="u-column-item" v-for="(item, index) in seconds" :key="index">
						{{ formatNumber(item) }}
						<text class="c-text" v-if="showTimeTag"></text>
					</view>
				</picker-view-column>
			</picker-view>
		</view>
</template>
复制代码
<script>
	export default {
		name:"date-selector",
		created(){
			this.initTimeList()
		},
		data() {
			return {
				moving: false, // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
				hours: [],
				minutes: [],
				seconds: [],
				hour: 0,
				minute: 0,
				second: 0,
				valueArr: [],
				timestamp:300
			};
		},
		props:{
			showTimeTag:{
				type:Boolean,
				default:false
			}
		},
		methods:{
			// 初始化
			initTimeList(){
				// 生成 时,分,秒
				this.valueArr.push(0);
				this.setHours();
				this.valueArr.push(0);
				this.setMinutes();
				this.valueArr.push(0);
				this.setSeconds();
				
			},
			getIndex(arr, val) {
				let index = arr.indexOf(val);
				// 如果index为-1(即找不到index值),~(-1)=-(-1)-1=0,导致条件不成立
				return ~index ? index : 0;
			},
			// 小于10前面补0,用于月份,日期,时分秒等
			formatNumber(num) {
				return +num < 10 ? '0' + num : String(num);
			},
			// 标识滑动开始,只有微信小程序才有这样的事件
			pickstart() {
				// #ifdef MP-WEIXIN
				this.moving = true;
				// #endif
			},
			// 标识滑动结束
			pickend() {
				// #ifdef MP-WEIXIN
				this.moving = false;
				this.$emit('pickend',this.timestamp)
				// #endif
			},
			setHours() {
				this.hours = this.generateArray(0, 23);
				this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour));
			},
			setMinutes() {
				this.minutes = this.generateArray(0, 59);
				this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute));
			},
			setSeconds() {
				this.seconds = this.generateArray(0, 59);
				this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second));
			},
			// 生成递进的数组
			generateArray(start, end) {
				// 转为数值格式,否则用户给end-year等传递字符串值时,下面的end+1会导致字符串拼接,而不是相加
				start = Number(start);
				end = Number(end);
				end = end > start ? end : start;
				// 生成数组,获取其中的索引,并剪出来
				return [...Array(end + 1).keys()].slice(start);
			},
			// 用户更改picker的列选项
			change(e) {
				this.valueArr = e.detail.value;
				let i = 0;
				this.hour = this.hours[this.valueArr[i++]];
				this.minute = this.minutes[this.valueArr[i++]];
				this.second = this.seconds[this.valueArr[i++]];
				this.getTimeToSecond(this.hour, this.minute, this.second);
			},
			// 将picker的时间专成秒数
			getTimeToSecond(hour, minute, second) {
				let time = 0;
				if (hour) {
					time += hour * 60 * 60;
				}
				if (minute) {
					time += minute * 60;
				}
				if (second) {
					time += second;
				}
				this.timestamp = time;
			},
			quickSetMinutes(minutes){
				this.valueArr[0] = 0;
				this.valueArr[1] = minutes;
				this.valueArr[2] = 0;
				this.setHours()
				this.setMinutes()
				this.setSeconds()
			}
		}
	}
</script>
复制代码
<style lang="scss">
	.u-picker-body {
		margin: 0;
		width: 100%;
		height: 500rpx;
		overflow: hidden;
		background-color: #ffffff;
		.u-picker-view {
			height: 100%;
			width: 700rpx;
			box-sizing: border-box;
			margin: 0 auto;
			.u-column-item {
				display: flex;
				flex-direction: row;
				align-items: center;
				justify-content: center;
				font-size: 50rpx;
				color: #000000;
				padding: 0 8rpx;
				.c-text {
					font-size: 24rpx;
					padding-left: 8rpx;
				}
			}
		}
		
	}
</style>
复制代码

同样,我们在index.vue中引入它,在此之前,我们还要引入我们魔改后的CountDown组件并传入需要的一些参数(timestamp autoplay ...),之后再引用我们写好的dateSelector组件,记得加上v-if,当starttrue时,说明当前正在倒计时,我们需要把时间选择器隐藏起来。(其实这里使用v-show更好,但是uniapp本身不支持v-show,因为v-if会把元素重新渲染,这个之后再用styledisplay来控制吧,目前先这样子吧)

<template>
	<view class="c-container">
		<note></note>
		<view class="c-countdown" v-if="start">
			<u-count-down ref="uCountDown" :timestamp="timestamp" :autoplay="autoplay" :show-days="false"
				:font-size="100" :separatorSize="65" :pause="pause"></u-count-down>
		</view>
		<date-selector v-else ref="dateSelectorRef" showTimeTag @pickend="pickend"></date-selector>
	</view>
</template>
复制代码
<script>
	export default {
		data() {
			return {
				start: false, // 是否开始倒计时
				pause: false, // 是否处于暂停状态
				autoplay: false,
				timestamp: 300, // 默认五分钟
			};
		},
		methods: {
			pickend(timestamp) {
				this.timestamp = timestamp
			},
		}
	};
</script>
复制代码

4.png

controller-area组件

整个项目的按钮组件都在这里边,它的按钮功能主要是控制倒计时器 CountDown的的进行和暂停以及重置功能

<template>
	<view class="c-controller">
		<!-- 快捷设置按钮 -->
		<view :animation="animationQuickChooseTime" class="c-default-list">
			<view v-for="(time,index) in defaultTimeList" v-if="defaultTimeList.length!==0"
				@tap="selectDefaultTime(time,index)" :key="time">
				<view :class="[index == selectIndex&&time*60==timestamp?'c-time-item-sel':'c-time-item']">
					<view class="c-time-num">
						{{time}}
					</view>
					<view :class="[index == selectIndex&&time*60==timestamp?'c-time-text-sel':'c-time-text']">
						分钟
					</view>
				</view>
			</view>

		</view>
		<!-- 控制按钮 -->
		<view class="c-controller-area">
			<view :animation="animationToLeftData" class="c-button" style="z-index: 1;" @tap="startCountTime()">
				<image v-if="start" class="c-icon" src="../../static/restart.png"></image>
				<image v-else class="c-icon" src="../../static/start.png"></image>
			</view>
			<view :animation="animationToRightData" class="c-button" @tap="stopCountTime()">
				<image v-if="!pause" class="c-icon" src="../../static/stop.png"></image>
				<image v-else class="c-icon" src="../../static/start.png"></image>
			</view>
		</view>
	</view>
</template>
复制代码
<script>
	export default {
		name: "controller-area",
		data() {
			return {
				// 动画
				animationToLeftData: {},
				animationToRightData: {},
				animationQuickChooseTime: {},
				selectIndex: null
			};
		},
		methods: {
			startCountTime() {
				if (this.start) {
					// 正在计时,停止它
					this.$emit('startCountTime', this.start)
					// 执行动画
					let leftButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 500,
						timingFunction: "ease",
						delay: 0
					})
					// 执行动画
					let rightButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 500,
						timingFunction: "ease",
						delay: 0
					})
					// 执行动画
					let quickChooseTimeButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 300,
						timingFunction: "ease",
						delay: 0
					})
					leftButton.translateX(0).step()
					this.animationToLeftData = leftButton.export()
					rightButton.translateX(0).opacity(0).step()
					this.animationToRightData = rightButton.export()
					quickChooseTimeButton.scale(1.1).step()
					quickChooseTimeButton.scale(1).step()
					this.animationQuickChooseTime = quickChooseTimeButton.export()
				} else {
					// 没有计时,开始计时
					this.$emit('startCountTime', this.start)
					// 执行动画
					let leftButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 500,
						timingFunction: "ease",
						delay: 0
					})
					// 执行动画
					let rightButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 500,
						timingFunction: "ease",
						delay: 0
					})
					// 执行动画
					let quickChooseTimeButton = uni.createAnimation({
						transformOrigin: "50% 50%",
						duration: 500,
						timingFunction: "ease",
						delay: 0
					})
					leftButton.translateX(-60).step()
					this.animationToLeftData = leftButton.export()
					rightButton.opacity(1).translateX(60).step()
					this.animationToRightData = rightButton.export()
					quickChooseTimeButton.scale(0).step()
					this.animationQuickChooseTime = quickChooseTimeButton.export()
				}

			},
			stopCountTime() {
				this.$emit('stopCountTime')
			},
			selectDefaultTime(time, index) {
				this.$emit('clickSetTime', time)
				this.selectIndex = index;

			},
		},
		props: {
			timestamp: {
				type: Number,
				default: 0
			},
			defaultTimeList: {
				type: Array,
				default: []
			},
			pause: {
				type: Boolean,
				default: false
			},
			start: {
				type: Boolean,
				default: false
			}
		}
	}
</script>
复制代码
<style lang="scss">
	.c-controller {
		position: absolute;
		top: 750rpx;
		left: 0;
		height: 400rpx;
		width: 100%;

		.c-default-list {
			display: flex;
			justify-content: center;
			position: absolute;
			 top:0;
			   left: 0;
			   right: 0;
			   bottom: 0;
			   margin:auto;
			

			.c-time-item {
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: center;
				width: 125rpx;
				height: 125rpx;
				border-radius: 50%;
				padding: 20rpx;
				margin: 20rpx;
				background-color: #eeeeee;

			}


			.c-time-item-sel {
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: center;
				width: 125rpx;
				height: 125rpx;
				border-radius: 50%;
				padding: 20rpx;
				margin: 20rpx;
				color: #FFFFFF;
				background-color: #007aff;

			}

			.c-time-item,
			.c-time-item-sel {
				.c-time-num {
					font-size: 45rpx;
				}

				.c-time-text {
					font-size: 20rpx;
					color: #808080;
				}

				.c-time-text-sel {
					font-size: 20rpx;
					color: #d0f0f7;
				}
			}


		}


		.c-controller-area {
			height: 200rpx;
			width: 100%;
			position: absolute;
			top: 200rpx;
			left: 0;
			margin: 0;

			.c-button {
				position: absolute;
				display: flex;
				flex-direction: column;
				justify-content: center;
				align-items: center;
				width: 125rpx;
				height: 125rpx;
				border-radius: 50%;
				background-color: #ffffff;
				box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1), 0 3px 10px 0 rgba(0, 0, 0, 0.15);
				top:0;
				  left: 0;
				  right: 0;
				  bottom: 0;
				  margin:auto;

				/* 宽度的一半 */
				.c-icon {
					width: 50rpx;
					height: 50rpx;
				}

			}
		}
	}
</style>
复制代码

继续在index.vue中引入,在script标签中,继续加入一些需要的属性和方法

<template>
	<view class="c-container">
		<note></note>
		<view class="c-countdown" v-if="start">
			<u-count-down ref="uCountDown" :timestamp="timestamp" :autoplay="autoplay" :show-days="false"
				:font-size="100" :separatorSize="65" :pause="pause"></u-count-down>
		</view>
		<date-selector v-else ref="dateSelectorRef" showTimeTag @pickend="pickend"></date-selector>
		<controller-area @startCountTime="startCountTime" @stopCountTime="stopCountTime" @clickSetTime="clickSetTime"
			:defaultTimeList="defaultTime" :timestamp="timestamp" :pause="pause" :start="start" class="">
		</controller-area>
	</view>
</template>
复制代码
// 在index.vue中对应位置进行代码补充
data() {
			return {
				defaultTime: [5, 10, 15],
			};
		},
methods: {
    
			// 开始倒计时
			startCountTime() {
				if (this.start) {
					// 正在计时,停止它
					console.log('restart');
					this.start = false;
					this.pause = false;
				} else {
					// 没有计时,开始计时
					console.log('start');
					this.start = true;
					this.autoplay = true;
					this.pause = false;
				}
			},
			stopCountTime() {
				this.pause = !this.pause;
			},

			clickSetTime(minutes) {
				this.timestamp = minutes * 60;
				this.$refs.dateSelectorRef.quickSetMinutes(minutes)
			},

		}
复制代码

到了这里我们的三大组件已经书写好啦,接下来我们就要在index.vue文件中进行一个引用,和一些逻辑方法的书写

index.vue

附上最终index.vue的代码

<template>
	<view class="c-container">
		<note></note>
		<view class="c-countdown" v-if="start">
			<u-count-down ref="uCountDown" :timestamp="timestamp" :autoplay="autoplay" :show-days="false"
				:font-size="100" :separatorSize="65" :pause="pause"></u-count-down>
		</view>
		<date-selector v-else ref="dateSelectorRef" showTimeTag @pickend="pickend"></date-selector>
		<controller-area @startCountTime="startCountTime" @stopCountTime="stopCountTime" @clickSetTime="clickSetTime"
			:defaultTimeList="defaultTime" :timestamp="timestamp" :pause="pause" :start="start" class="">
		</controller-area>
	</view>
</template>
复制代码
<script>
	export default {
		data() {
			return {
				start: false, // 是否开始倒计时
				pause: false, // 是否处于暂停状态
				autoplay: false,
				timestamp: 300, // 默认五分钟
				defaultTime: [5, 10, 15],
			};
		},
		methods: {
			// 开始倒计时
			startCountTime() {
				if (this.start) {
					// 正在计时,停止它
					console.log('restart');
					this.start = false;
					this.pause = false;
				} else {
					// 没有计时,开始计时
					console.log('start');
					this.start = true;
					this.autoplay = true;
					this.pause = false;
				}
			},
			pickend(timestamp) {
				this.timestamp = timestamp
			},
			stopCountTime() {
				this.pause = !this.pause;
			},

			clickSetTime(minutes) {
				this.timestamp = minutes * 60;
				this.$refs.dateSelectorRef.quickSetMinutes(minutes)
			},

		}
	};
</script>
复制代码
<style lang="scss" scoped>
	.c-container {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
	}

	.c-countdown {
		margin-top: 50rpx;
	}
</style>
复制代码

到这里 终于写完了!看看项目效果如何吧!

index.png

最后

该小程序项目完整的GIT仓库地址:github.com/eatmans/cod…

别忘了 ⭐️⭐️⭐️

最后再贴个小程序码

扫码_搜索联合传播样式-微信标准绿版.png

感谢大家的阅读,希望看完大家能有所收获! 如有不足之处,请多多指教!

文章分类
前端
文章标签