feat(全局):增加 number-range-input 组件

pull/345/head
YunaiV 2026-05-17 23:35:31 +08:00
parent 08511191f7
commit 3135b28211
4 changed files with 224 additions and 0 deletions

View File

@ -0,0 +1,39 @@
import type { VbenFormSchema } from '#/adapter/form';
import { markRaw } from 'vue';
import NumberRangeInput from './number-range-input.vue';
export { default as NumberRangeInput } from './number-range-input.vue';
export type NumberRangeValue = [number | undefined, number | undefined];
function splitNumberRange(minFieldName: string, maxFieldName: string) {
return (
value: NumberRangeValue | undefined,
setValue: (fieldName: string, value: number | undefined) => void,
) => {
setValue(minFieldName, value?.[0]);
setValue(maxFieldName, value?.[1]);
return undefined;
};
}
export function buildNumberRangeSchema(
label: string,
fieldName: string,
minFieldName: string,
maxFieldName: string,
precision: number,
): VbenFormSchema {
return {
component: markRaw(NumberRangeInput),
componentProps: {
min: 0,
precision,
},
fieldName,
label,
valueFormat: splitNumberRange(minFieldName, maxFieldName),
};
}

View File

@ -0,0 +1,73 @@
<script lang="ts" setup>
import { InputNumber } from 'ant-design-vue';
type NumberRangeValue = [number | undefined, number | undefined];
const props = withDefaults(
defineProps<{
maxPlaceholder?: string;
min?: number;
minPlaceholder?: string;
precision?: number;
value?: NumberRangeValue;
}>(),
{
maxPlaceholder: '最大值',
min: undefined,
minPlaceholder: '最小值',
precision: 2,
value: undefined,
},
);
const emit = defineEmits<{
'update:value': [value: NumberRangeValue | undefined];
}>();
function normalizeValue(value: unknown) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : undefined;
}
if (typeof value === 'string' && value.trim() !== '') {
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue : undefined;
}
return undefined;
}
function updateValue(index: 0 | 1, value: unknown) {
const next: NumberRangeValue = [
props.value?.[0] ?? undefined,
props.value?.[1] ?? undefined,
];
next[index] = normalizeValue(value);
emit(
'update:value',
next[0] === undefined && next[1] === undefined ? undefined : next,
);
}
</script>
<template>
<div class="flex w-full items-center gap-2">
<InputNumber
:controls="false"
:min="min"
:placeholder="minPlaceholder"
:precision="precision"
:value="value?.[0]"
class="min-w-0 flex-1"
@update:value="updateValue(0, $event)"
/>
<span class="shrink-0 text-muted-foreground"></span>
<InputNumber
:controls="false"
:min="min"
:placeholder="maxPlaceholder"
:precision="precision"
:value="value?.[1]"
class="min-w-0 flex-1"
@update:value="updateValue(1, $event)"
/>
</div>
</template>

View File

@ -0,0 +1,39 @@
import type { VbenFormSchema } from '#/adapter/form';
import { markRaw } from 'vue';
import NumberRangeInput from './number-range-input.vue';
export { default as NumberRangeInput } from './number-range-input.vue';
export type NumberRangeValue = [number | undefined, number | undefined];
function splitNumberRange(minFieldName: string, maxFieldName: string) {
return (
value: NumberRangeValue | undefined,
setValue: (fieldName: string, value: number | undefined) => void,
) => {
setValue(minFieldName, value?.[0]);
setValue(maxFieldName, value?.[1]);
return undefined;
};
}
export function buildNumberRangeSchema(
label: string,
fieldName: string,
minFieldName: string,
maxFieldName: string,
precision: number,
): VbenFormSchema {
return {
component: markRaw(NumberRangeInput),
componentProps: {
min: 0,
precision,
},
fieldName,
label,
valueFormat: splitNumberRange(minFieldName, maxFieldName),
};
}

View File

@ -0,0 +1,73 @@
<script lang="ts" setup>
import { ElInputNumber } from 'element-plus';
type NumberRangeValue = [number | undefined, number | undefined];
const props = withDefaults(
defineProps<{
maxPlaceholder?: string;
min?: number;
minPlaceholder?: string;
modelValue?: NumberRangeValue;
precision?: number;
}>(),
{
maxPlaceholder: '最大值',
min: undefined,
minPlaceholder: '最小值',
modelValue: undefined,
precision: 2,
},
);
const emit = defineEmits<{
'update:modelValue': [value: NumberRangeValue | undefined];
}>();
function normalizeValue(value: unknown) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : undefined;
}
if (typeof value === 'string' && value.trim() !== '') {
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue : undefined;
}
return undefined;
}
function updateValue(index: 0 | 1, value: unknown) {
const next: NumberRangeValue = [
props.modelValue?.[0] ?? undefined,
props.modelValue?.[1] ?? undefined,
];
next[index] = normalizeValue(value);
emit(
'update:modelValue',
next[0] === undefined && next[1] === undefined ? undefined : next,
);
}
</script>
<template>
<div class="flex w-full items-center gap-2">
<ElInputNumber
:controls="false"
:min="min"
:model-value="modelValue?.[0]"
:placeholder="minPlaceholder"
:precision="precision"
class="min-w-0 flex-1"
@update:model-value="updateValue(0, $event)"
/>
<span class="shrink-0 text-muted-foreground"></span>
<ElInputNumber
:controls="false"
:min="min"
:model-value="modelValue?.[1]"
:placeholder="maxPlaceholder"
:precision="precision"
class="min-w-0 flex-1"
@update:model-value="updateValue(1, $event)"
/>
</div>
</template>