Picker选择器(同时适用PC和手机端)

前言:

        开发项目中有些页面是手机和pc两端显示,然后我用了vant的popup和picker,发现其组件的定位是position:fixed,这就导致在pc端这个popup和picker是全屏的非常不好看,最主要的是,这些组件在pc端和手机端的效果是不一样,手机端可以滑动,在pc就不行,也懒得去找了,所以自己写了一个在pc和手机端都可以滑动选择的弹出层picker!

效果图:

友情提示:

        应开发需求,我分享的这picker代码是position: absolute绝对定位,相对于父盒子的大小来展示内容的,需要不同定位可以自己改css。

参考代码

(1)父组件代码

<template>
  <div class="home">
    <el-button type="primary" @click="showPickerBtn" >显示picker</el-button>
    <div style="position: relative; width: 375px;height: 90vh;" >
      <div>父盒子</div>
      <div>选中的日期:{{ checkedDate }}</div>
      <DatePicker v-model="showPicker" @dateConfirm="dateConfirm" ></DatePicker>
    </div>
  </div>
</template>

<script>
import DatePicker from '@/components/DatePicker.vue'

export default {
  name: 'HomeView',
  components: {
    DatePicker
  },
  data() {
    return {
      showPicker: false,
      checkedDate: '' // 接收picker返回的日期
    }
  },
  methods:{
    showPickerBtn() {
      this.showPicker = !this.showPicker
    },
    dateConfirm(date) {
      this.checkedDate = date
    }
  }
}
</script>

(2)picke代码

<!-- 日期选择器 -->
<template>
	<!-- elementui 内置过渡动画 (可以自己写想要的过渡效果) -->
	<transition name="el-fade-in-linear">
		<div class="container" v-show="showPicker" >
			<!-- 遮罩放冒泡 -->
			<div class="modal" @click.stop="modalClick"></div>
			<transition name="el-zoom-in-bottom">
				<div v-show="showPicker" class="contentBox" @click.stop>
					<div class="btnBox">
						<span class="cancleBtn" @click="popBtn(0)">取消</span>
						<span class="confirmBtn" @click="popBtn(1)">确定</span>
					</div>
					<div class="optionBox">
						<div class="curCheckBox"></div>
						<!-- 年 -->
						<div ref="yearScrollRef" class="arrange" 
							@touchstart="onTouchStart($event, 0)"
							@mousedown="startDrag($event, 0)"
							@scroll="curCheckDate($event,0)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in yearOption" :key="index">
								{{ item }}
								<span style="font-size: 16px;" v-if="unit">年</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
						<!-- 月 -->
						<div ref="monthScrollRef" class="arrange"
							@touchstart="onTouchStart($event, 1)"
							@mousedown="startDrag($event, 1)"
							@scroll="curCheckDate($event, 1)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in 12" :key="index">
								<span v-if="item < 10">0</span>
								{{ item }}
								<span v-if="unit">月</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
						<!-- 日 -->
						<div ref="dayScrollRef" class="arrange"
							@touchstart="onTouchStart($event, 2)"
							@mousedown="startDrag($event, 2)"
							@scroll="curCheckDate($event, 2)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in dayOption" :key="index">
								<span v-if="item < 10">0</span>
								{{ item }}
								<span v-if="unit">日</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
						<!-- 时 -->
						<div v-if="type==='dateTime'" ref="hourScrollRef" class="arrange" 
							@touchstart="onTouchStart($event, 3)"
							@mousedown="startDrag($event, 3)"
							@scroll="curCheckDate($event, 3)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in 24" :key="index">
								<span v-if="index < 10">0</span>
								{{ index }}
								<span v-if="unit">时</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
						<!-- 分 -->
						<div v-if="type==='dateTime'" ref="minuteScrollRef" class="arrange"
							@touchstart="onTouchStart($event, 4)"
							@mousedown="startDrag($event, 4)"
							@scroll="curCheckDate($event, 4)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in 60" :key="index">
								<span v-if="item < 10">0</span>
								{{ item }}
								<span v-if="unit">分</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
						<!-- 秒 -->
						<div v-if="type==='dateTime'" ref="secondScrollRef" class="arrange" 
							@touchstart="onTouchStart($event, 5)" 
							@mousedown="startDrag($event, 5)" 
							@scroll="curCheckDate($event, 5)"
						>
							<div class="topVagueBox"></div>
							<div class="optionItem" v-for="(item, index) in 60" :key="index">
								<span v-if="item < 10">0</span>
								{{ index }}
								<span v-if="unit">秒</span>
							</div>
							<div class="bottomVagueBox"></div>
						</div>
					</div>
				</div>
			</transition>
		</div>
	</transition>
</template>

<script>
export default {
	name:'DatePicker',
	// 自定义v-model
	model: {
		prop: 'showPicker',
		event: 'hidePicker'
	},
	props: {
		// 是否显示picker
		showPicker: {
			type: Boolean,
			default: true,
		},
		// 是否显示单位
		unit: {
			type: Boolean,
			default: true,
		},
		// 时间类型 date=="YYYY-MM-DD" dateTime=="YYYY-MM-DD HH:mm:ss"
		type: {
			type: String,
            default: 'date',
		}
	},
	data() {
		return {
			// 年
			yearOption: [],
			// 月
			monthOption: [],
			// 日
			dayOption: [],
			// 选择的日期
			checkedDate: [],
			timer: null, // 防抖
			isStop: false
		};
	},
	mounted() {
		const currentDate = new Date();
		let currentYear = currentDate.getFullYear()
		this.yearOption = Array(10).fill(null).map((_, i) => currentYear + i + ''); // 本年-后10年
		this.dayOption = Array(31).fill(null).map((_, i) => i + 1); // 31天
		this.defaultDate()
	},	
	methods: {
		// 默认日期
		defaultDate() {
			if(this.type==='date') {
				this.checkedDate = [ this.yearOption[0], '01', '01' ]
			} else {
				this.checkedDate = [ this.yearOption[0], '01', '01', '00', '00', '00' ]
			}
		},
		// 获取对应scroll的ref
		getScrollTopRef(type) {
			const scrollContainerRef = type===0 ?  this.$refs.yearScrollRef 
			:  type===1 ? this.$refs.monthScrollRef 
			:  type===2 ? this.$refs.dayScrollRef 
			:  type===3 ? this.$refs.hourScrollRef 
			:  type===4 ? this.$refs.minuteScrollRef 
			: this.$refs.secondScrollRef
			return scrollContainerRef
		},
		// 手指按下
		onTouchStart(event, type) {
			// event.preventDefault();
			const scrollContainer = this.getScrollTopRef(type)
			// 记录起始位置
			const startY = event.touches[0].clientY;
			const startScrollTop = scrollContainer.scrollTop;
			// 手指移动
			const onTouchMove = (moveEvent) => {
				const dy = moveEvent.touches[0].clientY - startY;
				scrollContainer.scrollTop = startScrollTop - dy;
			};
			// 手指松开
			const onTouchEnd = () => {
				scrollContainer.scrollTop = Math.round(scrollContainer.scrollTop / 50) * 50;
				this.isStop = true
				document.removeEventListener('touchmove', onTouchMove);
				document.removeEventListener('touchend', onTouchEnd);
				
			};
			document.addEventListener('touchmove', onTouchMove);
			document.addEventListener('touchend', onTouchEnd);
		},
		// 鼠标左键按下
		startDrag(event,type) {
			event.preventDefault();
			const scrollContainer = this.getScrollTopRef(type)
			const startY = event.clientY;
			const startScrollTop = scrollContainer.scrollTop;
			// 鼠标移动
			const onMouseMove = (moveEvent) => {
				const dy = moveEvent.clientY - startY;
				scrollContainer.scrollTop = startScrollTop - dy
			};
			// 鼠标松开
			const onMouseUp = () => {
				scrollContainer.scrollTop = Math.round(scrollContainer.scrollTop / 50) * 50
				document.removeEventListener('mousemove', onMouseMove);
				document.removeEventListener('mouseup', onMouseUp);
			};

			document.addEventListener('mousemove', onMouseMove);
			document.addEventListener('mouseup', onMouseUp);
		},
		// 滑动 type:0年 1月 2日 3时 4分 5秒
		curCheckDate(event, type) {
			clearInterval(this.timer)
			this.timer = setTimeout(() => {
				const scrollContainer = this.getScrollTopRef(type)
				let num = scrollContainer.scrollTop / 50
				if(this.isStop){
					scrollContainer.scrollTop = Math.round(num) * 50;
					this.isStop = false
				}
				const itemHeight = 50;
				let curCheckedIndex = Math.round(event.target.scrollTop / itemHeight);
				// 选择二月
				if(type===1 && curCheckedIndex===1) {
					this.dayOption = Array(28).fill(null).map((_, i) => i + 1); // 28天
				} 
				// 选择非二月
				if(type===1 && curCheckedIndex!=1) {
					if([1,3,5,7,8,10,12].includes(curCheckedIndex+1)) {
						this.dayOption = Array(31).fill(null).map((_, i) => i + 1); // 31天
					} else {
						this.dayOption = Array(30).fill(null).map((_, i) => i + 1)
					}
				}
				// 补0 
				let time = curCheckedIndex<10 ? '0' + (curCheckedIndex+1) : '' + (curCheckedIndex+1)
				let time1 = curCheckedIndex<10 ? '0' + (curCheckedIndex+1) : '' + (curCheckedIndex+1)
				// 对应赋值
				if(type===0) {
					this.checkedDate[0] = this.yearOption[curCheckedIndex]
				} else if(type===1) {
					this.checkedDate[1] = time
				} else if(type===2) {
					this.checkedDate[2] = time
				} else if(type===3) {
					this.checkedDate[3] = time1
				} else if(type===4) {
					this.checkedDate[4] = time
				} else {
					this.checkedDate[5] = time1
				}
			}, 200);

		},
		// 弹窗按钮
		popBtn(type) {
			if(type === 1) {
				this.$emit('dateConfirm', this.checkedDate);
			}
			this.$emit('hidePicker', false);
		},
		// 点击模态框
		modalClick() {
			this.$emit('hidePicker', false);
		}
	}
}
</script>

<style lang="scss" scoped>
.container {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	user-select: none;
  	z-index: 3000; /* 确保弹出层位于其他内容之上 */

	.modal {
		width: 100%;
		height: 100%;
		background: rgba(0, 0, 0, 0.5);
	}

	.contentBox {
		position: absolute;
		bottom: 0;
		left: 0;
		width: 100%;
		background-color: #fff;
		border-radius: 8px 8px 0 0;
		padding: 12px;
		box-sizing: border-box;

		.btnBox {
			display: flex;
			justify-content: space-between;
			margin-bottom: 12px;
			cursor: pointer;

			.cancleBtn {
				font-size: 16px;
				color: #969799;
			}

			.confirmBtn {
				font-size: 16px;
				color: #19a7fc;
			}
		}

		.optionBox {
			display: flex;
			align-items: center;
			justify-content: space-around;
			position: relative;
			cursor: grab;


			.curCheckBox {
				position: absolute;
				left: 0;
				top: 100px;
				width: 100%;
				height: 50px;
				border-top: 1px solid #d8d9da;
				border-bottom: 1px solid #d8d9da;
			}

			.arrange {
				position: relative;
				height: 250px;
				width: 80px;
				overflow: scroll;

				.optionItem {
					display: flex;
					align-items: center;
					justify-content: center;
					height: 50px;
					font-size: 16px;
					line-height: 16px;
					color: #817e7e;
					font-weight: 600;
				}

				.topVagueBox {
					position: sticky;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100px;
                    background: rgba(255, 255, 255, 0.7);
				}

				.bottomVagueBox {
					position: sticky;
                    bottom: 0;
                    left: 0;
                    width: 100%;
                    height: 100px;
                    background: rgba(255, 255, 255, 0.7);
				}
			}

			.arrange::-webkit-scrollbar {
				display: none;
			}
		}
	}
}
</style>

总结:

1、代码有注释,有问题可以在评论区留言,看到会及时回复。

2、需要改进的地方可以自己动动小手改一改,工时太赶,有空会优化再发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值