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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "7.0.25",
3
+ "version": "7.0.27",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",
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 }