Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-input-number/cl-input-number.uvue

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>