337 lines
6.5 KiB
Plaintext
337 lines
6.5 KiB
Plaintext
<template>
|
|
<view
|
|
class="cl-input-number"
|
|
:class="[
|
|
{
|
|
'cl-input-number--disabled': isDisabled
|
|
},
|
|
pt.className
|
|
]"
|
|
:style="{
|
|
height: parseRpx(size!)
|
|
}"
|
|
>
|
|
<view
|
|
class="cl-input-number__minus"
|
|
:class="[
|
|
{
|
|
'is-disabled': !isMinus
|
|
},
|
|
pt.op?.className,
|
|
pt.op?.minus?.className
|
|
]"
|
|
hover-class="!bg-surface-200"
|
|
:hover-stay-time="250"
|
|
:style="{
|
|
height: parseRpx(size!),
|
|
width: parseRpx(size!)
|
|
}"
|
|
@touchstart="onMinus"
|
|
@touchend="longPress.stop"
|
|
@touchcancel="longPress.stop"
|
|
>
|
|
<cl-icon
|
|
name="subtract-line"
|
|
:size="pt.op?.icon?.size ?? 36"
|
|
:color="pt.op?.icon?.color ?? 'info'"
|
|
:pt="{
|
|
className: pt.op?.icon?.className
|
|
}"
|
|
></cl-icon>
|
|
</view>
|
|
|
|
<view class="cl-input-number__value">
|
|
<cl-input
|
|
:model-value="`${value}`"
|
|
:type="inputType"
|
|
:disabled="isDisabled"
|
|
:clearable="false"
|
|
:readonly="inputable == false"
|
|
:placeholder="placeholder"
|
|
:hold-keyboard="false"
|
|
:pt="{
|
|
className: `!h-full w-[120rpx] ${pt.value?.className}`,
|
|
inner: {
|
|
className: `text-center ${pt.value?.input?.className}`
|
|
}
|
|
}"
|
|
@blur="onBlur"
|
|
></cl-input>
|
|
</view>
|
|
|
|
<view
|
|
class="cl-input-number__plus"
|
|
:class="[
|
|
{
|
|
'is-disabled': !isPlus
|
|
},
|
|
pt.op?.className,
|
|
pt.op?.plus?.className
|
|
]"
|
|
hover-class="!bg-primary-600"
|
|
:hover-stay-time="250"
|
|
:style="{
|
|
height: parseRpx(size!),
|
|
width: parseRpx(size!)
|
|
}"
|
|
@touchstart="onPlus"
|
|
@touchend="longPress.stop"
|
|
@touchcancel="longPress.stop"
|
|
>
|
|
<cl-icon
|
|
name="add-line"
|
|
:size="pt.op?.icon?.size ?? 36"
|
|
:color="pt.op?.icon?.color ?? 'white'"
|
|
:pt="{
|
|
className: pt.op?.icon?.className
|
|
}"
|
|
></cl-icon>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, nextTick, ref, watch, type PropType } from "vue";
|
|
import type { PassThroughProps } from "../../types";
|
|
import type { ClIconProps } from "../cl-icon/props";
|
|
import { useLongPress, parsePt, parseRpx } from "@/cool";
|
|
import { useForm } from "../../hooks";
|
|
|
|
defineOptions({
|
|
name: "cl-input-number"
|
|
});
|
|
|
|
// 定义组件属性
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 透传样式配置
|
|
pt: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
// 占位符 - 输入框为空时显示的提示文本
|
|
placeholder: {
|
|
type: String,
|
|
default: ""
|
|
},
|
|
// 步进值 - 点击加减按钮时改变的数值
|
|
step: {
|
|
type: Number,
|
|
default: 1
|
|
},
|
|
// 最大值 - 允许输入的最大数值
|
|
max: {
|
|
type: Number,
|
|
default: 100
|
|
},
|
|
// 最小值 - 允许输入的最小数值
|
|
min: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
// 输入框类型 - digit表示带小数点的数字键盘,number表示纯数字键盘
|
|
inputType: {
|
|
type: String as PropType<"digit" | "number">,
|
|
default: "number"
|
|
},
|
|
// 是否可输入 - 控制是否允许手动输入数值
|
|
inputable: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
// 是否禁用 - 禁用后无法输入和点击加减按钮
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 组件大小 - 控制加减按钮的尺寸,支持数字或字符串形式
|
|
size: {
|
|
type: [Number, String] as PropType<number | string>,
|
|
default: 50
|
|
}
|
|
});
|
|
|
|
// 定义组件事件
|
|
const emit = defineEmits(["update:modelValue", "change"]);
|
|
|
|
// 长按操作
|
|
const longPress = useLongPress();
|
|
|
|
// cl-form 上下文
|
|
const { disabled } = useForm();
|
|
|
|
// 是否禁用
|
|
const isDisabled = computed(() => {
|
|
return disabled.value || props.disabled;
|
|
});
|
|
|
|
// 数值样式
|
|
type ValuePassThrough = {
|
|
className?: string;
|
|
input?: PassThroughProps;
|
|
};
|
|
|
|
// 操作按钮样式
|
|
type OpPassThrough = {
|
|
className?: string;
|
|
minus?: PassThroughProps;
|
|
plus?: PassThroughProps;
|
|
icon?: ClIconProps;
|
|
};
|
|
|
|
// 定义透传样式类型
|
|
type PassThrough = {
|
|
className?: string;
|
|
value?: ValuePassThrough;
|
|
op?: OpPassThrough;
|
|
};
|
|
|
|
// 解析透传样式配置
|
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
|
|
|
// 绑定值
|
|
const value = ref(props.modelValue);
|
|
|
|
// 是否可以继续增加数值
|
|
const isPlus = computed(() => !isDisabled.value && value.value < props.max);
|
|
|
|
// 是否可以继续减少数值
|
|
const isMinus = computed(() => !isDisabled.value && value.value > props.min);
|
|
|
|
/**
|
|
* 更新数值并触发事件
|
|
* 确保数值在最大值和最小值范围内
|
|
*/
|
|
function update() {
|
|
nextTick(() => {
|
|
let val = value.value;
|
|
|
|
// 处理小于最小值的情况
|
|
if (val < props.min) {
|
|
val = props.min;
|
|
}
|
|
|
|
// 处理大于最大值的情况
|
|
if (val > props.max) {
|
|
val = props.max;
|
|
}
|
|
|
|
// 处理最小值大于最大值的异常情况
|
|
if (props.min > props.max) {
|
|
val = props.max;
|
|
}
|
|
|
|
// 小数点后两位
|
|
if (props.inputType == "digit") {
|
|
val = parseFloat(val.toFixed(2));
|
|
}
|
|
|
|
// 更新值,确保值是数字
|
|
value.value = val;
|
|
|
|
// 如果值发生变化,则触发事件
|
|
if (val != props.modelValue) {
|
|
emit("update:modelValue", val);
|
|
emit("change", val);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 点击加号按钮处理函数 (支持长按)
|
|
* 在非禁用状态下增加step值
|
|
*/
|
|
function onPlus() {
|
|
if (isDisabled.value || !isPlus.value) return;
|
|
|
|
longPress.start(() => {
|
|
if (isPlus.value) {
|
|
const val = props.max - value.value;
|
|
value.value += val > props.step ? props.step : val;
|
|
update();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 点击减号按钮处理函数 (支持长按)
|
|
* 在非禁用状态下减少step值
|
|
*/
|
|
function onMinus() {
|
|
if (isDisabled.value || !isMinus.value) return;
|
|
|
|
longPress.start(() => {
|
|
if (isMinus.value) {
|
|
const val = value.value - props.min;
|
|
value.value -= val > props.step ? props.step : val;
|
|
update();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 输入框失去焦点处理函数
|
|
* @param val 输入的字符串值
|
|
*/
|
|
function onBlur(e: UniInputBlurEvent) {
|
|
if (e.detail.value == "") {
|
|
value.value = 0;
|
|
} else {
|
|
value.value = parseFloat(e.detail.value);
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
// 监听绑定值变化
|
|
watch(
|
|
computed(() => props.modelValue),
|
|
(val: number) => {
|
|
value.value = val;
|
|
update();
|
|
},
|
|
{
|
|
immediate: true
|
|
}
|
|
);
|
|
|
|
// 监听最大值变化,确保当前值不超过新的最大值
|
|
watch(
|
|
computed(() => props.max),
|
|
update
|
|
);
|
|
|
|
// 监听最小值变化,确保当前值不小于新的最小值
|
|
watch(
|
|
computed(() => props.min),
|
|
update
|
|
);
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.cl-input-number {
|
|
@apply flex flex-row items-center;
|
|
|
|
&__plus,
|
|
&__minus {
|
|
@apply flex items-center justify-center rounded-md bg-surface-100;
|
|
|
|
&.is-disabled {
|
|
@apply opacity-50;
|
|
}
|
|
}
|
|
|
|
&__plus {
|
|
@apply bg-primary-500;
|
|
}
|
|
|
|
&__value {
|
|
@apply flex flex-row items-center justify-center h-full;
|
|
margin: 0 12rpx;
|
|
}
|
|
}
|
|
</style>
|