popup 弹窗 picker-view 滚动选择器

本文介绍了如何在uniapp中自定义popup类型的picker-view滚动选择器,包括组件结构、props配置、方法实现及不同模式展示。重点讲解了遮罩层、动画效果和picker-view的选择器功能。

popup 弹窗 picker-view 滚动选择器

组件链接: https://ext.dcloud.net.cn/plugin?id=7960

<m-picker mode="bottom" :show.sync="show" :range="list" @confirm="confirm" />

一、自定义popup类型的picker-view滚动选择器

  1. 组件是基于uniapp开发的,过度动画使用了uniapp的<uni-transition></uni-transition>组件

Mark 遮罩层

<uni-transition v-if="mark" ref="aniMark" custom-class="transition" mode-class="fade" :styles="stylesMark" :show="show" @click="clickMark" />

popup内容

<uni-transition ref="ani" custom-class="transition" :mode-class="modeClass" :styles="styles" :show="show">
	<view class="m-picker">
		<view class="m-picker__case" :style="{
			background: bgColor,
			borderTopLeftRadius: radius[0] + 'rpx',
			borderTopRightRadius: radius[1] + 'rpx',
			borderBottomLeftRadius: radius[2] + 'rpx',
			borderBottomRightRadius: radius[3] + 'rpx',
			paddingBottom: model == 'bottom' ? 'calc(constant(safe-area-inset-bottom) + 30rpx)' : '30rpx',
			paddingBottom: model == 'bottom' ? 'calc(env(safe-area-inset-bottom) + 30rpx)' : '30rpx'
		}">
			<!-- 确认/取消 按钮 -->
			<slot v-if="model == 'bottom'" name="handle">
				<view class="handle">
					<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
					<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
				</view>
			</slot>
			<!-- 内容 -->
			<slot>
				
			</slot>
			<!-- 确认/取消 按钮 -->
			<slot v-if="model != 'bottom'" name="handle">
				<view class="handle">
					<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
					<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
				</view>
			</slot>
		</view>
	</view>
</uni-transition>

picker-view 组件

<view class="content" :style="{height: `${height}rpx`}">
	<picker-view class="picker-view" :indicator-class="indicatorClass" :indicator-style="indicatorStyle" :value="pickerValue" @change="bindChange" @pickstart="pickstart" @pickend="pickend">
		<picker-view-column v-for="(rangeItem, rangeIndex) in range" :key="rangeIndex">
			<view class="picker-view__item" v-for="(item, index) in rangeItem" :key="index">
				{{ item[rangeKey] }}
			</view>
		</picker-view-column>
	</picker-view>
</view>

props.js

const props = {
	// 是否显示遮罩
	mark: {
		type: Boolean,
		default() {
			return true
		}
	},
	// 点击遮罩是否关闭
	markClose: {
		type: Boolean,
		default() {
			return true
		}
	},
	// 点击按钮是否关闭
	btnClose: {
		type: Boolean,
		default() {
			return true
		}
	},
	// 是否显示弹窗
	show: {
		type: Boolean,
		default() {
			return false
		}
	},
	// 数据
	range: {
		type: Array,
		default: []
	},
	// picker-item显示内容的key
	rangeKey: {
		type: String,
		default: 'label'
	},
	// 设置选择器中间选中框的样式
	indicatorStyle: {
		type: String,
		default: 'height: 50px;'
	},
	// 设置选择器中间选中框的类名,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
	indicatorClass: {
		type: String,
		default: ''
	},
	// cancel文字
	cancelText: {
		type: String,
		default: '取消'
	},
	// confirm文字
	confirmText: {
		type: String,
		default: '确定'
	},
	// cancel样式style
	cancelStyle: {
		type: Object,
		default: {}
	},
	// confirm样式style
	confirmStyle: {
		type: Object,
		default: {}
	},
	// 内容高度 rpx
	height: {
		type: Number,
		default: 500
	},
	// 圆角 rpx
	borderRadius: {
		type: Number,
		default: 16
	},
	// 背景颜色
	bgColor: {
		type: String,
		default: '#FFFFFF'
	},
	// mark 背景颜色
	markBgColor: {
		type: String,
		default: '#00000080'
	},
	// 方向 top/bottom/center
	mode: {
		type: String,
		default: 'bottom'
	}
}

export default props

接收样式参数

import props from './props.js'
export default {
	name:"m-picker",
	props,
	computed: {
		model() {
			if (this.mode == 'top') return 'top';
			else if (this.mode == 'bottom') return 'bottom';
			else if (this.mode == 'center') return 'center';
			else return 'bottom';
		},
		stylesMark() {
			return {
				position: 'fixed',
				top: 0,
				left: 0,
				right: 0,
				bottom: 0,
				zIndex: 99,
				backgroundColor: this.markBgColor,
			}
		},
		styles() {
			const top = {
				position: 'fixed',
				left: 0,
				right: 0,
				top: 0,
				zIndex: 100
			}
			const bottom = {
				position: 'fixed',
				left: 0,
				right: 0,
				bottom: 0,
				zIndex: 100
			}
			const center = {
				position: 'fixed',
				left: '50%',
				top: '50%',
				width: '90vw',
				transform: 'translate(-50%, -50%)',
				zIndex: 100
			}
			if (this.model == 'top') return top;
			else if (this.model == 'bottom') return bottom;
			else if (this.model == 'center') return center;
			else return bottom;
		},
		radius() {
			const borderRadius = this.borderRadius;
			if (this.model == 'top') return [0, 0, borderRadius, borderRadius];
			else if (this.model == 'bottom') return [borderRadius, borderRadius, 0, 0];
			else if (this.model == 'center') return [borderRadius, borderRadius, borderRadius, borderRadius];
			else return [0, 0, 0, 0];
		},
		modeClass() {
			if (this.model == 'top') return ['fade', 'slide-top'];
			else if (this.model == 'bottom') return ['fade', 'slide-bottom'];
			else if (this.model == 'center') return 'fade';
			else return ['fade', 'slide-bottom'];
		}
	},
}

二、组件说明

Methods

方法称名说明参数
confirm点击右侧按钮[]
cancel点击左侧按钮

字段

字段类型默认值说明
markBooleantrue是否显示遮罩
markCloseBooleantrue点击遮罩是否关闭
btnCloseBooleantrue点击按钮是否关闭
showBooleanfale是否显示弹窗
rangeArray[]数据
rangeKeyString‘label’picker-item显示内容的key
indicatorStyleString‘height: 50px;’设置选择器中间选中框的样式
indicatorClassString’ ’设置选择器中间选中框的类名,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
cancelTextString取消cancel文字
confirmTextString确定confirm文字
cancelStyleObject{}cancel样式style
confirmlStyleObject{}confirm样式style
heightNumber500内容高度 rpx
borderRadiusNumber16圆角 rpx
bgColorString‘#FFFFFF’背景颜色
modeStringbottom方向 top/bottom/center

Slots

称名说明
default会覆盖默认的picker-view
handle按钮部分

三、 图片演示

bottom

mode=bottom

top

mode=top

center

mode=center

四、完整代码

<template>
	<view class="pricke">
		<uni-transition v-if="mark" ref="aniMark" custom-class="transition" mode-class="fade" :styles="stylesMark" :show="show" @click="clickMark"></uni-transition>
		<uni-transition ref="ani" custom-class="transition" :mode-class="modeClass" :styles="styles" :show="show">
			<view class="m-picker">
				<view class="m-picker__case" :style="{
					background: bgColor,
					borderTopLeftRadius: radius[0] + 'rpx',
					borderTopRightRadius: radius[1] + 'rpx',
					borderBottomLeftRadius: radius[2] + 'rpx',
					borderBottomRightRadius: radius[3] + 'rpx',
					paddingBottom: model == 'bottom' ? 'calc(constant(safe-area-inset-bottom) + 30rpx)' : '30rpx',
					paddingBottom: model == 'bottom' ? 'calc(env(safe-area-inset-bottom) + 30rpx)' : '30rpx'
				}">
					<slot v-if="model == 'bottom'" name="handle">
						<view class="handle">
							<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
							<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
						</view>
					</slot>
					<slot>
						<view class="content" :style="{height: `${height}rpx`}">
							<picker-view class="picker-view" :indicator-class="indicatorClass" :indicator-style="indicatorStyle" :value="pickerValue" @change="bindChange" @pickstart="pickstart" @pickend="pickend">
								<picker-view-column v-for="(rangeItem, rangeIndex) in range" :key="rangeIndex">
									<view class="picker-view__item" v-for="(item, index) in rangeItem" :key="index">
										{{ item[rangeKey] }}
									</view>
								</picker-view-column>
							</picker-view>
						</view>
					</slot>
					<slot v-if="model != 'bottom'" name="handle">
						<view class="handle">
							<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
							<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
						</view>
					</slot>
				</view>
			</view>
		</uni-transition>
	</view>
</template>

<script>
	import props from './props.js'
	export default {
		name:"m-picker",
		props,
		data() {
			return {
				pickerValue: [],
				pickMove: false,
			};
		},
		computed: {
			model() {
				if (this.mode == 'top') return 'top';
				else if (this.mode == 'bottom') return 'bottom';
				else if (this.mode == 'center') return 'center';
				else return 'bottom';
			},
			stylesMark() {
				return {
					position: 'fixed',
					top: 0,
					left: 0,
					right: 0,
					bottom: 0,
					zIndex: 99,
					backgroundColor: this.markBgColor,
				}
			},
			styles() {
				const top = {
					position: 'fixed',
					left: 0,
					right: 0,
					top: 0,
					zIndex: 100
				}
				const bottom = {
					position: 'fixed',
					left: 0,
					right: 0,
					bottom: 0,
					zIndex: 100
				}
				const center = {
					position: 'fixed',
					left: '50%',
					top: '50%',
					width: '90vw',
					transform: 'translate(-50%, -50%)',
					zIndex: 100
				}
				if (this.model == 'top') return top;
				else if (this.model == 'bottom') return bottom;
				else if (this.model == 'center') return center;
				else return bottom;
			},
			radius() {
				const borderRadius = this.borderRadius;
				if (this.model == 'top') return [0, 0, borderRadius, borderRadius];
				else if (this.model == 'bottom') return [borderRadius, borderRadius, 0, 0];
				else if (this.model == 'center') return [borderRadius, borderRadius, borderRadius, borderRadius];
				else return [0, 0, 0, 0];
			},
			modeClass() {
				if (this.model == 'top') return ['fade', 'slide-top'];
				else if (this.model == 'bottom') return ['fade', 'slide-bottom'];
				else if (this.model == 'center') return 'fade';
				else return ['fade', 'slide-bottom'];
			}
		},
		watch: {
			range(val) {
				if (val.length) {
					let arr = []
					for (let index in this.range) {
						arr.push(0);
					}
					this.pickerValue = arr;
				}
			}
		},
		created() {
			if (this.range && this.range.length) {
				this.pickerValue = [];
				for (let index in this.range) {
					this.pickerValue.push(0)
				}
			}
		},
		methods: {
			// 关闭
			close() {
				if (!this.pickMove) this.$emit('update:show', false)
			},
			// 取消
			cancel() {
				this.$emit('cancel')
				if (this.btnClose) this.close()
			},
			// 确定
			confirm() {
				this.$emit('confirm', this.pickerValue);
				if (this.btnClose) this.close()
			},
			// 点击遮罩
			clickMark() {
				if (this.markClose) this.close()
			},
			bindChange (e) {
				const val = e.detail.value;
				this.pickerValue = val;
			},
			pickstart() {
				this.pickMove = true;
			},
			pickend() {
				this.pickMove = false;
			}
		}
	}
</script>

<style lang="scss">
.m-picker {
	width: 100%;
	height: 100%;
	
	&__case {
		padding-left: 56rpx;
		padding-right: 56rpx;
		padding-top: 30rpx;
		padding-bottom: 30rpx;
		
		.handle {
			width: 100%;
			display: flex;
			align-items: center;
			justify-content: space-between;
			
			.button {
				font-size: 48rpx;
				font-family: PingFang SC;
				font-weight: 500;
				color: #000000;
				
				&.cancel {
					
				}
				
				&.confirm {
					color: #FF8833;
				}
			}
		}
		
		.content {
			width: 100%;
			height: 400rpx;
		}
		
	}
}

.picker-view {
	width: 100%;
	height: 100%;
	
	&__item {
		display: flex;
		align-items: center;
		justify-content: center;
		font-size: 48rpx;
	}
}
</style>
<think>我们正在使用uni-app的uni-popuppicker-view组件,并希望实现在弹出选择器时默认选中指定的年份。根据引用[2]我们知道,picker-view需要设置高度才能显示,并且我们使用picker-view-column来定义列。关键点:1.设置picker-view的value属性(数组,每列选中的下标)来指定默认选中项。2.年份数据需要是一个数组,比如从1900到当前年份。3.在打开弹出层的时候,根据我们想要的默认年份,计算出在年份数组中的下标,然后设置value数组。步骤:1.准备年份数据(假设为1900-当前年份)2.在data中定义pickerValue(用于绑定picker-view的value属性)和年份数组3.设置默认年份,比如我们希望默认选中2020年,那么就要在年份数组中找到2020对应的下标4.在打开popup的时候,将pickerValue设置为[下标](因为picker-view的value是一个数组,单列所以只有一个元素)注意:picker-view的value属性是一个数组,数组的每个元素代表每一列选中的下标(从0开始)。代码示例:1.模板部分: ```html<uni-popupref="popup" type="bottom"><viewclass="popup-content"><picker-view:value="pickerValue"@change="pickerChange"indicator-style="height:50px;"style="width:100%; height:300px;"><picker-view-column><viewv-for="(item, index) inyears":key="index"style="line-height:50px; text-align:center;">{{item}}年</view></picker-view-column></picker-view></view></uni-popup> ```2.数据部分:```javascriptdata() {return{years:[], //年份数组,比如[1900,1901,...,当前年份]pickerValue:[0], //默认选中第一个selectedYear:null//选中的年份}}, created() {//初始化年份数组this.initYears();//设置默认选中的年份(比如2020)this.setDefaultYear(2020); },methods:{initYears() {//生成年份数组,从1900到当前年份constcurrentYear= newDate().getFullYear();letyears= [];for(leti =1900;i <=currentYear; i++) {years.push(i);}this.years =years;},setDefaultYear(year){//在years数组中找到year对应的下标const index= this.years.findIndex(item =>item ===year);if(index!== -1){this.pickerValue =[index]; //注意:pickerValue是数组,单列所以只有一个元素}},//打开popup,并设置默认选中的年份(可以在打开的时候重新设置默认值)openPopup() {this.$refs.popup.open();//假设我们想在每次打开的时候都默认选中2020年,可以在这里调用setDefaultYear//this.setDefaultYear(2020);},//picker改变事件pickerChange(e){const value= e.detail.value[0];//单列,取第一个值(下标)this.selectedYear= this.years[value];} }```3.样式部分(参考引用[2]):注意:picker-view需要设置高度,我们在上面的样式中设置了高度为300px(也可以使用rpx)。这样,在打开popup的时候,picker-view就会默认选中我们设置的年份(2020年)对应的位置。但是,如果我们希望每次打开popup时都根据某个变量来设置默认选中的年份,可以在openPopup方法中动态设置。例如,我们有一个变量currentYear,每次打开popup时,我们希望默认选中currentYear对应的年份,那么可以这样:```javascriptopenPopup(){this.$refs.popup.open();//假设currentYear是当前选中的年份(可能是之前选中的,也可能是初始值)this.setDefaultYear(this.currentYear); }```注意:在setDefaultYear方法中,我们改变了pickerValue,这会触发picker-view的重新渲染,从而选中指定的年份。相关问题:1.如何实现多列选择器(比如年月日)的默认选中?2.如何动态更新picker-view的数据源?3.如何解决picker-view在真机上滚动不流畅的问题?引用说明: [^1]提供了横屏设置和弹窗滚动问题的解决方案,但与本问题关联不大。[^2]提醒我们picker-view需要设置高度,否则不显示。</think>在 `uni-popup` 结合 `picker-view` 组件中实现默认选中指定年份的方法如下,核心是通过 `picker-view` 的 `value` 属性绑定选中项索引: ### 解决方案步骤 #### 1. 准备年份数据 ```javascript data() { return { years: [], // 年份数组(如:[2020, 2021, 2022...]) pickerValue: [0], // 默认选中索引(数组形式) selectedYear: null // 选中的年份 } }, mounted() { // 生成年份数据(示例:2000-2030) const startYear = 2000; const endYear = 2030; for (let i = startYear; i <= endYear; i++) { this.years.push(i); } // 设置默认选中2023年(下标位置) this.setDefaultYear(2023); }, methods: { // 设置默认年份 setDefaultYear(year) { const index = this.years.findIndex(y => y === year); if (index !== -1) { this.pickerValue = [index]; // 更新选中索引 this.selectedYear = year; // 更新选中值 } }, // 打开弹窗 openPicker() { this.$refs.popup.open(); } } ``` #### 2. 模板绑定 ```html <uni-popup ref="popup" type="bottom"> <view class="popup-content"> <!-- 绑定value和change事件 --> <picker-view :value="pickerValue" @change="pickerChange" indicator-style="height: 50px;" style="width: 100%; height: 300rpx;" > <picker-view-column> <view v-for="(year, index) in years" :key="index" class="picker-item" > {{ year }}年 </view> </picker-view-column> </picker-view> </view> </uni-popup> <!-- 触发按钮 --> <button @click="openPicker">选择年份</button> ``` #### 3. 处理选择事件 ```javascript methods: { // 选择器变化事件 pickerChange(e) { const index = e.detail.value[0]; this.selectedYear = this.years[index]; }, // 打开弹窗时重置默认值 openPicker() { this.setDefaultYear(2023); // 每次打开都默认选中2023 this.$refs.popup.open(); } } ``` #### 4. 关键样式(必加) ```css /* 确保picker-view有高度 */ picker-view { height: 300rpx; width: 100%; } /* 选中项样式 */ .picker-item { height: 50px; line-height: 50px; text-align: center; } ``` ### 实现原理 1. **数据绑定** 通过 `picker-view` 的 `:value="pickerValue"` 属性绑定选中索引,索引值为年份在数组中的位置 2. **索引计算** - 使用 `findIndex` 查找目标年份在数组中的位置 - 例如:年份数组 `[2020,2021,2022,2023]` 中: - 2020 → 索引0 - 2023 → 索引3 3. **默认值触发时机** - 在 `openPicker()` 方法中调用 `setDefaultYear()`,确保每次打开弹窗都重置默认值 - 可在 `mounted` 中设置初始默认值 ### 相关问题 1. 如何实现多列选择器(年份+月份)的默认选中? **方案**:扩展 `pickerValue` 为数组 `[yearIndex, monthIndex]`,并在 `setDefaultYear` 中计算双索引 2. 动态数据源如何确保默认值生效? **方案**:在数据加载完成的回调中调用 `setDefaultYear()`,例如: ```javascript loadData().then(() => { this.setDefaultYear(2023); }); ``` 3. 如何保存用户选择并同步到页面? **方案**:通过 `@change` 事件更新 `selectedYear` 变量,并绑定到页面显示区域: ```html <view>当前选中: {{ selectedYear }}年</view> ``` > 引用说明: > [^2] 指出 `picker-view` 必须设置高度才能正常显示 > 示例中通过 `style="height: 300rpx"` 确保组件可见性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DevilAngelia

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

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

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

打赏作者

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

抵扣说明:

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

余额充值