vue2server7 7.0.11 → 7.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="number-range" :class="{ 'is-disabled': disabled }">
|
|
2
|
+
<div class="number-range" :class="{ 'is-disabled': disabled, 'is-error': showInternalError }">
|
|
3
3
|
<el-input
|
|
4
4
|
v-model="minDisplay"
|
|
5
5
|
class="number-range__input"
|
|
6
|
+
:class="{ 'is-error': showInternalError }"
|
|
6
7
|
:placeholder="minPlaceholder"
|
|
7
8
|
:disabled="disabled"
|
|
8
9
|
clearable
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
<el-input
|
|
16
17
|
v-model="maxDisplay"
|
|
17
18
|
class="number-range__input"
|
|
19
|
+
:class="{ 'is-error': showInternalError }"
|
|
18
20
|
:placeholder="maxPlaceholder"
|
|
19
21
|
:disabled="disabled"
|
|
20
22
|
clearable
|
|
@@ -23,15 +25,44 @@
|
|
|
23
25
|
@blur="onMaxBlur"
|
|
24
26
|
@clear="onMaxClear"
|
|
25
27
|
/>
|
|
28
|
+
<span v-if="showInternalError" class="number-range__error">结束值不能小于起始值</span>
|
|
26
29
|
</div>
|
|
27
30
|
</template>
|
|
28
31
|
|
|
29
32
|
<script lang="ts">
|
|
30
33
|
import type { FormItemRule } from 'element-plus'
|
|
31
34
|
|
|
32
|
-
type RangeValue = [number | null, number | null]
|
|
35
|
+
export type RangeValue = [number | string | null, number | string | null]
|
|
33
36
|
type ValidatorCb = (error?: Error) => void
|
|
34
37
|
|
|
38
|
+
/** 将字符串/数字统一转为 number,空值返回 null */
|
|
39
|
+
function toNum(val: unknown): number | null {
|
|
40
|
+
if (val === null || val === undefined || val === '') return null
|
|
41
|
+
const n = Number(val)
|
|
42
|
+
return Number.isNaN(n) ? null : n
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface NumberRangeProps {
|
|
46
|
+
/** 数组模式绑定值 [min, max],与 start/end 二选一;值可以是 string 或 number */
|
|
47
|
+
modelValue?: RangeValue
|
|
48
|
+
/** 双字段模式 - 起始值(支持 string 类型数字) */
|
|
49
|
+
start?: number | string | null
|
|
50
|
+
/** 双字段模式 - 结束值(支持 string 类型数字) */
|
|
51
|
+
end?: number | string | null
|
|
52
|
+
minPlaceholder?: string
|
|
53
|
+
maxPlaceholder?: string
|
|
54
|
+
/** 两个输入框之间的分隔文字 */
|
|
55
|
+
separator?: string
|
|
56
|
+
/** 允许输入的最小边界 */
|
|
57
|
+
min?: number
|
|
58
|
+
/** 允许输入的最大边界 */
|
|
59
|
+
max?: number
|
|
60
|
+
step?: number
|
|
61
|
+
/** 小数位数限制,如 2 表示最多两位小数 */
|
|
62
|
+
precision?: number
|
|
63
|
+
disabled?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
/**
|
|
36
67
|
* NumberRange 数字区间输入组件
|
|
37
68
|
*
|
|
@@ -61,10 +92,10 @@ export function rangeRequired(
|
|
|
61
92
|
trigger,
|
|
62
93
|
validator: startField
|
|
63
94
|
? (_r: unknown, _v: unknown, cb: ValidatorCb, source: Record<string, unknown>) =>
|
|
64
|
-
cb(source[startField] == null
|
|
95
|
+
cb(toNum(source[startField]) == null ? new Error(message) : undefined)
|
|
65
96
|
: (_r: unknown, v: unknown, cb: ValidatorCb) => {
|
|
66
97
|
const arr = v as RangeValue | undefined
|
|
67
|
-
cb(!Array.isArray(arr) || arr[0] == null || arr[1] == null ? new Error(message) : undefined)
|
|
98
|
+
cb(!Array.isArray(arr) || toNum(arr[0]) == null || toNum(arr[1]) == null ? new Error(message) : undefined)
|
|
68
99
|
}
|
|
69
100
|
}
|
|
70
101
|
}
|
|
@@ -78,43 +109,26 @@ export function rangeRule(
|
|
|
78
109
|
return {
|
|
79
110
|
trigger,
|
|
80
111
|
validator: startField
|
|
81
|
-
? (_r: unknown, v: unknown, cb: ValidatorCb, source: Record<string, unknown>) =>
|
|
82
|
-
|
|
112
|
+
? (_r: unknown, v: unknown, cb: ValidatorCb, source: Record<string, unknown>) => {
|
|
113
|
+
const startNum = toNum(source[startField])
|
|
114
|
+
const endNum = toNum(v)
|
|
115
|
+
cb(startNum != null && endNum != null && endNum < startNum ? new Error(message) : undefined)
|
|
116
|
+
}
|
|
83
117
|
: (_r: unknown, v: unknown, cb: ValidatorCb) => {
|
|
84
118
|
const arr = v as RangeValue | undefined
|
|
85
|
-
|
|
119
|
+
const minNum = toNum(arr?.[0])
|
|
120
|
+
const maxNum = toNum(arr?.[1])
|
|
121
|
+
cb(minNum != null && maxNum != null && maxNum < minNum ? new Error(message) : undefined)
|
|
86
122
|
}
|
|
87
123
|
}
|
|
88
124
|
}
|
|
89
125
|
</script>
|
|
90
126
|
|
|
91
127
|
<script setup lang="ts">
|
|
92
|
-
import { ref, watch, computed } from 'vue'
|
|
128
|
+
import { ref, watch, computed, inject } from 'vue'
|
|
129
|
+
import { formItemContextKey } from 'element-plus'
|
|
93
130
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
interface Props {
|
|
97
|
-
/** 数组模式绑定值 [min, max],与 start/end 二选一 */
|
|
98
|
-
modelValue?: RangeValue
|
|
99
|
-
/** 双字段模式 - 起始值 */
|
|
100
|
-
start?: number | null
|
|
101
|
-
/** 双字段模式 - 结束值 */
|
|
102
|
-
end?: number | null
|
|
103
|
-
minPlaceholder?: string
|
|
104
|
-
maxPlaceholder?: string
|
|
105
|
-
/** 两个输入框之间的分隔文字 */
|
|
106
|
-
separator?: string
|
|
107
|
-
/** 允许输入的最小边界 */
|
|
108
|
-
min?: number
|
|
109
|
-
/** 允许输入的最大边界 */
|
|
110
|
-
max?: number
|
|
111
|
-
step?: number
|
|
112
|
-
/** 小数位数限制,如 2 表示最多两位小数 */
|
|
113
|
-
precision?: number
|
|
114
|
-
disabled?: boolean
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
131
|
+
const props = withDefaults(defineProps<NumberRangeProps>(), {
|
|
118
132
|
modelValue: undefined,
|
|
119
133
|
start: undefined,
|
|
120
134
|
end: undefined,
|
|
@@ -135,18 +149,22 @@ const emit = defineEmits<{
|
|
|
135
149
|
change: [value: RangeValue]
|
|
136
150
|
}>()
|
|
137
151
|
|
|
152
|
+
/** 检测是否处于 el-form-item 内,有则交给 form 校验,无则组件内部校验 */
|
|
153
|
+
const formItemContext = inject(formItemContextKey, undefined)
|
|
154
|
+
const useInternalValidation = computed(() => !formItemContext)
|
|
155
|
+
|
|
138
156
|
/** 根据是否传入 modelValue 自动判断绑定模式 */
|
|
139
157
|
const isArrayMode = computed(() => props.modelValue !== undefined)
|
|
140
158
|
|
|
141
|
-
function getInitMin():
|
|
142
|
-
return isArrayMode.value ? props.modelValue?.[0] : props.start
|
|
159
|
+
function getInitMin(): number | null {
|
|
160
|
+
return toNum(isArrayMode.value ? props.modelValue?.[0] : props.start)
|
|
143
161
|
}
|
|
144
|
-
function getInitMax():
|
|
145
|
-
return isArrayMode.value ? props.modelValue?.[1] : props.end
|
|
162
|
+
function getInitMax(): number | null {
|
|
163
|
+
return toNum(isArrayMode.value ? props.modelValue?.[1] : props.end)
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
function toDisplay(val:
|
|
149
|
-
if (val === null
|
|
166
|
+
function toDisplay(val: number | null): string {
|
|
167
|
+
if (val === null) return ''
|
|
150
168
|
return String(val)
|
|
151
169
|
}
|
|
152
170
|
|
|
@@ -202,6 +220,12 @@ const rangeInvalid = computed(() => {
|
|
|
202
220
|
|
|
203
221
|
const isValid = computed(() => !rangeInvalid.value)
|
|
204
222
|
|
|
223
|
+
/** 内部校验:仅在无 el-form-item 且两个输入框都失焦后展示 */
|
|
224
|
+
const blurred = ref(false)
|
|
225
|
+
const showInternalError = computed(() =>
|
|
226
|
+
useInternalValidation.value && blurred.value && rangeInvalid.value
|
|
227
|
+
)
|
|
228
|
+
|
|
205
229
|
/** 根据绑定模式 emit 对应事件 */
|
|
206
230
|
function emitValue(): void {
|
|
207
231
|
const minVal = formatNum(parseNum(minDisplay.value))
|
|
@@ -254,6 +278,7 @@ function onMinBlur(): void {
|
|
|
254
278
|
minDisplay.value = ''
|
|
255
279
|
}
|
|
256
280
|
emitValue()
|
|
281
|
+
if (!minFocused.value && !maxFocused.value) blurred.value = true
|
|
257
282
|
}
|
|
258
283
|
|
|
259
284
|
function onMaxBlur(): void {
|
|
@@ -268,9 +293,11 @@ function onMaxBlur(): void {
|
|
|
268
293
|
maxDisplay.value = ''
|
|
269
294
|
}
|
|
270
295
|
emitValue()
|
|
296
|
+
if (!minFocused.value && !maxFocused.value) blurred.value = true
|
|
271
297
|
}
|
|
272
298
|
|
|
273
299
|
function validate(): boolean {
|
|
300
|
+
blurred.value = true
|
|
274
301
|
return isValid.value
|
|
275
302
|
}
|
|
276
303
|
|
|
@@ -280,13 +307,14 @@ defineExpose({ validate, isValid })
|
|
|
280
307
|
watch(
|
|
281
308
|
() => isArrayMode.value ? props.modelValue : [props.start, props.end],
|
|
282
309
|
(val) => {
|
|
283
|
-
const newMin = toDisplay(val?.[0]
|
|
284
|
-
const newMax = toDisplay(val?.[1]
|
|
310
|
+
const newMin = toDisplay(toNum(val?.[0]))
|
|
311
|
+
const newMax = toDisplay(toNum(val?.[1]))
|
|
285
312
|
if (newMin !== minDisplay.value) minDisplay.value = newMin
|
|
286
313
|
if (newMax !== maxDisplay.value) maxDisplay.value = newMax
|
|
287
|
-
if ((val?.[0] == null
|
|
314
|
+
if (toNum(val?.[0]) == null && toNum(val?.[1]) == null) {
|
|
288
315
|
minFocused.value = false
|
|
289
316
|
maxFocused.value = false
|
|
317
|
+
blurred.value = false
|
|
290
318
|
}
|
|
291
319
|
},
|
|
292
320
|
{ deep: true }
|
|
@@ -298,12 +326,12 @@ watch(
|
|
|
298
326
|
display: inline-flex;
|
|
299
327
|
align-items: center;
|
|
300
328
|
gap: 8px;
|
|
301
|
-
|
|
302
|
-
position: relative;
|
|
329
|
+
width: 100%;
|
|
303
330
|
}
|
|
304
331
|
|
|
305
332
|
.number-range__input {
|
|
306
|
-
|
|
333
|
+
flex: 1;
|
|
334
|
+
min-width: 0;
|
|
307
335
|
}
|
|
308
336
|
|
|
309
337
|
.number-range__separator {
|
|
@@ -315,4 +343,16 @@ watch(
|
|
|
315
343
|
.number-range.is-disabled .number-range__separator {
|
|
316
344
|
color: var(--el-text-color-placeholder);
|
|
317
345
|
}
|
|
346
|
+
|
|
347
|
+
.number-range__input.is-error :deep(.el-input__wrapper) {
|
|
348
|
+
box-shadow: 0 0 0 1px var(--el-color-danger) inset;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.number-range__error {
|
|
352
|
+
width: 100%;
|
|
353
|
+
color: var(--el-color-danger);
|
|
354
|
+
font-size: 12px;
|
|
355
|
+
line-height: 1;
|
|
356
|
+
padding-top: 2px;
|
|
357
|
+
}
|
|
318
358
|
</style>
|