还是先上图,看效果
封装原因:
使用组件库的步进器组件时,会遇到
- 样式与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>
最后
这是一个基础版的步进器,功能只有点击按钮进行加减和输入框输入数值
如果有特殊的需求,就需要自己手动添加完成需求的逻辑了