低代码可视化-uniapp下拉组件-代码生成器

uniapp下拉组件在官方提供的例子通常是用picker组件来实现,但希望像html 里select组件一样,点击后直接在下方显示。为此我们扩展了select组件。

扩展组件库代码

<template>
	<view class="diy-selectdown">
		<view class="diy-selectdown__content" :class="direction" :style="[contentStyle, {
			transition: `opacity ${duration / 1000}s linear`,
			'--hover-color':activeColor,
			paddingLeft: menuPositionData.left+'px',
			height: active?contentHeight + 'px':'0px'
		}]" @tap="maskClick" @touchmove.stop.prevent>
			<view :style="{width:menuPositionData.width+'px'}">
				<view @tap.stop.prevent class="diy-selectdown__content__popup" :style="[popupStyle]">
					<block v-if="!$slots.default && !$slots.$default">
						<scroll-view scroll-y="true" :style="{
							height: $tools.addUnit(itemHeight*(showLength!=0?showLength:list.length))
						}">
							<view class="diy-dropdown-items">
								<view>
									<view class="diy-dropdown-item" hover-class="diy-dropdown-item-hover"
										hover-stay-time="150" @tap="cellClick(item[valueName])" :arrow="false"
										v-for="(item, index) in list" :key="index" :style="{
										
										color: selectValue == item[valueName] ? activeColor : inactiveColor
									}">
										{{item[labelName]}}
									</view>
								</view>
							</view>
						</scroll-view>
					</block>
					<slot v-else />
				</view>
			</view>
			<view v-if="isMask" class="diy-selectdown__content__mask"></view>
		</view>
	</view>
</template>

<script>
	export default {
		name: 'diy-selectdown',
		emits: ["open", "close", "change"],
		props: {
			triggerId: {
				type: String,
				default: 'selectdownId'
			},
			modelValue: {
				type: Boolean,
				default: false
			},
			defaultValue: {
				type: [String, Number],
				default: ''
			},
			backgroundColor: {
				type: String,
				default: '#fff'
			},
			// 列数据
			list: {
				type: Array,
				default () {
					return [];
				}
			},
			// 自定义value属性名
			valueName: {
				type: String,
				default: 'value'
			},
			// 自定义label属性名
			labelName: {
				type: String,
				default: 'label'
			},
			// 菜单标题和选项的激活态颜色
			activeColor: {
				type: String,
				default: '#19be6b'
			},
			// 菜单标题和选项的未激活态颜色
			inactiveColor: {
				type: String,
				default: '#606266'
			},
			// 点击遮罩是否关闭菜单
			closeOnClickMask: {
				type: Boolean,
				default: true
			},
			// 是否遮罩
			isMask: {
				type: Boolean,
				default: false
			},
			// 点击当前激活项标题是否关闭菜单
			closeOnClickSelf: {
				type: Boolean,
				default: true
			},
			direction: {
				type: String,
				default: 'down'
			},
			// 过渡时间
			duration: {
				type: [Number, String],
				default: 300
			},
			// 标题菜单的高度,单位任意,数值默认为rpx单位
			itemHeight: {
				type: [Number, String],
				default: 76
			},
			// 如果默认显示0个时,显示全部 
			showLength: {
				type: Number,
				default: 0
			},
			// 下拉出来的内容部分的圆角值
			borderRadius: {
				type: [Number, String],
				default: 10
			},
			offsetTop:{
				type:Number,
				default:0
			}
		},
		watch: {
			modelValue: {
				immediate: true,
				handler(val) {
					if (val) {
						setTimeout(() => this.open(), 2);
					} else {
						setTimeout(() => this.close(), 2);
					}
				}
			},
		},
		data() {
			return {
				selectValue: this.defaultValue,
				showDropdown: true, // 是否打开下来菜单,
				active: false, // 下拉菜单的状态
				// 外层内容的样式,初始时处于底层,且透明
				contentStyle: {
					zIndex: -1,
					opacity: 0
				},
				// 让某个菜单保持高亮的状态
				highlightIndex: 99999,
				menuPositionData: {
					left: 0,
					width: 375,
					top: 0,
					height: 40
				},
				topheight: 40,
				contentHeight: 0
			}
		},
		computed: {
			// 下拉出来部分的样式
			popupStyle() {
				let style = {};
				// 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏
				if (this.direction == 'up') {
					style.transform = `translateY(${this.active ? 0 : '100%'})`
					style.borderRadius =
						`${this.$tools.addUnit(this.borderRadius)} ${this.$tools.addUnit(this.borderRadius)} 0 0 `;
				} else {
					style.transform = `translateY(${this.active ? 0 : '-100%'})`
					style.borderRadius =
						`0 0 ${this.$tools.addUnit(this.borderRadius)} ${this.$tools.addUnit(this.borderRadius)}`;
				}
				style['backgroundColor'] = this.backgroundColor;
				style['transition-duration'] = this.duration / 1000 + 's';
				style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
				return style;
			}
		},
		created() {
			// 引用所有子组件(diy-selectdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
			this.children = [];
		},
		methods: {
			init() {
				// 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍
				// 以保证数据的正确性
				this.menuList = [];
				this.children.map(child => {
					child.init();
				})
			},
			cellClick(value) {
				this.selectValue = value
				// 通知父组件(diy-selectdown)收起菜单
				this.close();
				// 发出事件,抛出当前勾选项的value
				this.$emit("change", value);
			},
			// 点击菜单
			menuClick() {
				this.open();
			},
			getStatusBar() {
				let promise = new Promise((resolve, reject) => {
					uni.getSystemInfo({
						success: function(e) {
							let customBar
							// #ifdef H5
							customBar = e.statusBarHeight + e.windowTop;
							// #endif
							resolve(customBar)
						}
					})
				})
				return promise
			},
			getTopHeight() {
				let thiz = this
				return new Promise(function(resolve, reject) {
					const query = uni.createSelectorQuery().in(this);
					query.select('#' + thiz.triggerId).boundingClientRect(res => {
						if (res) {
							resolve(res)
						} else {
							resolve({
								left: 0,
								width: thiz.$u.sys().windowWidth,
								top: 0,
								height: 40
							})
						}
					}).exec();
				})
			},
			// 打开下拉菜单
			async open(index) {
				// 重置高亮索引,否则会造成多个菜单同时高亮
				// 展开时,设置下拉内容的样式
				this.contentStyle = {
					zIndex: 999999,
				}
				let res = await this.getTopHeight();
				this.menuPositionData = res;
				let topHeight = res.top + res.height;
				let statusBar = await this.getStatusBar()
				statusBar = statusBar || 0
				if (this.direction == 'up') {
					this.topheight = statusBar
				} else {
					this.topheight = statusBar + topHeight
				}
				this.getContentHeight();
				// 标记展开状态以及当前展开项的索引
				this.active = true;
			},
			// 设置下拉菜单处于收起状态
			close() {
				this.active = false;
				this.$emit("update:modelValue", false);
				// 下拉内容的样式进行调整,不透明度设置为0
				this.contentStyle = {
					zIndex: -1,
					opacity: 0
				}
			},
			// 点击遮罩
			maskClick() {
				// 如果不允许点击遮罩,直接返回
				if (!this.closeOnClickMask) return;
				this.close();
			},
			// 外部手动设置某个菜单高亮
			highlight(index = undefined) {
				this.highlightIndex = index !== undefined ? index : 99999;
			},
			// 获取下拉菜单内容的高度
			getContentHeight() {
				// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
				// 才能让遮罩占满菜单一下,直到屏幕底部的高度
				// this.$u.sys()为uView封装的获取设备信息的方法
				let windowHeight = this.$u.sys().windowHeight;
				if (this.direction == 'up') {
					this.contentHeight = this.menuPositionData.top;
					let bottom = windowHeight - this.menuPositionData.top
					this.contentStyle.bottom = (this.topheight - this.offsetTop) + 'px'
				} else {
					this.contentHeight = windowHeight - this.menuPositionData.top;
					this.contentStyle.top = (this.topheight + this.offsetTop) + 'px'
				}
			}
		}
	}
</script>

<style scoped lang="scss">
	.diy-selectdown {

		&__content {
			--hover-color: #19be6b;
			position: fixed;
			z-index: 999999;
			width: 100%;
			left: 0px;
			bottom: 0;
			padding-top: 2px;
			overflow: hidden;

			&__mask {
				position: absolute;
				z-index: 9;
				background: rgba(0, 0, 0, .3);
				width: 100%;
				left: 0;
				top: 0;
				bottom: 0;
			}

			&__popup {
				box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
				position: relative;
				z-index: 10;
				transition: all 0.3s;
				transform: translateY(-100%);
				overflow: hidden;
			}

			&.up &__popup {
				position: absolute;
				bottom: 0;
				width: 100%;
				transform: translateY(100%);
			}
		}

		.diy-dropdown-item {
			display: flex;
			flex-direction: row;
			align-items: center;
			position: relative;
			box-sizing: border-box;
			width: 100%;
			padding: 20rpx;
			color: #606266;
			background-color: #fff;
			text-align: left;

			&-hover {
				color: var(--hover-color) !important
			}
		}
	}
</style>

组件库调用

经测试我们支持了循环子表单下拉选择、单选择、表单里选择。

<template>
	<view class="container container329152">
		<u-form-item class="diygw-col-24 diygw-uform-item diygw-form-border" label="下拉1" prop="sctdown1">
			<diy-selectinput :id="`sctdown1`" @click="showSctdown1 = true" class="diygw-col-24 solid" valueName="value" labelName="label" :list="sctdown1Datas" placeholder="请选择" v-model="sctdown1" type="select"></diy-selectinput>
			<diy-selectdown  :trigger-id="`sctdown1`" v-model="showSctdown1" valueName="value" labelName="label" :list="sctdown1Datas" :defaultValue="sctdown1" @change="changeSctdown1"></diy-selectdown>
		</u-form-item>
		<u-form :model="form" :rules="formRules" :errorType="['message', 'toast']" ref="formRef" class="flex diygw-form diygw-col-24">
			<u-form-item class="diygw-col-24" label="下拉" prop="sctdown">
				<diy-selectinput :id="`formDatasctdown`" @click="formData.showSctdown = true" class="diygw-col-24" valueName="value" labelName="label" :list="formData.sctdownDatas" placeholder="请选择" v-model="form.sctdown" type="select"></diy-selectinput>
				<diy-selectdown :offset-top="15" :trigger-id="`formDatasctdown`" v-model="formData.showSctdown" valueName="value" labelName="label" :list="formData.sctdownDatas" :defaultValue="form.sctdown" @change="changeFormSctdown"></diy-selectdown>
			</u-form-item>
			<view class="flex flex-wrap diygw-col-24">
				<view class="diygw-col-24" v-for="(subformItem, subformIndex) in form.subform" :key="subformIndex">
					<u-form class="diygw-col-24" :model="form.subform[subformIndex]" :errorType="['message', 'toast']" ref="subformRef" :rules="subformItemRules">
						<u-form-item class="diygw-col-24" label="下拉" prop="sct">
							<diy-selectinput @click="formData.subformItemDatas[subformIndex].showSct = true" class="diygw-col-24" valueName="value" labelName="label" :list="formData.subformItemDatas[subformIndex].sctDatas" placeholder="请选择" v-model="subformItem.sct" type="select"></diy-selectinput>
							<u-select mode="single-column" valueName="value" labelName="label" :list="formData.subformItemDatas[subformIndex].sctDatas" isDefaultSelect :defaultSelectValue="subformItem.sct" v-model="formData.subformItemDatas[subformIndex].showSct" @confirm="changeSubformItemSct($event, subformIndex, subformItem)"></u-select>
						</u-form-item>
					</u-form>
					<view class="padding-xs diygw-col-24 flex justify-end">
						<button @tap="upSubformItem" :data-index="subformIndex" class="diygw-btn flex-sub radius margin-xs"><text class="button-icon diy-icon-fold"></text> 上移</button>
						<button @tap="downSubformItem" :data-index="subformIndex" class="diygw-btn flex-sub radius margin-xs"><text class="button-icon diy-icon-unfold"></text> 下移</button>
						<button @tap="addSubformItem" :data-index="subformIndex" class="diygw-btn flex-sub radius margin-xs"><text class="button-icon diy-icon-add"></text> 新增</button>
						<button @tap="delSubformItem" :data-index="subformIndex" class="diygw-btn flex-sub radius margin-xs"><text class="button-icon diy-icon-close"></text> 删除</button>
					</view>
				</view>
				<view class="padding-xs diygw-col-24">
					<button @tap="addSubformItem" class="diygw-btn diygw-col-24 radius" style="background: #07c160; color: #fff"><text class="diy-icon-add"></text>新增</button>
				</view>
			</view>
		</u-form>
		<view class="clearfix"></view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				//用户全局信息
				userInfo: {},
				//页面传参
				globalOption: {},
				//自定义全局变量
				globalData: {},
				showSctdown1: false,
				sctdown1Datas: [
					{ value: '1', label: '选项一' },
					{ value: '2', label: '选项二' },
					{ value: '3', label: '选项三' }
				],
				sctdown1: '1',
				subformItemData: {
					showSct: false,
					sctDatas: [
						{ value: '1', label: '选项一' },
						{ value: '2', label: '选项二' },
						{ value: '3', label: '选项三' }
					]
				},
				subformItem: {
					sct: '1'
				},
				form: {
					sctdown: '1',
					subform: []
				},
				formRules: {},
				subformItemRules: {},
				formData: {
					showSctdown: false,
					sctdownDatas: [
						{ value: '1', label: '选项一' },
						{ value: '2', label: '选项二' },
						{ value: '3', label: '选项三' }
					],
					subformItemDatas: []
				}
			};
		},
		onShow() {
			this.setCurrentPage(this);
		},
		onLoad(option) {
			this.setCurrentPage(this);
			if (option) {
				this.setData({
					globalOption: this.getOption(option)
				});
			}

			this.init();
		},
		onReady() {
			this.$refs.formRef?.setRules(this.formRules);
			this.initSubformData();
		},
		methods: {
			async init() {
				await this.initResetform();
			},
			changeSctdown1(evt) {
				this.sctdown1 = evt;
			},
			changeFormSctdown(evt) {
				this.form.sctdown = evt;
			},
			//初始化显示子表单数据条数
			initSubformData() {
				for (let i = 0; i < 1; i++) {
					this.form.subform.push(JSON.parse(JSON.stringify(this.subformItem)));
					this.formData.subformItemDatas.push(JSON.parse(JSON.stringify(this.subformItemData)));
				}
				this.initSubformValid();
			},
			//子表单验证
			initSubformValid() {
				this.$nextTick(() => {
					this.$refs['subformRef']?.forEach((subform) => {
						subform.setRules(this.subformItemRules);
					});
				});
			},
			//上移子表单
			upSubformItem(evt) {
				let { index } = evt.currentTarget.dataset;
				if (index == 0) {
					this.navigateTo({
						type: 'tip',
						tip: '已经是第一个'
					});
					return false;
				}
				this.form.subform[index] = this.form.subform.splice(index - 1, 1, this.form.subform[index])[0];
				this.formData.subformItemDatas[index] = this.formData.subformItemDatas.splice(index - 1, 1, this.formData.subformItemDatas[index])[0];
				this.initSubformValid();
			},
			//下移子表单
			downSubformItem(evt) {
				let { index } = evt.currentTarget.dataset;
				if (index == this.form.subform.length - 1) {
					this.navigateTo({
						type: 'tip',
						tip: '已经是最后一个'
					});
					return false;
				}
				this.form.subform[index] = this.form.subform.splice(index + 1, 1, this.form.subform[index])[0];
				this.formData.subformItemDatas[index] = this.formData.subformItemDatas.splice(index + 1, 1, this.formData.subformItemDatas[index])[0];
				this.initSubformValid();
			},
			//删除子表单
			delSubformItem(evt) {
				let { index } = evt.currentTarget.dataset;
				this.form.subform.splice(index, 1);
				this.formData.subformItemDatas.splice(index, 1);
				this.initSubformValid();
			},
			//增加子表单
			addSubformItem() {
				this.form.subform.push(JSON.parse(JSON.stringify(this.subformItem)));
				this.formData.subformItemDatas.push(JSON.parse(JSON.stringify(this.subformItemData)));
				this.initSubformValid();
			},
			//验证所有的子表单
			checkSubformValid() {
				let flag = true;
				this.$refs['subformRef']?.forEach((subform) => {
					subform.validate((valid) => {
						if (!valid) {
							flag = false;
							return false;
						}
					});
				});
				return flag;
			},
			changeSubformItemSct(evt, subformIndex, subformItem) {
				evt.map((val, index) => {
					subformItem.sct = val.value;
				});
			},
			initResetform() {
				this.initform = JSON.stringify(this.form);
				//如果想给表单默认初始值,其中row为某一行数据也可能是API返回的结果集,然后给到this.form
				//this.form = this.$tools.changeRowToForm(row,this.form)
			},
			resetForm() {
				this.form = JSON.parse(this.initform);
			},

			async submitForm(e) {
				this.$refs.formRef?.setRules(this.formRules);

				this.initSubformValid();
				this.$nextTick(async () => {
					let subformvalid = await this.checkSubformValid();
					let valid = await this.$refs.formRef.validate();
					if (valid && subformvalid) {
						//保存数据
						let param = this.form;
						let header = {
							'Content-Type': 'application/json'
						};
						let url = '';
						if (!url) {
							this.showToast('请先配置表单提交地址', 'none');
							return false;
						}

						let res = await this.$http.post(url, param, header, 'json');

						if (res.code == 200) {
							this.showToast(res.msg, 'success');
						} else {
							this.showModal(res.msg, '提示', false);
						}
					} else {
						console.log('验证失败');
					}
				});
			}
		}
	};
</script>

<style lang="scss" scoped>
	.container329152 {
	}
</style>
相关推荐
web150850966418 小时前
在uniapp Vue3版本中如何解决webH5网页浏览器跨域的问题
前端·uni-app
何极光18 小时前
uniapp小程序样式穿透
前端·小程序·uni-app
User_undefined1 天前
uniapp Native.js 调用安卓arr原生service
android·javascript·uni-app
流氓也是种气质 _Cookie1 天前
uniapp blob格式转换为video .mp4文件使用ffmpeg工具
ffmpeg·uni-app
爱笑的眼睛111 天前
uniapp 极速上手鸿蒙开发
华为·uni-app·harmonyos
鱼樱前端2 天前
uni-app框架核心/常用API梳理一(数据缓存)
前端·uni-app
阿琳a_2 天前
解决uniapp中使用axios在真机和模拟器下请求报错问题
前端·javascript·uni-app
三天不学习2 天前
uni-app 跨端开发精美开源UI框架推荐
ui·uni-app·开源
多客软件佳佳2 天前
便捷的线上游戏陪玩、线下家政预约以及语音陪聊服务怎么做?系统代码解析
前端·游戏·小程序·前端框架·uni-app·交友
洗发水很好用2 天前
uniApp上传文件踩坑日记
uni-app