vue2server7 7.0.12 → 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,22 +25,30 @@
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
- export 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
+
35
45
  export interface NumberRangeProps {
36
- /** 数组模式绑定值 [min, max],与 start/end 二选一 */
46
+ /** 数组模式绑定值 [min, max],与 start/end 二选一;值可以是 string 或 number */
37
47
  modelValue?: RangeValue
38
- /** 双字段模式 - 起始值 */
39
- start?: number | null
40
- /** 双字段模式 - 结束值 */
41
- end?: number | null
48
+ /** 双字段模式 - 起始值(支持 string 类型数字) */
49
+ start?: number | string | null
50
+ /** 双字段模式 - 结束值(支持 string 类型数字) */
51
+ end?: number | string | null
42
52
  minPlaceholder?: string
43
53
  maxPlaceholder?: string
44
54
  /** 两个输入框之间的分隔文字 */
@@ -82,10 +92,10 @@ export function rangeRequired(
82
92
  trigger,
83
93
  validator: startField
84
94
  ? (_r: unknown, _v: unknown, cb: ValidatorCb, source: Record<string, unknown>) =>
85
- cb(source[startField] == null || source[startField] === '' ? new Error(message) : undefined)
95
+ cb(toNum(source[startField]) == null ? new Error(message) : undefined)
86
96
  : (_r: unknown, v: unknown, cb: ValidatorCb) => {
87
97
  const arr = v as RangeValue | undefined
88
- 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)
89
99
  }
90
100
  }
91
101
  }
@@ -99,18 +109,24 @@ export function rangeRule(
99
109
  return {
100
110
  trigger,
101
111
  validator: startField
102
- ? (_r: unknown, v: unknown, cb: ValidatorCb, source: Record<string, unknown>) =>
103
- cb(source[startField] != null && v != null && (v as number) < (source[startField] as number) ? new Error(message) : undefined)
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
+ }
104
117
  : (_r: unknown, v: unknown, cb: ValidatorCb) => {
105
118
  const arr = v as RangeValue | undefined
106
- cb(arr?.[0] != null && arr?.[1] != null && arr[1] < arr[0] ? new Error(message) : undefined)
119
+ const minNum = toNum(arr?.[0])
120
+ const maxNum = toNum(arr?.[1])
121
+ cb(minNum != null && maxNum != null && maxNum < minNum ? new Error(message) : undefined)
107
122
  }
108
123
  }
109
124
  }
110
125
  </script>
111
126
 
112
127
  <script setup lang="ts">
113
- import { ref, watch, computed } from 'vue'
128
+ import { ref, watch, computed, inject } from 'vue'
129
+ import { formItemContextKey } from 'element-plus'
114
130
 
115
131
  const props = withDefaults(defineProps<NumberRangeProps>(), {
116
132
  modelValue: undefined,
@@ -133,18 +149,22 @@ const emit = defineEmits<{
133
149
  change: [value: RangeValue]
134
150
  }>()
135
151
 
152
+ /** 检测是否处于 el-form-item 内,有则交给 form 校验,无则组件内部校验 */
153
+ const formItemContext = inject(formItemContextKey, undefined)
154
+ const useInternalValidation = computed(() => !formItemContext)
155
+
136
156
  /** 根据是否传入 modelValue 自动判断绑定模式 */
137
157
  const isArrayMode = computed(() => props.modelValue !== undefined)
138
158
 
139
- function getInitMin(): number | null | undefined {
140
- return isArrayMode.value ? props.modelValue?.[0] : props.start
159
+ function getInitMin(): number | null {
160
+ return toNum(isArrayMode.value ? props.modelValue?.[0] : props.start)
141
161
  }
142
- function getInitMax(): number | null | undefined {
143
- return isArrayMode.value ? props.modelValue?.[1] : props.end
162
+ function getInitMax(): number | null {
163
+ return toNum(isArrayMode.value ? props.modelValue?.[1] : props.end)
144
164
  }
145
165
 
146
- function toDisplay(val: number | null | undefined): string {
147
- if (val === null || val === undefined) return ''
166
+ function toDisplay(val: number | null): string {
167
+ if (val === null) return ''
148
168
  return String(val)
149
169
  }
150
170
 
@@ -200,6 +220,12 @@ const rangeInvalid = computed(() => {
200
220
 
201
221
  const isValid = computed(() => !rangeInvalid.value)
202
222
 
223
+ /** 内部校验:仅在无 el-form-item 且两个输入框都失焦后展示 */
224
+ const blurred = ref(false)
225
+ const showInternalError = computed(() =>
226
+ useInternalValidation.value && blurred.value && rangeInvalid.value
227
+ )
228
+
203
229
  /** 根据绑定模式 emit 对应事件 */
204
230
  function emitValue(): void {
205
231
  const minVal = formatNum(parseNum(minDisplay.value))
@@ -252,6 +278,7 @@ function onMinBlur(): void {
252
278
  minDisplay.value = ''
253
279
  }
254
280
  emitValue()
281
+ if (!minFocused.value && !maxFocused.value) blurred.value = true
255
282
  }
256
283
 
257
284
  function onMaxBlur(): void {
@@ -266,9 +293,11 @@ function onMaxBlur(): void {
266
293
  maxDisplay.value = ''
267
294
  }
268
295
  emitValue()
296
+ if (!minFocused.value && !maxFocused.value) blurred.value = true
269
297
  }
270
298
 
271
299
  function validate(): boolean {
300
+ blurred.value = true
272
301
  return isValid.value
273
302
  }
274
303
 
@@ -278,13 +307,14 @@ defineExpose({ validate, isValid })
278
307
  watch(
279
308
  () => isArrayMode.value ? props.modelValue : [props.start, props.end],
280
309
  (val) => {
281
- const newMin = toDisplay(val?.[0] ?? null)
282
- const newMax = toDisplay(val?.[1] ?? null)
310
+ const newMin = toDisplay(toNum(val?.[0]))
311
+ const newMax = toDisplay(toNum(val?.[1]))
283
312
  if (newMin !== minDisplay.value) minDisplay.value = newMin
284
313
  if (newMax !== maxDisplay.value) maxDisplay.value = newMax
285
- if ((val?.[0] == null) && (val?.[1] == null)) {
314
+ if (toNum(val?.[0]) == null && toNum(val?.[1]) == null) {
286
315
  minFocused.value = false
287
316
  maxFocused.value = false
317
+ blurred.value = false
288
318
  }
289
319
  },
290
320
  { deep: true }
@@ -296,12 +326,12 @@ watch(
296
326
  display: inline-flex;
297
327
  align-items: center;
298
328
  gap: 8px;
299
- flex-wrap: wrap;
300
- position: relative;
329
+ width: 100%;
301
330
  }
302
331
 
303
332
  .number-range__input {
304
- width: 140px;
333
+ flex: 1;
334
+ min-width: 0;
305
335
  }
306
336
 
307
337
  .number-range__separator {
@@ -313,4 +343,16 @@ watch(
313
343
  .number-range.is-disabled .number-range__separator {
314
344
  color: var(--el-text-color-placeholder);
315
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
+ }
316
358
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "7.0.12",
3
+ "version": "7.0.13",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",