vue2server7 7.0.25 → 7.0.27
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.
- package/frontEnd/src/components/DateRange.vue +317 -0
- package/frontEnd/src/pages/DateRangePage.vue +122 -0
- package/frontEnd/src/router/routes.js +10 -0
- package/package.json +1 -1
- package/test/dist.zip +0 -0
- package/test/fun.js +15 -0
- package/test/11111111.tx +0 -24
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="date-range" :class="{ 'is-disabled': disabled }">
|
|
3
|
+
<el-date-picker
|
|
4
|
+
v-model="startVal"
|
|
5
|
+
class="date-range__picker"
|
|
6
|
+
:class="{ 'is-error': showError }"
|
|
7
|
+
:type="pickerType"
|
|
8
|
+
:placeholder="startPlaceholder"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:disabled-date="disabledStartDate"
|
|
11
|
+
:value-format="valueFormat"
|
|
12
|
+
:format="displayFormat"
|
|
13
|
+
clearable
|
|
14
|
+
@change="onStartChange"
|
|
15
|
+
@focus="onStartFocus"
|
|
16
|
+
@blur="onStartBlur"
|
|
17
|
+
/>
|
|
18
|
+
<span class="date-range__separator">{{ separator }}</span>
|
|
19
|
+
<el-date-picker
|
|
20
|
+
v-model="endVal"
|
|
21
|
+
class="date-range__picker"
|
|
22
|
+
:class="{ 'is-error': showError }"
|
|
23
|
+
:type="pickerType"
|
|
24
|
+
:placeholder="endPlaceholder"
|
|
25
|
+
:disabled="disabled"
|
|
26
|
+
:disabled-date="disabledEndDate"
|
|
27
|
+
:value-format="valueFormat"
|
|
28
|
+
:format="displayFormat"
|
|
29
|
+
clearable
|
|
30
|
+
@change="onEndChange"
|
|
31
|
+
@focus="onEndFocus"
|
|
32
|
+
@blur="onEndBlur"
|
|
33
|
+
/>
|
|
34
|
+
<span v-if="showError" class="date-range__error">{{ activeErrorMessage }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script setup lang="ts">
|
|
39
|
+
import { ref, watch, computed } from 'vue'
|
|
40
|
+
|
|
41
|
+
export type DateRangeValue = [string | null, string | null]
|
|
42
|
+
|
|
43
|
+
export type MaxSpanDaysInput = number | [number]
|
|
44
|
+
|
|
45
|
+
function resolveMaxSpanDays(v: MaxSpanDaysInput | undefined): number | null {
|
|
46
|
+
if (v === undefined) return null
|
|
47
|
+
const n = Array.isArray(v) ? v[0] : v
|
|
48
|
+
if (typeof n !== 'number' || !Number.isFinite(n) || n <= 0) return null
|
|
49
|
+
return Math.floor(n)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function startOfLocalDay(d: Date): Date {
|
|
53
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 按自然日计:end 在 start 当天为 0,隔天为 1 … */
|
|
57
|
+
function calendarDayDiff(start: Date, end: Date): number {
|
|
58
|
+
const s = startOfLocalDay(start).getTime()
|
|
59
|
+
const e = startOfLocalDay(end).getTime()
|
|
60
|
+
return Math.round((e - s) / 86400000)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 含首尾的自然日个数,如 1 月 1 日~1 月 15 日为 15 */
|
|
64
|
+
function inclusiveDaySpan(start: Date, end: Date): number {
|
|
65
|
+
return calendarDayDiff(start, end) + 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const props = withDefaults(defineProps<{
|
|
69
|
+
/** 数组模式绑定值 [start, end] */
|
|
70
|
+
modelValue?: DateRangeValue
|
|
71
|
+
/** 双字段模式 - 起始日期 */
|
|
72
|
+
start?: string | null
|
|
73
|
+
/** 双字段模式 - 结束日期 */
|
|
74
|
+
end?: string | null
|
|
75
|
+
startPlaceholder?: string
|
|
76
|
+
endPlaceholder?: string
|
|
77
|
+
separator?: string
|
|
78
|
+
/** date-picker 的 type,如 date / datetime / month / year */
|
|
79
|
+
pickerType?: 'date' | 'datetime' | 'month' | 'year'
|
|
80
|
+
/** 传给 el-date-picker 的 value-format */
|
|
81
|
+
valueFormat?: string
|
|
82
|
+
/** 传给 el-date-picker 的 format(显示格式) */
|
|
83
|
+
displayFormat?: string
|
|
84
|
+
disabled?: boolean
|
|
85
|
+
/** 是否限制结束日期不能早于开始日期(禁用不合法的日期) */
|
|
86
|
+
linkage?: boolean
|
|
87
|
+
/**
|
|
88
|
+
* 最长区间(自然日,含起止当天)。可传数字如 15,或单元素数组 [15]。
|
|
89
|
+
* 仅对 pickerType 为 date / datetime 生效。
|
|
90
|
+
*/
|
|
91
|
+
maxSpanDays?: MaxSpanDaysInput
|
|
92
|
+
errorMessage?: string
|
|
93
|
+
/** 超出 maxSpanDays 时的提示(默认带天数) */
|
|
94
|
+
maxSpanErrorMessage?: string
|
|
95
|
+
}>(), {
|
|
96
|
+
modelValue: undefined,
|
|
97
|
+
start: undefined,
|
|
98
|
+
end: undefined,
|
|
99
|
+
startPlaceholder: '开始日期',
|
|
100
|
+
endPlaceholder: '结束日期',
|
|
101
|
+
separator: '至',
|
|
102
|
+
pickerType: 'date',
|
|
103
|
+
valueFormat: 'YYYY-MM-DD',
|
|
104
|
+
displayFormat: undefined,
|
|
105
|
+
disabled: false,
|
|
106
|
+
linkage: true,
|
|
107
|
+
maxSpanDays: undefined,
|
|
108
|
+
errorMessage: '结束日期不能早于开始日期',
|
|
109
|
+
maxSpanErrorMessage: undefined
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const emit = defineEmits<{
|
|
113
|
+
'update:modelValue': [value: DateRangeValue]
|
|
114
|
+
'update:start': [value: string | null]
|
|
115
|
+
'update:end': [value: string | null]
|
|
116
|
+
'validate': [valid: boolean]
|
|
117
|
+
}>()
|
|
118
|
+
|
|
119
|
+
const isArrayMode = computed(() => props.modelValue !== undefined)
|
|
120
|
+
|
|
121
|
+
const startVal = ref<string | null>(null)
|
|
122
|
+
const endVal = ref<string | null>(null)
|
|
123
|
+
|
|
124
|
+
const startFocused = ref(false)
|
|
125
|
+
const endFocused = ref(false)
|
|
126
|
+
const hasBlurred = ref(false)
|
|
127
|
+
|
|
128
|
+
function toDate(val: string | null | undefined): Date | null {
|
|
129
|
+
if (!val) return null
|
|
130
|
+
const d = new Date(val)
|
|
131
|
+
return Number.isNaN(d.getTime()) ? null : d
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const effectiveMaxSpan = computed(() => resolveMaxSpanDays(props.maxSpanDays))
|
|
135
|
+
|
|
136
|
+
const spanConstraintApplies = computed(
|
|
137
|
+
() =>
|
|
138
|
+
effectiveMaxSpan.value !== null &&
|
|
139
|
+
(props.pickerType === 'date' || props.pickerType === 'datetime')
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
const orderInvalid = computed(() => {
|
|
143
|
+
const s = toDate(startVal.value)
|
|
144
|
+
const e = toDate(endVal.value)
|
|
145
|
+
return s !== null && e !== null && e < s
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const spanInvalid = computed(() => {
|
|
149
|
+
if (!spanConstraintApplies.value || effectiveMaxSpan.value === null) return false
|
|
150
|
+
const s = toDate(startVal.value)
|
|
151
|
+
const e = toDate(endVal.value)
|
|
152
|
+
if (s === null || e === null || e < s) return false
|
|
153
|
+
return inclusiveDaySpan(s, e) > effectiveMaxSpan.value
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const rangeInvalid = computed(() => orderInvalid.value || spanInvalid.value)
|
|
157
|
+
|
|
158
|
+
const showError = computed(
|
|
159
|
+
() => hasBlurred.value && !startFocused.value && !endFocused.value && rangeInvalid.value
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const activeErrorMessage = computed(() => {
|
|
163
|
+
if (!showError.value) return ''
|
|
164
|
+
if (orderInvalid.value) return props.errorMessage
|
|
165
|
+
if (spanInvalid.value && effectiveMaxSpan.value !== null) {
|
|
166
|
+
return (
|
|
167
|
+
props.maxSpanErrorMessage ??
|
|
168
|
+
`日期区间最长为 ${effectiveMaxSpan.value} 天(含起止当天)`
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
return props.errorMessage
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const disabledStartDate = (date: Date): boolean => {
|
|
175
|
+
if (props.linkage && endVal.value) {
|
|
176
|
+
const e = toDate(endVal.value)
|
|
177
|
+
if (e !== null && date.getTime() > e.getTime()) return true
|
|
178
|
+
}
|
|
179
|
+
if (
|
|
180
|
+
spanConstraintApplies.value &&
|
|
181
|
+
effectiveMaxSpan.value !== null &&
|
|
182
|
+
endVal.value
|
|
183
|
+
) {
|
|
184
|
+
const e = toDate(endVal.value)
|
|
185
|
+
if (e !== null) {
|
|
186
|
+
const maxDiff = effectiveMaxSpan.value - 1
|
|
187
|
+
if (calendarDayDiff(date, e) > maxDiff) return true
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const disabledEndDate = (date: Date): boolean => {
|
|
194
|
+
if (props.linkage && startVal.value) {
|
|
195
|
+
const s = toDate(startVal.value)
|
|
196
|
+
if (s !== null && date.getTime() < s.getTime()) return true
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
spanConstraintApplies.value &&
|
|
200
|
+
effectiveMaxSpan.value !== null &&
|
|
201
|
+
startVal.value
|
|
202
|
+
) {
|
|
203
|
+
const s = toDate(startVal.value)
|
|
204
|
+
if (s !== null) {
|
|
205
|
+
const maxDiff = effectiveMaxSpan.value - 1
|
|
206
|
+
if (calendarDayDiff(s, date) > maxDiff) return true
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function emitValue() {
|
|
213
|
+
const s = startVal.value || null
|
|
214
|
+
const e = endVal.value || null
|
|
215
|
+
if (isArrayMode.value) {
|
|
216
|
+
emit('update:modelValue', [s, e])
|
|
217
|
+
} else {
|
|
218
|
+
emit('update:start', s)
|
|
219
|
+
emit('update:end', e)
|
|
220
|
+
}
|
|
221
|
+
emit('validate', !rangeInvalid.value)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function onStartChange() {
|
|
225
|
+
emitValue()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function onEndChange() {
|
|
229
|
+
emitValue()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onStartFocus() {
|
|
233
|
+
startFocused.value = true
|
|
234
|
+
hasBlurred.value = false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function onEndFocus() {
|
|
238
|
+
endFocused.value = true
|
|
239
|
+
hasBlurred.value = false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onStartBlur() {
|
|
243
|
+
startFocused.value = false
|
|
244
|
+
emitValue()
|
|
245
|
+
if (!endFocused.value) hasBlurred.value = true
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function onEndBlur() {
|
|
249
|
+
endFocused.value = false
|
|
250
|
+
emitValue()
|
|
251
|
+
if (!startFocused.value) hasBlurred.value = true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function validate(): boolean {
|
|
255
|
+
hasBlurred.value = true
|
|
256
|
+
return !rangeInvalid.value
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function reset() {
|
|
260
|
+
startVal.value = null
|
|
261
|
+
endVal.value = null
|
|
262
|
+
hasBlurred.value = false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
defineExpose({ validate, reset })
|
|
266
|
+
|
|
267
|
+
watch(
|
|
268
|
+
() => ({
|
|
269
|
+
isArr: isArrayMode.value,
|
|
270
|
+
s: isArrayMode.value ? props.modelValue?.[0] : props.start,
|
|
271
|
+
e: isArrayMode.value ? props.modelValue?.[1] : props.end
|
|
272
|
+
}),
|
|
273
|
+
({ s, e }) => {
|
|
274
|
+
const newS = s || null
|
|
275
|
+
const newE = e || null
|
|
276
|
+
if (newS !== startVal.value) startVal.value = newS
|
|
277
|
+
if (newE !== endVal.value) endVal.value = newE
|
|
278
|
+
},
|
|
279
|
+
{ immediate: true }
|
|
280
|
+
)
|
|
281
|
+
</script>
|
|
282
|
+
|
|
283
|
+
<style scoped>
|
|
284
|
+
.date-range {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 8px;
|
|
288
|
+
flex-wrap: wrap;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.date-range__picker {
|
|
292
|
+
flex: 1;
|
|
293
|
+
min-width: 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.date-range__separator {
|
|
297
|
+
color: var(--el-text-color-primary);
|
|
298
|
+
font-size: 14px;
|
|
299
|
+
flex-shrink: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.date-range.is-disabled .date-range__separator {
|
|
303
|
+
color: var(--el-text-color-placeholder);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.date-range__picker.is-error :deep(.el-input__wrapper) {
|
|
307
|
+
box-shadow: 0 0 0 1px var(--el-color-danger) inset;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.date-range__error {
|
|
311
|
+
width: 100%;
|
|
312
|
+
color: var(--el-color-danger);
|
|
313
|
+
font-size: 12px;
|
|
314
|
+
line-height: 1;
|
|
315
|
+
padding-top: 4px;
|
|
316
|
+
}
|
|
317
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="page date-range-page">
|
|
3
|
+
<h1 class="title">日期区间组件</h1>
|
|
4
|
+
<p class="desc">演示 <code>DateRange</code> 的数组绑定、双字段绑定与不同 <code>pickerType</code>。</p>
|
|
5
|
+
|
|
6
|
+
<el-card class="demo-card" shadow="never">
|
|
7
|
+
<template #header>
|
|
8
|
+
<span>数组绑定 <code>v-model</code></span>
|
|
9
|
+
</template>
|
|
10
|
+
<DateRange v-model="rangeArray" class="demo-control" />
|
|
11
|
+
<div class="demo-output">
|
|
12
|
+
当前值:<code>{{ JSON.stringify(rangeArray) }}</code>
|
|
13
|
+
</div>
|
|
14
|
+
</el-card>
|
|
15
|
+
|
|
16
|
+
<el-card class="demo-card" shadow="never">
|
|
17
|
+
<template #header>
|
|
18
|
+
<span>双字段绑定 <code>start</code> / <code>end</code></span>
|
|
19
|
+
</template>
|
|
20
|
+
<DateRange
|
|
21
|
+
v-model:start="startField"
|
|
22
|
+
v-model:end="endField"
|
|
23
|
+
class="demo-control"
|
|
24
|
+
/>
|
|
25
|
+
<div class="demo-output">
|
|
26
|
+
start:<code>{{ startField ?? 'null' }}</code>,end:<code>{{ endField ?? 'null' }}</code>
|
|
27
|
+
</div>
|
|
28
|
+
</el-card>
|
|
29
|
+
|
|
30
|
+
<el-card class="demo-card" shadow="never">
|
|
31
|
+
<template #header>
|
|
32
|
+
<span>最长区间 <code>max-span-days</code>(如 15 或 <code>[15]</code>,含首尾共 15 个自然日)</span>
|
|
33
|
+
</template>
|
|
34
|
+
<DateRange v-model="rangeMaxSpan" :max-span-days="[15]" class="demo-control" />
|
|
35
|
+
<div class="demo-output">
|
|
36
|
+
当前值:<code>{{ JSON.stringify(rangeMaxSpan) }}</code>
|
|
37
|
+
</div>
|
|
38
|
+
</el-card>
|
|
39
|
+
|
|
40
|
+
<el-card class="demo-card" shadow="never">
|
|
41
|
+
<template #header>
|
|
42
|
+
<span>日期时间 <code>pickerType="datetime"</code></span>
|
|
43
|
+
</template>
|
|
44
|
+
<DateRange
|
|
45
|
+
v-model="rangeDatetime"
|
|
46
|
+
picker-type="datetime"
|
|
47
|
+
value-format="YYYY-MM-DD HH:mm:ss"
|
|
48
|
+
start-placeholder="开始时间"
|
|
49
|
+
end-placeholder="结束时间"
|
|
50
|
+
class="demo-control"
|
|
51
|
+
/>
|
|
52
|
+
<div class="demo-output">
|
|
53
|
+
当前值:<code>{{ JSON.stringify(rangeDatetime) }}</code>
|
|
54
|
+
</div>
|
|
55
|
+
</el-card>
|
|
56
|
+
|
|
57
|
+
<el-card class="demo-card" shadow="never">
|
|
58
|
+
<template #header>
|
|
59
|
+
<span>禁用态</span>
|
|
60
|
+
</template>
|
|
61
|
+
<DateRange
|
|
62
|
+
v-model="rangeDisabled"
|
|
63
|
+
disabled
|
|
64
|
+
class="demo-control"
|
|
65
|
+
/>
|
|
66
|
+
</el-card>
|
|
67
|
+
</section>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref } from 'vue'
|
|
72
|
+
import DateRange, { type DateRangeValue } from '../components/DateRange.vue'
|
|
73
|
+
|
|
74
|
+
const rangeArray = ref<DateRangeValue>([null, null])
|
|
75
|
+
const startField = ref<string | null>(null)
|
|
76
|
+
const endField = ref<string | null>(null)
|
|
77
|
+
const rangeMaxSpan = ref<DateRangeValue>([null, null])
|
|
78
|
+
const rangeDatetime = ref<DateRangeValue>([null, null])
|
|
79
|
+
const rangeDisabled = ref<DateRangeValue>(['2026-01-01', '2026-01-31'])
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<style scoped>
|
|
83
|
+
.page.date-range-page {
|
|
84
|
+
padding: 20px 24px;
|
|
85
|
+
max-width: 720px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.title {
|
|
89
|
+
margin: 0 0 8px;
|
|
90
|
+
font-size: 20px;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.desc {
|
|
95
|
+
margin: 0 0 20px;
|
|
96
|
+
color: var(--el-text-color-secondary);
|
|
97
|
+
font-size: 14px;
|
|
98
|
+
line-height: 1.5;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.demo-card {
|
|
102
|
+
margin-bottom: 16px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.demo-card:last-child {
|
|
106
|
+
margin-bottom: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.demo-control {
|
|
110
|
+
max-width: 560px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.demo-output {
|
|
114
|
+
margin-top: 12px;
|
|
115
|
+
font-size: 13px;
|
|
116
|
+
color: var(--el-text-color-regular);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.demo-output code {
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
@@ -3,6 +3,7 @@ import CascaderPage from '../pages/CascaderPage.vue'
|
|
|
3
3
|
import ExportExcelPage from '../pages/ExportExcelPage.vue'
|
|
4
4
|
import ImportTablePage from '../pages/ImportTablePage.vue'
|
|
5
5
|
import PositionReportPage from '../pages/PositionReportPage.vue'
|
|
6
|
+
import DateRangePage from '../pages/DateRangePage.vue'
|
|
6
7
|
|
|
7
8
|
export const routes = [
|
|
8
9
|
{
|
|
@@ -53,5 +54,14 @@ export const routes = [
|
|
|
53
54
|
title: '头寸报备',
|
|
54
55
|
showInMenu: true
|
|
55
56
|
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: '/date-range',
|
|
60
|
+
name: 'DateRange',
|
|
61
|
+
component: DateRangePage,
|
|
62
|
+
meta: {
|
|
63
|
+
title: '日期区间',
|
|
64
|
+
showInMenu: true
|
|
65
|
+
}
|
|
56
66
|
}
|
|
57
67
|
]
|
package/package.json
CHANGED
package/test/dist.zip
ADDED
|
Binary file
|
package/test/fun.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function addIdText(list = []) {
|
|
2
|
+
const stack = [...list]
|
|
3
|
+
|
|
4
|
+
while (stack.length) {
|
|
5
|
+
const node = stack.pop()
|
|
6
|
+
|
|
7
|
+
node.idText = `${node.id}-${node.text}`
|
|
8
|
+
|
|
9
|
+
if (Array.isArray(node.children) && node.children.length) {
|
|
10
|
+
stack.push(...node.children)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return list
|
|
15
|
+
}
|
package/test/11111111.tx
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { App, Component } from 'vue'
|
|
2
|
-
import NumberInput from './NumberInput.vue'
|
|
3
|
-
|
|
4
|
-
// ✅ 组件列表类型声明
|
|
5
|
-
const components: Component[] = [NumberInput]
|
|
6
|
-
|
|
7
|
-
// ✅ 插件类型
|
|
8
|
-
const install = (app: App): void => {
|
|
9
|
-
components.forEach((comp) => {
|
|
10
|
-
app.component(
|
|
11
|
-
// @ts-ignore 兼容 name 类型
|
|
12
|
-
comp.name as string,
|
|
13
|
-
comp
|
|
14
|
-
)
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ✅ 导出(支持 app.use)
|
|
19
|
-
export default {
|
|
20
|
-
install
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ✅ 也可以单独导出组件(可选)
|
|
24
|
-
export { NumberInput }
|