组件 number-box.vue
<template>
<view class="uni-numbox" v-if="mode === 'drag'">
<!-- 可拖拽式标签 -->
<view @mousedown="touchstart" @touchmove="touchmove">
<slot>
<text class="drag-label" :style="cptFontSize">{{label}}</text>
</slot>
</view>
<input :disabled="disabled" @blur="_onBlur" class="uni-numbox__value-drag" type="number" v-model="inputValue" :placeholder="`${inputValue}`"
:style="cptValueStyle" />
<view class="action">
<view class="action-item" @click="_calcValue('plus')" :style="cptDragButtonStyle">
<text class="uni-numbox__text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">▲</text>
</view>
<view class="action-item" @click="_calcValue('minus')" :style="cptDragButtonStyle">
<text class="uni-numbox__text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }">▼</text>
</view>
</view>
<view v-if="show" class="drag-range--mask"></view>
</view>
<view class="uni-numbox" v-else>
<view @click="_calcValue('minus')" class="uni-numbox__minus" :style="cptButtonStyle">
<text class="uni-numbox__text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }">-</text>
</view>
<input :disabled="disabled" @blur="_onBlur" class="uni-numbox__value" type="number" v-model="inputValue" :style="cptValueStyle" />
<view @click="_calcValue('plus')" class="uni-numbox__plus" :style="cptButtonStyle">
<text class="uni-numbox__text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
</view>
</view>
</template>
<script>
const sizeDef = ['small', 'normal', 'big']
const sizeDefMap = [8, 10, 14]
/**
* NumberBox 数字输入框
* @description 带加减按钮的数字输入框
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
* @property {Number} value 输入框当前值
* @property {Number} min 最小值
* @property {Number} max 最大值
* @property {String, Number} size=[small|normal|big|Number] 设置组件大小size 【注意】显示空间不够请设置width
* @value small small -base:8
* @value normal normal -base:10
* @value big big -base:14
* @value Number 任意数字 -base: number
* @property {Number} step 每次点击改变的间隔大小
* @property {Boolean} disabled = [true|false] 是否为禁用状态
* @property {String} mode = [button|drag] 显示样式
* @value button 左右按键式
* @value drag 可拖拽按键式
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
*
*/
export default {
name: "NumberBox",
props: {
value: {
type: [Number, String],
default: 1
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'button'
},
size: {
type: [String, Number],
default: 'normal'
},
label: String
},
data() {
return {
inputValue: 0,
show: false
};
},
watch: {
value(val) {
this.inputValue = +val;
},
inputValue(newVal, oldVal) {
if (+newVal !== +oldVal) {
this.$emit("change", newVal);
}
}
},
computed: {
cptBase() {
let index = sizeDef.indexOf(this.size)
if (index > -1) {
return sizeDefMap[index]
} else {
return this.size
}
},
cptButtonStyle() {
return {
width: 3 * this.cptBase + 'px',
height: 3 * this.cptBase + 'px',
fontSize: 2 * this.cptBase + 'px',
}
},
cptDragButtonStyle() {
return {
fontSize: this.cptBase + 'px',
}
},
cptFontSize() {
return {
fontSize: 1.5 * this.cptBase + 'px'
}
},
cptValueStyle() {
return {
width: '100%',
height: 3 * this.cptBase + 'px',
...this.cptFontSize
}
}
},
created() {
this.inputValue = +this.value;
},
methods: {
_calcValue(type) {
if (this.disabled) {
return;
}
const scale = this._getDecimalScale();
let value = this.inputValue * scale;
let step = this.step * scale;
if (type === "minus") {
value -= step;
if (value < (this.min * scale)) {
return;
}
if (value > (this.max * scale)) {
value = this.max * scale
}
} else if (type === "plus") {
value += step;
if (value > (this.max * scale)) {
return;
}
if (value < (this.min * scale)) {
value = this.min * scale
}
}
this.inputValue = String(value / scale);
},
_getDecimalScale() {
let scale = 1;
// 浮点型
if (~~this.step !== this.step) {
scale = Math.pow(10, (this.step + "").split(".")[1].length);
}
return scale;
},
_onBlur(event) {
let value = event.detail.value;
if (!value) {
// this.inputValue = 0;
return;
}
value = +value;
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min;
}
this.inputValue = value;
},
touchstart(e) {
const self = this
this.startX = e.clientX
this.show = true
function removeEventListeners() {
document.removeEventListener('mousemove', self.touchmove)
document.removeEventListener('mouseup', removeEventListeners)
self.show = false
}
document.addEventListener('mousemove', this.touchmove)
document.addEventListener('mouseup', removeEventListeners)
},
touchmove(e) {
let dx = e.clientX - this.startX
this.lastDx = dx
this._calcValue(dx < 0 ? 'minus' : 'plus')
}
}
};
</script>
<style lang="scss" scoped>
/* #ifdef APP-NVUE */
/* #endif */
.uni-numbox {
$sizeBase: 8;
$N3NumberSize: 3*$sizeBase + px;
$N3NumberFontSize: 2*$sizeBase + px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-around;
align-items: center;
border-radius: 3px;
border: 1px solid transparent;
box-sizing: border-box;
transition: 0.3s;
&:hover {
border-color: rgb(235, 235, 235);
}
&:focus-within {
border: 1px solid rgb(41, 141, 248);
}
&:hover .action,
&:focus-within .action {
visibility: visible;
}
.drag-label {
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding: 0px 3px 0px 5px;
width: max-content;
height: 100%;
color: rgb(141, 158, 167);
}
.action {
display: flex;
flex-direction: column;
width: 2em;
text-align: center;
transition: background 0.2s ease-out 0s, border, opacity;
visibility: hidden;
.action-item {
width: 100%;
height: 100%;
flex: 1;
line-height: 0;
cursor: pointer;
color: rgb(141, 158, 167);
transition: all 0.1s ease-out 0s;
.uni-numbox__text {
transform: scale(0.7);
display: inline-block;
}
}
.action-item:hover {
color: rgb(91, 107, 115);
}
}
.uni-numbox__text {
font-weight: bold;
}
.uni-numbox__minus {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: $N3NumberSize;
height: $N3NumberSize;
font-size: $N3NumberFontSize;
color: #333;
background-color: #f8f8f8;
border-width: 1px;
border-style: solid;
border-color: #e5e5e5;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-right-width: 0;
&:hover {
background-color: #cccccc;
}
}
.uni-numbox__plus {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: $N3NumberSize;
height: $N3NumberSize;
border-width: 1px;
border-style: solid;
border-color: #e5e5e5;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
background-color: #f8f8f8;
border-left-width: 0;
transition: 0.3s;
&:hover {
background-color: #cccccc;
}
}
.uni-numbox__value {
background-color: transparent;
width: $N3NumberSize;
height: $N3NumberSize;
text-align: center;
font-size: 1.6* $sizeBase+px;
border-width: 1px;
border-style: solid;
border-color: #e5e5e5;
border-left-width: 0;
border-right-width: 0;
box-sizing: border-box;
flex-grow: 1;
}
.uni-numbox__value-drag {
background-color: transparent;
width: $N3NumberSize;
height: $N3NumberSize;
text-align: center;
font-size: 1.6* $sizeBase+px;
border: none;
box-sizing: border-box;
flex-grow: 1;
}
.uni-numbox--disabled {
color: #c0c0c0;
}
}
.drag-range {
cursor: e-resize;
user-select: none;
}
.drag-range--mask{
cursor: e-resize;
user-select: none;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 9999;
}
</style>
使用
<number-box :min="0" value="0" size="small" mode="drag" label="W"> </number-box>