uniapp 封装步进器

371 阅读3分钟

还是先上图,看效果

image.png

封装原因:

使用组件库的步进器组件时,会遇到

  • 样式与UI设计的不同,不好修改样式
  • 有些比较刁钻的功能,组件可能无法实现,需要我们手动处理,但是改源码比较麻烦
  • 组件库的步进器组件,如果绑定的值是0或者null之类的数据,那么他会默认显示最小值,但是如果项目的需求是最小值为0.01,这样就会出现:明明数据的值是0但是步进器上显示的却是最小值(0.01),这样明显不能满足项目需求

组件参数

参数说明示例
number绑定的数据 必填number类型:5
automaticWidth组件的宽度是否根据内容自动变化(变大变小)布尔类型:true
minNumber最小值number类型:1
maxNumber最大值number类型:100
step步进的频率number类型:1

代码

<template>
	<div class="box-number">
		<div class="number-left" @click="changeNum('reduce')"> - </div>
		<div class="number-center" :style="{ width: inputWidth }">
			<input
				class="number-input"
				border="none"
				type="number"
				v-model="num"
				@blur="inputChangeNum"
			/>
			<text class="number-size" v-if="automaticWidth">{{ num }}</text>
		</div>
		<div class="number-right" @click="changeNum('add')"> + </div>
	</div>
</template>

<script setup>
	import { ref, watch, getCurrentInstance, nextTick } from 'vue';
	const { proxy } = getCurrentInstance();
	let example = proxy;

	const props = defineProps({
		number: {
			type: Number,
			required: true,
		},
		automaticWidth: {
			type: Boolean,
			default: false,
		},
		minNumber: {
			type: Number,
			default: 1,
			required: false,
		},
		maxNumber: {
			type: Number,
			default: 100,
			required: false,
		},
		step: {
			type: Number,
			default: 1,
			required: false,
		},
	});

	const emit = defineEmits(['update:number']);

	let num = ref(); // 数量
	let numBackUp = ref(); // 数量 用于备份原数据
	let inputWidth = ref('');
	watch(
		() => props.number,
		(val) => {
			num.value = val;
			numBackUp.value = val;
		},
		{ immediate: true }
	);

	// 动态改变input宽度
	watch(
		num,
		async () => {
			if (!props.automaticWidth) return;
			await nextTick();
			uni
				.createSelectorQuery()
				.in(example)
				.select('.number-size')
				.boundingClientRect((data) => {
					let width = Math.ceil(data.width) * 2;
					width = width > 50 ? width : 50;
					inputWidth.value = width + 20 + 'rpx';
				})
				.exec();
		},
		{
			immediate: true,
		}
	);

	// 改变数量
	function changeNum(type) {
		num.value = Number(num.value);
		switch (type) {
			case 'add':
				num.value += props.step;
				break;
			case 'reduce':
				num.value -= props.step;
				break;
		}
		let isLegal = checkNum(type);
		if (isLegal) {
			confirmChangeNum();
		} else {
			backUpNum();
		}
	}
	// 输入框失去焦点 改变数量
	function inputChangeNum() {
		num.value = Number(num.value);
		let isLegal = checkNum();
		if (isLegal) {
			confirmChangeNum();
		} else {
			backUpNum();
		}
	}
	// 判断数据是否合法
	function checkNum(type = 'change') {
		let isLegal = true;
		switch (type) {
			case 'reduce':
				if (num.value < props.minNumber) {
					uni.showToast({ title: `数量不能小于 ${props.minNumber}`, icon: 'none' });
					isLegal = false;
				}
				break;
			case 'add':
				if (num.value > props.maxNumber) {
					uni.showToast({ title: `数量不能大于 ${props.maxNumber}`, icon: 'none' });
					isLegal = false;
				}
				break;
			case 'change':
				if (isNaN(num.value)) {
					uni.showToast({ title: '请输入正确的数量', icon: 'none' });
					isLegal = false;
				}
				if (num.value < props.minNumber) {
					uni.showToast({ title: `数量不能小于 ${props.minNumber}`, icon: 'none' });
					isLegal = false;
				}
				if (num.value > props.maxNumber) {
					uni.showToast({ title: `数量不能大于 ${props.maxNumber}`, icon: 'none' });
					isLegal = false;
				}
				break;
		}
		return isLegal;
	}
	// 确定改变数据
	function confirmChangeNum() {
		emit('update:number', num.value);
		numBackUp.value = num.value;
	}
	// 数据回退
	function backUpNum() {
		num.value = numBackUp.value;
	}
</script>

<style lang="scss" scoped>
	.box-number {
		display: flex;
		align-items: center;
		.number-left,
		.number-center,
		.number-right {
			display: flex;
			justify-content: center;
			align-items: center;
		}
		.number-left {
			width: 51rpx;
			height: 50rpx;
			background: #ffffff;
			border: 1px solid #d2d2d2;
			border-radius: 10rpx 0rpx 0rpx 10rpx;
			image {
				width: 23rpx;
				height: 3rpx;
			}
		}
		.number-center {
			position: relative;
			width: 75rpx;
			height: 50rpx;
			background: #ffffff;
			border: 1px solid #d2d2d2;
			font-size: 28rpx;
			font-weight: 500;
			color: #333333;
			transition: all 0.3s;
			.number-input {
				text-align: center;
				width: 100%;
			}
			.number-size {
				font-size: 28rpx;
				position: absolute;
				visibility: hidden;
			}
		}
		.number-right {
			width: 51rpx;
			height: 50rpx;
			background: #ffffff;
			border: 1px solid #d2d2d2;
			border-radius: 0rpx 10rpx 10rpx 0rpx;
			image {
				width: 23rpx;
				height: 23rpx;
			}
		}
	}
</style>
  • 这里在html中的number-size元素是为了动态计算输入框的宽度而声明的一个占位元素
  • 这里在js中多声明的numBackUp数据是为了在用户使用输入框输入数量之后,验证到输入的值是非法的之后,可以将数量的值还原到最近一次合法的值
  • css样式根据项目需要进行更改

页面使用

let num = ref(5);
<myStepper v-model:number="num" :automaticWidth="true"></myStepper>

最后

这是一个基础版的步进器,功能只有点击按钮进行加减和输入框输入数值

如果有特殊的需求,就需要自己手动添加完成需求的逻辑了