| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- <template>
- <view
- class="cl-input-number"
- :class="[
- {
- 'cl-input-number--disabled': isDisabled
- },
- pt.className
- ]"
- >
- <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>
|