@wangxinowo/vue-datepicker-next 1.0.2 → 1.0.5

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,1031 +1,1105 @@
1
- import PropTypes from '../../_util/vue-types';
2
- import BaseMixin from '../../_util/BaseMixin';
3
- import {
4
- getOptionProps,
5
- hasProp,
6
- mergeProps,
7
- getComponentFromProp,
8
- getListeners,
9
- } from '../../_util/props-util';
10
- import moment from 'moment';
11
- import KeyCode from '../../_util/KeyCode';
12
- import CalendarPart from './range-calendar/CalendarPart';
13
- import TodayButton from './calendar/TodayButton';
14
- import OkButton from './calendar/OkButton';
15
- import TimePickerButton from './calendar/TimePickerButton';
16
- import CommonMixin from './mixin/CommonMixin';
17
- import enUs from './locale/en_US';
18
- import { syncTime, getTodayTime, isAllowedDate } from './util/';
19
- import { goTime, goStartMonth, goEndMonth, includesTime } from './util/toTime';
20
-
21
- function noop() {}
22
-
23
- function isEmptyArray(arr) {
24
- return Array.isArray(arr) && (arr.length === 0 || arr.every(i => !i));
25
- }
26
-
27
- function isArraysEqual(a, b) {
28
- if (a === b) return true;
29
- if (a === null || typeof a === 'undefined' || b === null || typeof b === 'undefined') {
30
- return false;
31
- }
32
- if (a.length !== b.length) return false;
33
-
34
- for (let i = 0; i < a.length; ++i) {
35
- if (a[i] !== b[i]) return false;
36
- }
37
- return true;
38
- }
39
-
40
- function getValueFromSelectedValue(selectedValue) {
41
- let [start, end] = selectedValue;
42
- if (end && (start === undefined || start === null)) {
43
- start = end.clone().subtract(1, 'month');
44
- }
45
-
46
- if (start && (end === undefined || end === null)) {
47
- end = start.clone().add(1, 'month');
48
- }
49
- return [start, end];
50
- }
51
-
52
- function normalizeAnchor(props, init) {
53
- const selectedValue = props.selectedValue || (init && props.defaultSelectedValue);
54
- const value = props.value || (init && props.defaultValue);
55
- const normalizedValue = value
56
- ? getValueFromSelectedValue(value)
57
- : getValueFromSelectedValue(selectedValue);
58
- return !isEmptyArray(normalizedValue)
59
- ? normalizedValue
60
- : init && [moment(), moment().add(1, 'months')];
61
- }
62
-
63
- function generateOptions(length, extraOptionGen) {
64
- const arr = extraOptionGen ? extraOptionGen().concat() : [];
65
- for (let value = 0; value < length; value++) {
66
- if (arr.indexOf(value) === -1) {
67
- arr.push(value);
68
- }
69
- }
70
- return arr;
71
- }
72
-
73
- function onInputSelect(direction, value, cause) {
74
- if (!value) {
75
- return;
76
- }
77
- const originalValue = this.sSelectedValue;
78
- const selectedValue = originalValue.concat();
79
- const index = direction === 'left' ? 0 : 1;
80
- selectedValue[index] = value;
81
- if (selectedValue[0] && this.compare(selectedValue[0], selectedValue[1]) > 0) {
82
- selectedValue[1 - index] = this.sShowTimePicker ? selectedValue[index] : undefined;
83
- }
84
- this.__emit('inputSelect', selectedValue);
85
- this.fireSelectValueChange(selectedValue, null, cause || { source: 'dateInput' });
86
- }
87
-
88
- const RangeCalendar = {
89
- props: {
90
- locale: PropTypes.object.def(enUs),
91
- visible: PropTypes.bool.def(true),
92
- prefixCls: PropTypes.string.def('rc-calendar'),
93
- dateInputPlaceholder: PropTypes.any,
94
- separator: PropTypes.string.def('~'),
95
- defaultValue: PropTypes.any,
96
- value: PropTypes.any,
97
- hoverValue: PropTypes.any,
98
- mode: PropTypes.arrayOf(PropTypes.oneOf(['time', 'date', 'month', 'year', 'decade'])),
99
- showDateInput: PropTypes.bool.def(true),
100
- timePicker: PropTypes.any,
101
- showOk: PropTypes.bool,
102
- showToday: PropTypes.bool.def(true),
103
- defaultSelectedValue: PropTypes.array.def([]),
104
- selectedValue: PropTypes.array,
105
- showClear: PropTypes.bool,
106
- showWeekNumber: PropTypes.bool,
107
- // locale: PropTypes.object,
108
- // onChange: PropTypes.func,
109
- // onSelect: PropTypes.func,
110
- // onValueChange: PropTypes.func,
111
- // onHoverChange: PropTypes.func,
112
- // onPanelChange: PropTypes.func,
113
- format: PropTypes.oneOfType([
114
- PropTypes.string,
115
- PropTypes.arrayOf(PropTypes.string),
116
- PropTypes.func,
117
- ]),
118
- // onClear: PropTypes.func,
119
- type: PropTypes.any.def('both'),
120
- disabledDate: PropTypes.func,
121
- disabledTime: PropTypes.func.def(noop),
122
- renderFooter: PropTypes.func.def(() => null),
123
- renderSidebar: PropTypes.func.def(() => null),
124
- dateRender: PropTypes.func,
125
- clearIcon: PropTypes.any,
126
- inputReadOnly: PropTypes.bool,
127
- },
128
-
129
- mixins: [BaseMixin, CommonMixin],
130
-
131
- data() {
132
- const props = this.$props;
133
- const selectedValue = props.selectedValue || props.defaultSelectedValue;
134
- const value = normalizeAnchor(props, 1);
135
- return {
136
- sSelectedValue: selectedValue,
137
- prevSelectedValue: selectedValue,
138
- firstSelectedValue: null,
139
- sHoverValue: props.hoverValue || [],
140
- sValue: value,
141
- sShowTimePicker: false,
142
- sMode: props.mode || ['date', 'date'],
143
- sPanelTriggerSource: '', // Trigger by which picker panel: 'start' & 'end'
144
- };
145
- },
146
- watch: {
147
- value() {
148
- const newState = {};
149
- newState.sValue = normalizeAnchor(this.$props, 0);
150
- this.setState(newState);
151
- },
152
- hoverValue(val) {
153
- if (!isArraysEqual(this.sHoverValue, val)) {
154
- this.setState({ sHoverValue: val });
155
- }
156
- },
157
- selectedValue(val) {
158
- const newState = {};
159
- newState.sSelectedValue = val;
160
- newState.prevSelectedValue = val;
161
- this.setState(newState);
162
- },
163
- mode(val) {
164
- if (!isArraysEqual(this.sMode, val)) {
165
- this.setState({ sMode: val });
166
- }
167
- },
168
- },
169
-
170
- methods: {
171
- onDatePanelEnter() {
172
- if (this.hasSelectedValue()) {
173
- this.fireHoverValueChange(this.sSelectedValue.concat());
174
- }
175
- },
176
-
177
- onDatePanelLeave() {
178
- if (this.hasSelectedValue()) {
179
- this.fireHoverValueChange([]);
180
- }
181
- },
182
-
183
- onSelect(value) {
184
- const { type, sSelectedValue, prevSelectedValue, firstSelectedValue } = this;
185
- let nextSelectedValue;
186
- if (type === 'both') {
187
- if (!firstSelectedValue) {
188
- syncTime(prevSelectedValue[0], value);
189
- nextSelectedValue = [value];
190
- } else if (this.compare(firstSelectedValue, value) < 0) {
191
- syncTime(prevSelectedValue[1], value);
192
- nextSelectedValue = [firstSelectedValue, value];
193
- } else {
194
- syncTime(prevSelectedValue[0], value);
195
- syncTime(prevSelectedValue[1], firstSelectedValue);
196
- nextSelectedValue = [value, firstSelectedValue];
197
- }
198
- } else if (type === 'start') {
199
- syncTime(prevSelectedValue[0], value);
200
- const endValue = sSelectedValue[1];
201
- nextSelectedValue =
202
- endValue && this.compare(endValue, value) > 0 ? [value, endValue] : [value, null];
203
- } else {
204
- // type === 'end'
205
- const startValue = sSelectedValue[0];
206
- if (startValue && this.compare(startValue, value) <= 0) {
207
- syncTime(prevSelectedValue[1], value);
208
- nextSelectedValue = [startValue, value];
209
- } else {
210
- // 先选择结束日期,正确放在第二个位置
211
- syncTime(prevSelectedValue[1], value);
212
- nextSelectedValue = [null, value];
213
- }
214
- }
215
-
216
- this.fireSelectValueChange(nextSelectedValue);
217
- },
218
-
219
- onKeyDown(event) {
220
- if (event.target.nodeName.toLowerCase() === 'input') {
221
- return;
222
- }
223
-
224
- const { keyCode } = event;
225
- const ctrlKey = event.ctrlKey || event.metaKey;
226
-
227
- const {
228
- sSelectedValue: selectedValue,
229
- sHoverValue: hoverValue,
230
- firstSelectedValue,
231
- sValue: value, // Value is used for `CalendarPart` current page
232
- } = this.$data;
233
- const { disabledDate } = this.$props;
234
-
235
- // Update last time of the picker
236
- const updateHoverPoint = func => {
237
- // Change hover to make focus in UI
238
- let currentHoverTime;
239
- let nextHoverTime;
240
- let nextHoverValue;
241
-
242
- if (!firstSelectedValue) {
243
- currentHoverTime = hoverValue[0] || selectedValue[0] || value[0] || moment();
244
- nextHoverTime = func(currentHoverTime);
245
- nextHoverValue = [nextHoverTime];
246
- this.fireHoverValueChange(nextHoverValue);
247
- } else {
248
- if (hoverValue.length === 1) {
249
- currentHoverTime = hoverValue[0].clone();
250
- nextHoverTime = func(currentHoverTime);
251
- nextHoverValue = this.onDayHover(nextHoverTime);
252
- } else {
253
- currentHoverTime = hoverValue[0].isSame(firstSelectedValue, 'day')
254
- ? hoverValue[1]
255
- : hoverValue[0];
256
- nextHoverTime = func(currentHoverTime);
257
- nextHoverValue = this.onDayHover(nextHoverTime);
258
- }
259
- }
260
-
261
- // Find origin hover time on value index
262
- if (nextHoverValue.length >= 2) {
263
- const miss = nextHoverValue.some(ht => !includesTime(value, ht, 'month'));
264
- if (miss) {
265
- const newValue = nextHoverValue.slice().sort((t1, t2) => t1.valueOf() - t2.valueOf());
266
- if (newValue[0].isSame(newValue[1], 'month')) {
267
- newValue[1] = newValue[0].clone().add(1, 'month');
268
- }
269
- this.fireValueChange(newValue);
270
- }
271
- } else if (nextHoverValue.length === 1) {
272
- // If only one value, let's keep the origin panel
273
- let oriValueIndex = value.findIndex(time => time.isSame(currentHoverTime, 'month'));
274
- if (oriValueIndex === -1) oriValueIndex = 0;
275
-
276
- if (value.every(time => !time.isSame(nextHoverTime, 'month'))) {
277
- const newValue = value.slice();
278
- newValue[oriValueIndex] = nextHoverTime.clone();
279
- this.fireValueChange(newValue);
280
- }
281
- }
282
-
283
- event.preventDefault();
284
-
285
- return nextHoverTime;
286
- };
287
-
288
- switch (keyCode) {
289
- case KeyCode.DOWN:
290
- updateHoverPoint(time => goTime(time, 1, 'weeks'));
291
- return;
292
- case KeyCode.UP:
293
- updateHoverPoint(time => goTime(time, -1, 'weeks'));
294
- return;
295
- case KeyCode.LEFT:
296
- if (ctrlKey) {
297
- updateHoverPoint(time => goTime(time, -1, 'years'));
298
- } else {
299
- updateHoverPoint(time => goTime(time, -1, 'days'));
300
- }
301
- return;
302
- case KeyCode.RIGHT:
303
- if (ctrlKey) {
304
- updateHoverPoint(time => goTime(time, 1, 'years'));
305
- } else {
306
- updateHoverPoint(time => goTime(time, 1, 'days'));
307
- }
308
- return;
309
- case KeyCode.HOME:
310
- updateHoverPoint(time => goStartMonth(time));
311
- return;
312
- case KeyCode.END:
313
- updateHoverPoint(time => goEndMonth(time));
314
- return;
315
- case KeyCode.PAGE_DOWN:
316
- updateHoverPoint(time => goTime(time, 1, 'month'));
317
- return;
318
- case KeyCode.PAGE_UP:
319
- updateHoverPoint(time => goTime(time, -1, 'month'));
320
- return;
321
- case KeyCode.ENTER: {
322
- let lastValue;
323
- if (hoverValue.length === 0) {
324
- lastValue = updateHoverPoint(time => time);
325
- } else if (hoverValue.length === 1) {
326
- lastValue = hoverValue[0];
327
- } else {
328
- lastValue = hoverValue[0].isSame(firstSelectedValue, 'day')
329
- ? hoverValue[1]
330
- : hoverValue[0];
331
- }
332
- if (lastValue && (!disabledDate || !disabledDate(lastValue))) {
333
- this.onSelect(lastValue);
334
- }
335
- event.preventDefault();
336
- return;
337
- }
338
- default:
339
- this.__emit('keydown', event);
340
- }
341
- },
342
-
343
- onDayHover(value) {
344
- let hoverValue = [];
345
- const { sSelectedValue, firstSelectedValue, type } = this;
346
- if (type === 'start' && sSelectedValue[1]) {
347
- hoverValue =
348
- this.compare(value, sSelectedValue[1]) < 0 ? [value, sSelectedValue[1]] : [value];
349
- } else if (type === 'end' && sSelectedValue[0]) {
350
- hoverValue = this.compare(value, sSelectedValue[0]) > 0 ? [sSelectedValue[0], value] : [];
351
- } else {
352
- if (!firstSelectedValue) {
353
- if (this.sHoverValue.length) {
354
- this.setState({ sHoverValue: [] });
355
- }
356
- return hoverValue;
357
- }
358
- hoverValue =
359
- this.compare(value, firstSelectedValue) < 0
360
- ? [value, firstSelectedValue]
361
- : [firstSelectedValue, value];
362
- }
363
- this.fireHoverValueChange(hoverValue);
364
- return hoverValue;
365
- },
366
-
367
- onToday() {
368
- const startValue = getTodayTime(this.sValue[0]);
369
- const endValue = startValue.clone().add(1, 'months');
370
- this.setState({ sValue: [startValue, endValue] });
371
- },
372
-
373
- onOpenTimePicker() {
374
- this.setState({
375
- sShowTimePicker: true,
376
- });
377
- },
378
- onCloseTimePicker() {
379
- this.setState({
380
- sShowTimePicker: false,
381
- });
382
- },
383
-
384
- onOk() {
385
- const { sSelectedValue } = this;
386
- if (this.isAllowedDateAndTime(sSelectedValue)) {
387
- this.__emit('ok', sSelectedValue);
388
- }
389
- },
390
-
391
- onStartInputChange(...oargs) {
392
- const args = ['left'].concat(oargs);
393
- return onInputSelect.apply(this, args);
394
- },
395
-
396
- onEndInputChange(...oargs) {
397
- const args = ['right'].concat(oargs);
398
- return onInputSelect.apply(this, args);
399
- },
400
-
401
- onStartInputSelect(value) {
402
- const args = ['left', value, { source: 'dateInputSelect' }];
403
- return onInputSelect.apply(this, args);
404
- },
405
-
406
- onEndInputSelect(value) {
407
- const args = ['right', value, { source: 'dateInputSelect' }];
408
- return onInputSelect.apply(this, args);
409
- },
410
-
411
- onStartValueChange(leftValue) {
412
- const value = [...this.sValue];
413
- const { sMode: mode } = this;
414
- value[0] = leftValue;
415
-
416
- // 年份模式:只在十年范围变化时才联动(即用户导航时,而不是选择时)
417
- // 使用 getStartValue() 获取面板实际显示的值,而不是 sValue
418
- if (mode[0] === 'year' && mode[1] === 'year') {
419
- const displayedLeftValue = this.getStartValue();
420
- const oldLeftDecade = displayedLeftValue
421
- ? Math.floor(displayedLeftValue.year() / 10)
422
- : null;
423
- const newLeftDecade = Math.floor(leftValue.year() / 10);
424
- // 只有十年范围变化时才执行联动
425
- if (oldLeftDecade !== newLeftDecade) {
426
- const rightDecade = value[1] ? Math.floor(value[1].year() / 10) : newLeftDecade;
427
- if (rightDecade <= newLeftDecade) {
428
- value[1] = leftValue.clone().year((newLeftDecade + 1) * 10);
429
- }
430
- }
431
- }
432
-
433
- // 月份模式:只在年份变化时才联动(即用户导航时)
434
- if (mode[0] === 'month' && mode[1] === 'month') {
435
- const displayedLeftValue = this.getStartValue();
436
- const oldLeftYear = displayedLeftValue ? displayedLeftValue.year() : null;
437
- const newLeftYear = leftValue.year();
438
- // 只有年份变化时才执行联动
439
- if (oldLeftYear !== newLeftYear) {
440
- const rightYear = value[1] ? value[1].year() : newLeftYear;
441
- if (rightYear <= newLeftYear) {
442
- value[1] = leftValue.clone().year(newLeftYear + 1);
443
- }
444
- }
445
- }
446
-
447
- return this.fireValueChange(value);
448
- },
449
-
450
- onEndValueChange(rightValue) {
451
- const value = [...this.sValue];
452
- const { sMode: mode } = this;
453
- value[1] = rightValue;
454
-
455
- // 年份模式:只在十年范围变化时才联动(即用户导航时,而不是选择时)
456
- // 使用 getEndValue() 获取面板实际显示的值,而不是 sValue
457
- if (mode[0] === 'year' && mode[1] === 'year') {
458
- const displayedRightValue = this.getEndValue();
459
- const oldRightDecade = displayedRightValue
460
- ? Math.floor(displayedRightValue.year() / 10)
461
- : null;
462
- const newRightDecade = Math.floor(rightValue.year() / 10);
463
- // 只有十年范围变化时才执行联动
464
- if (oldRightDecade !== newRightDecade) {
465
- const leftDecade = value[0] ? Math.floor(value[0].year() / 10) : newRightDecade;
466
- if (leftDecade >= newRightDecade) {
467
- value[0] = rightValue.clone().year((newRightDecade - 1) * 10);
468
- }
469
- }
470
- }
471
-
472
- // 月份模式:只在年份变化时才联动(即用户导航时)
473
- if (mode[0] === 'month' && mode[1] === 'month') {
474
- const displayedRightValue = this.getEndValue();
475
- const oldRightYear = displayedRightValue ? displayedRightValue.year() : null;
476
- const newRightYear = rightValue.year();
477
- // 只有年份变化时才执行联动
478
- if (oldRightYear !== newRightYear) {
479
- const leftYear = value[0] ? value[0].year() : newRightYear;
480
- if (leftYear >= newRightYear) {
481
- value[0] = rightValue.clone().year(newRightYear - 1);
482
- }
483
- }
484
- }
485
-
486
- return this.fireValueChange(value);
487
- },
488
-
489
- onStartPanelChange(value, mode) {
490
- const { sMode, sValue } = this;
491
- const newMode = [mode, sMode[1]];
492
- const newValue = [value || sValue[0], sValue[1]];
493
- // 传递来源信息,让 RangePicker 知道是哪个面板触发的选择
494
- this.__emit('panelChange', newValue, newMode, { source: 'start' });
495
- const newState = {
496
- sPanelTriggerSource: 'start',
497
- };
498
- if (!hasProp(this, 'mode')) {
499
- newState.sMode = newMode;
500
- }
501
- this.setState(newState);
502
- },
503
-
504
- onEndPanelChange(value, mode) {
505
- const { sMode, sValue } = this;
506
- const newMode = [sMode[0], mode];
507
- const newValue = [sValue[0], value || sValue[1]];
508
- // 传递来源信息,让 RangePicker 知道是哪个面板触发的选择
509
- this.__emit('panelChange', newValue, newMode, { source: 'end' });
510
- const newState = {
511
- sPanelTriggerSource: 'end',
512
- };
513
- if (!hasProp(this, 'mode')) {
514
- newState.sMode = newMode;
515
- }
516
- this.setState(newState);
517
- },
518
-
519
- getStartValue() {
520
- const {
521
- sSelectedValue: selectedValue,
522
- sShowTimePicker: showTimePicker,
523
- sValue: value,
524
- sMode: mode,
525
- sPanelTriggerSource: panelTriggerSource,
526
- } = this.$data;
527
- let startValue = value[0];
528
- // keep selectedTime when select date
529
- if (selectedValue[0] && this.$props.timePicker) {
530
- startValue = startValue.clone();
531
- syncTime(selectedValue[0], startValue);
532
- }
533
- if (showTimePicker && selectedValue[0]) {
534
- startValue = selectedValue[0];
535
- }
536
-
537
- // Adjust month if date not align
538
- if (
539
- panelTriggerSource === 'end' &&
540
- mode[0] === 'date' &&
541
- mode[1] === 'date' &&
542
- startValue.isSame(value[1], 'month')
543
- ) {
544
- startValue = startValue.clone().subtract(1, 'month');
545
- }
546
-
547
- return startValue;
548
- },
549
-
550
- getEndValue() {
551
- const {
552
- sSelectedValue: selectedValue,
553
- sShowTimePicker: showTimePicker,
554
- sValue: value,
555
- sMode: mode,
556
- sPanelTriggerSource: panelTriggerSource,
557
- } = this.$data;
558
- let endValue = value[1] ? value[1].clone() : value[0].clone().add(1, 'month');
559
- // keep selectedTime when select date
560
- if (selectedValue[1] && this.$props.timePicker) {
561
- syncTime(selectedValue[1], endValue);
562
- }
563
- if (showTimePicker) {
564
- endValue = selectedValue[1] ? selectedValue[1] : this.getStartValue();
565
- }
566
-
567
- // Adjust month if date not align
568
- if (
569
- !showTimePicker &&
570
- panelTriggerSource !== 'end' &&
571
- mode[0] === 'date' &&
572
- mode[1] === 'date' &&
573
- endValue.isSame(value[0], 'month')
574
- ) {
575
- endValue = endValue.clone().add(1, 'month');
576
- }
577
-
578
- // 年份模式:确保左右面板显示不同的十年范围
579
- // 左面板显示 2020-2029,右面板显示 2030-2039
580
- if (mode[0] === 'year' && mode[1] === 'year') {
581
- const startValue = this.getStartValue();
582
- const startDecade = Math.floor(startValue.year() / 10);
583
- const endDecade = Math.floor(endValue.year() / 10);
584
- // 如果在同一十年,右面板前进一个十年
585
- if (startDecade >= endDecade) {
586
- endValue = endValue.clone().year((startDecade + 1) * 10);
587
- }
588
- }
589
-
590
- // 月份模式:确保左右面板显示不同的年份
591
- // 左面板显示 2024年,右面板显示 2025年
592
- if (mode[0] === 'month' && mode[1] === 'month') {
593
- const startValue = this.getStartValue();
594
- const startYear = startValue.year();
595
- const endYear = endValue.year();
596
- // 如果在同一年,右面板前进一年
597
- if (startYear >= endYear) {
598
- endValue = endValue.clone().year(startYear + 1);
599
- }
600
- }
601
-
602
- return endValue;
603
- },
604
- // get disabled hours for second picker
605
- getEndDisableTime() {
606
- const { sSelectedValue, sValue, disabledTime } = this;
607
- const userSettingDisabledTime = disabledTime(sSelectedValue, 'end') || {};
608
- const startValue = (sSelectedValue && sSelectedValue[0]) || sValue[0].clone();
609
- // if startTime and endTime is same day..
610
- // the second time picker will not able to pick time before first time picker
611
- if (!sSelectedValue[1] || startValue.isSame(sSelectedValue[1], 'day')) {
612
- const hours = startValue.hour();
613
- const minutes = startValue.minute();
614
- const second = startValue.second();
615
- let { disabledHours, disabledMinutes, disabledSeconds } = userSettingDisabledTime;
616
- const oldDisabledMinutes = disabledMinutes ? disabledMinutes() : [];
617
- const olddisabledSeconds = disabledSeconds ? disabledSeconds() : [];
618
- disabledHours = generateOptions(hours, disabledHours);
619
- disabledMinutes = generateOptions(minutes, disabledMinutes);
620
- disabledSeconds = generateOptions(second, disabledSeconds);
621
- return {
622
- disabledHours() {
623
- return disabledHours;
624
- },
625
- disabledMinutes(hour) {
626
- if (hour === hours) {
627
- return disabledMinutes;
628
- }
629
- return oldDisabledMinutes;
630
- },
631
- disabledSeconds(hour, minute) {
632
- if (hour === hours && minute === minutes) {
633
- return disabledSeconds;
634
- }
635
- return olddisabledSeconds;
636
- },
637
- };
638
- }
639
- return userSettingDisabledTime;
640
- },
641
-
642
- isAllowedDateAndTime(selectedValue) {
643
- return (
644
- isAllowedDate(selectedValue[0], this.disabledDate, this.disabledStartTime) &&
645
- isAllowedDate(selectedValue[1], this.disabledDate, this.disabledEndTime)
646
- );
647
- },
648
-
649
- isMonthYearPanelShow(mode) {
650
- return ['month', 'year', 'decade'].indexOf(mode) > -1;
651
- },
652
-
653
- hasSelectedValue() {
654
- const { sSelectedValue } = this;
655
- return !!sSelectedValue[1] && !!sSelectedValue[0];
656
- },
657
-
658
- compare(v1, v2) {
659
- if (this.timePicker) {
660
- return v1.diff(v2);
661
- }
662
- return v1.diff(v2, 'days');
663
- },
664
-
665
- fireSelectValueChange(selectedValue, direct, cause) {
666
- const { timePicker, prevSelectedValue } = this;
667
- if (timePicker) {
668
- const timePickerProps = getOptionProps(timePicker);
669
- if (timePickerProps.defaultValue) {
670
- const timePickerDefaultValue = timePickerProps.defaultValue;
671
- if (!prevSelectedValue[0] && selectedValue[0]) {
672
- syncTime(timePickerDefaultValue[0], selectedValue[0]);
673
- }
674
- if (!prevSelectedValue[1] && selectedValue[1]) {
675
- syncTime(timePickerDefaultValue[1], selectedValue[1]);
676
- }
677
- }
678
- }
679
- // 尚未选择过时间,直接输入的话
680
- if (!this.sSelectedValue[0] || !this.sSelectedValue[1]) {
681
- const startValue = selectedValue[0] || moment();
682
- const endValue = selectedValue[1] || startValue.clone().add(1, 'months');
683
- this.setState({
684
- sSelectedValue: selectedValue,
685
- sValue:
686
- selectedValue && selectedValue.length === 2
687
- ? getValueFromSelectedValue([startValue, endValue])
688
- : this.sValue,
689
- });
690
- }
691
-
692
- if (selectedValue[0] && !selectedValue[1]) {
693
- this.setState({ firstSelectedValue: selectedValue[0] });
694
- this.fireHoverValueChange(selectedValue.concat());
695
- }
696
- this.__emit('change', selectedValue);
697
- if (direct || (selectedValue[0] && selectedValue[1])) {
698
- this.setState({
699
- prevSelectedValue: selectedValue,
700
- firstSelectedValue: null,
701
- });
702
- this.fireHoverValueChange([]);
703
- this.__emit('select', selectedValue, cause);
704
- }
705
- if (!hasProp(this, 'selectedValue')) {
706
- this.setState({
707
- sSelectedValue: selectedValue,
708
- });
709
- }
710
- },
711
-
712
- fireValueChange(value) {
713
- if (!hasProp(this, 'value')) {
714
- this.setState({
715
- sValue: value,
716
- });
717
- }
718
- this.__emit('valueChange', value);
719
- },
720
-
721
- fireHoverValueChange(hoverValue) {
722
- if (!hasProp(this, 'hoverValue')) {
723
- this.setState({ sHoverValue: hoverValue });
724
- }
725
- this.__emit('hoverChange', hoverValue);
726
- },
727
-
728
- clear() {
729
- this.fireSelectValueChange([], true);
730
- this.__emit('clear');
731
- },
732
-
733
- disabledStartTime(time) {
734
- return this.disabledTime(time, 'start');
735
- },
736
-
737
- disabledEndTime(time) {
738
- return this.disabledTime(time, 'end');
739
- },
740
-
741
- disabledStartMonth(month) {
742
- const { sValue } = this;
743
- return month.isAfter(sValue[1], 'month');
744
- },
745
-
746
- disabledEndMonth(month) {
747
- const { sValue } = this;
748
- return month.isBefore(sValue[0], 'month');
749
- },
750
-
751
- // 年份模式的禁用逻辑:基于已选择的值,而不是面板显示值
752
- disabledStartYear(year) {
753
- const { sSelectedValue } = this;
754
- // 如果没有选择结束年份,不禁用任何年份
755
- if (!sSelectedValue[1]) {
756
- return false;
757
- }
758
- // 禁用大于结束年份的年份
759
- return year.isAfter(sSelectedValue[1], 'year');
760
- },
761
-
762
- disabledEndYear(year) {
763
- const { sSelectedValue } = this;
764
- // 如果没有选择开始年份,不禁用任何年份
765
- if (!sSelectedValue[0]) {
766
- return false;
767
- }
768
- // 禁用小于开始年份的年份
769
- return year.isBefore(sSelectedValue[0], 'year');
770
- },
771
-
772
- // 月份模式的禁用逻辑:基于已选择的值,而不是面板显示值
773
- disabledStartMonthForPicker(month) {
774
- const { sSelectedValue } = this;
775
- // 如果没有选择结束月份,不禁用任何月份
776
- if (!sSelectedValue[1]) {
777
- return false;
778
- }
779
- // 禁用大于结束月份的月份
780
- return month.isAfter(sSelectedValue[1], 'month');
781
- },
782
-
783
- disabledEndMonthForPicker(month) {
784
- const { sSelectedValue } = this;
785
- // 如果没有选择开始月份,不禁用任何月份
786
- if (!sSelectedValue[0]) {
787
- return false;
788
- }
789
- // 禁用小于开始月份的月份
790
- return month.isBefore(sSelectedValue[0], 'month');
791
- },
792
- },
793
-
794
- render() {
795
- const props = getOptionProps(this);
796
- const {
797
- prefixCls,
798
- dateInputPlaceholder,
799
- timePicker,
800
- showOk,
801
- locale,
802
- showClear,
803
- showToday,
804
- type,
805
- separator,
806
- } = props;
807
- const clearIcon = getComponentFromProp(this, 'clearIcon');
808
- const { sHoverValue, sSelectedValue, sMode: mode, sShowTimePicker, sValue } = this;
809
- const className = {
810
- [prefixCls]: 1,
811
- [`${prefixCls}-hidden`]: !props.visible,
812
- [`${prefixCls}-range`]: 1,
813
- [`${prefixCls}-show-time-picker`]: sShowTimePicker,
814
- [`${prefixCls}-week-number`]: props.showWeekNumber,
815
- };
816
- const baseProps = {
817
- props,
818
- on: getListeners(this),
819
- };
820
- const newProps = {
821
- props: {
822
- selectedValue: sSelectedValue,
823
- },
824
- on: {
825
- select: this.onSelect,
826
- dayHover:
827
- (type === 'start' && sSelectedValue[1]) ||
828
- (type === 'end' && sSelectedValue[0]) ||
829
- !!sHoverValue.length
830
- ? this.onDayHover
831
- : noop,
832
- },
833
- };
834
-
835
- let placeholder1;
836
- let placeholder2;
837
-
838
- if (dateInputPlaceholder) {
839
- if (Array.isArray(dateInputPlaceholder)) {
840
- [placeholder1, placeholder2] = dateInputPlaceholder;
841
- } else {
842
- placeholder1 = placeholder2 = dateInputPlaceholder;
843
- }
844
- }
845
- const showOkButton = showOk === true || (showOk !== false && !!timePicker);
846
- const cls = {
847
- [`${prefixCls}-footer`]: true,
848
- [`${prefixCls}-range-bottom`]: true,
849
- [`${prefixCls}-footer-show-ok`]: showOkButton,
850
- };
851
-
852
- const startValue = this.getStartValue();
853
- const endValue = this.getEndValue();
854
- const todayTime = getTodayTime(startValue);
855
- const thisMonth = todayTime.month();
856
- const thisYear = todayTime.year();
857
- const isTodayInView =
858
- (startValue.year() === thisYear && startValue.month() === thisMonth) ||
859
- (endValue.year() === thisYear && endValue.month() === thisMonth);
860
- const nextMonthOfStart = startValue.clone().add(1, 'months');
861
- const isClosestMonths =
862
- nextMonthOfStart.year() === endValue.year() && nextMonthOfStart.month() === endValue.month();
863
-
864
- // 年份模式:检查是否在相邻十年
865
- const isClosestDecades =
866
- mode[0] === 'year' &&
867
- mode[1] === 'year' &&
868
- Math.floor(startValue.year() / 10) + 1 === Math.floor(endValue.year() / 10);
869
-
870
- // 月份模式:检查是否在相邻年份
871
- const isClosestYears =
872
- mode[0] === 'month' && mode[1] === 'month' && startValue.year() + 1 === endValue.year();
873
-
874
- // 判断是否相邻(根据不同模式使用不同判断)
875
- let isClosest;
876
- if (mode[0] === 'year' && mode[1] === 'year') {
877
- isClosest = isClosestDecades;
878
- } else if (mode[0] === 'month' && mode[1] === 'month') {
879
- isClosest = isClosestYears;
880
- } else {
881
- isClosest = isClosestMonths;
882
- }
883
-
884
- // 根据模式选择正确的禁用函数
885
- let leftDisabledMonth;
886
- let rightDisabledMonth;
887
- if (mode[0] === 'year') {
888
- leftDisabledMonth = this.disabledStartYear;
889
- } else if (mode[0] === 'month') {
890
- leftDisabledMonth = this.disabledStartMonthForPicker;
891
- } else {
892
- leftDisabledMonth = this.disabledStartMonth;
893
- }
894
- if (mode[1] === 'year') {
895
- rightDisabledMonth = this.disabledEndYear;
896
- } else if (mode[1] === 'month') {
897
- rightDisabledMonth = this.disabledEndMonthForPicker;
898
- } else {
899
- rightDisabledMonth = this.disabledEndMonth;
900
- }
901
-
902
- const leftPartProps = mergeProps(baseProps, newProps, {
903
- props: {
904
- hoverValue: sHoverValue,
905
- direction: 'left',
906
- disabledTime: this.disabledStartTime,
907
- disabledMonth: leftDisabledMonth,
908
- format: this.getFormat(),
909
- value: startValue,
910
- mode: mode[0],
911
- placeholder: placeholder1,
912
- showDateInput: this.showDateInput,
913
- timePicker,
914
- showTimePicker: sShowTimePicker || mode[0] === 'time',
915
- enablePrev: true,
916
- enableNext: !isClosest || this.isMonthYearPanelShow(mode[1]),
917
- clearIcon,
918
- },
919
- on: {
920
- inputChange: this.onStartInputChange,
921
- inputSelect: this.onStartInputSelect,
922
- valueChange: this.onStartValueChange,
923
- panelChange: this.onStartPanelChange,
924
- },
925
- });
926
- const rightPartProps = mergeProps(baseProps, newProps, {
927
- props: {
928
- hoverValue: sHoverValue,
929
- direction: 'right',
930
- format: this.getFormat(),
931
- timePickerDisabledTime: this.getEndDisableTime(),
932
- placeholder: placeholder2,
933
- value: endValue,
934
- mode: mode[1],
935
- showDateInput: this.showDateInput,
936
- timePicker,
937
- showTimePicker: sShowTimePicker || mode[1] === 'time',
938
- disabledTime: this.disabledEndTime,
939
- disabledMonth: rightDisabledMonth,
940
- enablePrev: !isClosest || this.isMonthYearPanelShow(mode[0]),
941
- enableNext: true,
942
- clearIcon,
943
- },
944
- on: {
945
- inputChange: this.onEndInputChange,
946
- inputSelect: this.onEndInputSelect,
947
- valueChange: this.onEndValueChange,
948
- panelChange: this.onEndPanelChange,
949
- },
950
- });
951
- let TodayButtonNode = null;
952
- if (showToday) {
953
- const todayButtonProps = mergeProps(baseProps, {
954
- props: {
955
- disabled: isTodayInView,
956
- value: sValue[0],
957
- text: locale.backToToday,
958
- },
959
- on: {
960
- today: this.onToday,
961
- },
962
- });
963
- TodayButtonNode = <TodayButton key="todayButton" {...todayButtonProps} />;
964
- }
965
-
966
- let TimePickerButtonNode = null;
967
- if (props.timePicker) {
968
- const timePickerButtonProps = mergeProps(baseProps, {
969
- props: {
970
- showTimePicker: sShowTimePicker || (mode[0] === 'time' && mode[1] === 'time'),
971
- timePickerDisabled: !this.hasSelectedValue() || sHoverValue.length,
972
- },
973
- on: {
974
- openTimePicker: this.onOpenTimePicker,
975
- closeTimePicker: this.onCloseTimePicker,
976
- },
977
- });
978
- TimePickerButtonNode = <TimePickerButton key="timePickerButton" {...timePickerButtonProps} />;
979
- }
980
-
981
- let OkButtonNode = null;
982
- if (showOkButton) {
983
- const okButtonProps = mergeProps(baseProps, {
984
- props: {
985
- okDisabled:
986
- !this.isAllowedDateAndTime(sSelectedValue) ||
987
- !this.hasSelectedValue() ||
988
- sHoverValue.length,
989
- },
990
- on: {
991
- ok: this.onOk,
992
- },
993
- });
994
- OkButtonNode = <OkButton key="okButtonNode" {...okButtonProps} />;
995
- }
996
- const extraFooter = this.renderFooter(mode);
997
- return (
998
- <div ref="rootInstance" class={className} tabIndex="0" onKeydown={this.onKeyDown}>
999
- {props.renderSidebar()}
1000
- <div class={`${prefixCls}-panel`}>
1001
- {showClear && sSelectedValue[0] && sSelectedValue[1] ? (
1002
- <a role="button" title={locale.clear} onClick={this.clear}>
1003
- {clearIcon || <span class={`${prefixCls}-clear-btn`} />}
1004
- </a>
1005
- ) : null}
1006
- <div
1007
- class={`${prefixCls}-date-panel`}
1008
- onMouseleave={type !== 'both' ? this.onDatePanelLeave : noop}
1009
- onMouseenter={type !== 'both' ? this.onDatePanelEnter : noop}
1010
- >
1011
- <CalendarPart {...leftPartProps} />
1012
- <span class={`${prefixCls}-range-middle`}>{separator}</span>
1013
- <CalendarPart {...rightPartProps} />
1014
- </div>
1015
- <div class={cls}>
1016
- {showToday || props.timePicker || showOkButton || extraFooter ? (
1017
- <div class={`${prefixCls}-footer-btn`}>
1018
- {extraFooter}
1019
- {TodayButtonNode}
1020
- {TimePickerButtonNode}
1021
- {OkButtonNode}
1022
- </div>
1023
- ) : null}
1024
- </div>
1025
- </div>
1026
- </div>
1027
- );
1028
- },
1029
- };
1030
-
1031
- export default RangeCalendar;
1
+ import PropTypes from '../../_util/vue-types';
2
+ import BaseMixin from '../../_util/BaseMixin';
3
+ import {
4
+ getOptionProps,
5
+ hasProp,
6
+ mergeProps,
7
+ getComponentFromProp,
8
+ getListeners,
9
+ } from '../../_util/props-util';
10
+ import moment from 'moment';
11
+ import KeyCode from '../../_util/KeyCode';
12
+ import CalendarPart from './range-calendar/CalendarPart';
13
+ import TodayButton from './calendar/TodayButton';
14
+ import OkButton from './calendar/OkButton';
15
+ import TimePickerButton from './calendar/TimePickerButton';
16
+ import CommonMixin from './mixin/CommonMixin';
17
+ import zhCn from './locale/zh_CN';
18
+ import { syncTime, getTodayTime, isAllowedDate } from './util/';
19
+ import { goTime, goStartMonth, goEndMonth, includesTime } from './util/toTime';
20
+
21
+ function noop() {}
22
+
23
+ function isEmptyArray(arr) {
24
+ return Array.isArray(arr) && (arr.length === 0 || arr.every(i => !i));
25
+ }
26
+
27
+ function isArraysEqual(a, b) {
28
+ if (a === b) return true;
29
+ if (a === null || typeof a === 'undefined' || b === null || typeof b === 'undefined') {
30
+ return false;
31
+ }
32
+ if (a.length !== b.length) return false;
33
+
34
+ for (let i = 0; i < a.length; ++i) {
35
+ if (a[i] !== b[i]) return false;
36
+ }
37
+ return true;
38
+ }
39
+
40
+ function getValueFromSelectedValue(selectedValue) {
41
+ let [start, end] = selectedValue;
42
+ if (end && (start === undefined || start === null)) {
43
+ start = end.clone().subtract(1, 'month');
44
+ }
45
+
46
+ if (start && (end === undefined || end === null)) {
47
+ end = start.clone().add(1, 'month');
48
+ }
49
+ return [start, end];
50
+ }
51
+
52
+ function normalizeAnchor(props, init) {
53
+ const selectedValue = props.selectedValue || (init && props.defaultSelectedValue);
54
+ const value = props.value || (init && props.defaultValue);
55
+ const normalizedValue = value
56
+ ? getValueFromSelectedValue(value)
57
+ : getValueFromSelectedValue(selectedValue);
58
+ return !isEmptyArray(normalizedValue)
59
+ ? normalizedValue
60
+ : init && [moment(), moment().add(1, 'months')];
61
+ }
62
+
63
+ function generateOptions(length, extraOptionGen) {
64
+ const arr = extraOptionGen ? extraOptionGen().concat() : [];
65
+ for (let value = 0; value < length; value++) {
66
+ if (arr.indexOf(value) === -1) {
67
+ arr.push(value);
68
+ }
69
+ }
70
+ return arr;
71
+ }
72
+
73
+ function onInputSelect(direction, value, cause) {
74
+ if (!value) {
75
+ return;
76
+ }
77
+ const originalValue = this.sSelectedValue;
78
+ const selectedValue = originalValue.concat();
79
+ const index = direction === 'left' ? 0 : 1;
80
+ selectedValue[index] = value;
81
+ if (selectedValue[0] && this.compare(selectedValue[0], selectedValue[1]) > 0) {
82
+ selectedValue[1 - index] = this.sShowTimePicker ? selectedValue[index] : undefined;
83
+ }
84
+ this.__emit('inputSelect', selectedValue);
85
+ this.fireSelectValueChange(selectedValue, null, cause || { source: 'dateInput' });
86
+ }
87
+
88
+ const RangeCalendar = {
89
+ props: {
90
+ locale: PropTypes.object.def(zhCn),
91
+ visible: PropTypes.bool.def(true),
92
+ prefixCls: PropTypes.string.def('rc-calendar'),
93
+ dateInputPlaceholder: PropTypes.any,
94
+ separator: PropTypes.string.def('~'),
95
+ defaultValue: PropTypes.any,
96
+ value: PropTypes.any,
97
+ hoverValue: PropTypes.any,
98
+ mode: PropTypes.arrayOf(PropTypes.oneOf(['time', 'date', 'month', 'year', 'decade'])),
99
+ showDateInput: PropTypes.bool.def(true),
100
+ timePicker: PropTypes.any,
101
+ showOk: PropTypes.bool,
102
+ showToday: PropTypes.bool.def(true),
103
+ defaultSelectedValue: PropTypes.array.def([]),
104
+ selectedValue: PropTypes.array,
105
+ showClear: PropTypes.bool,
106
+ showWeekNumber: PropTypes.bool,
107
+ // locale: PropTypes.object,
108
+ // onChange: PropTypes.func,
109
+ // onSelect: PropTypes.func,
110
+ // onValueChange: PropTypes.func,
111
+ // onHoverChange: PropTypes.func,
112
+ // onPanelChange: PropTypes.func,
113
+ format: PropTypes.oneOfType([
114
+ PropTypes.string,
115
+ PropTypes.arrayOf(PropTypes.string),
116
+ PropTypes.func,
117
+ ]),
118
+ // onClear: PropTypes.func,
119
+ type: PropTypes.any.def('both'),
120
+ disabledDate: PropTypes.func,
121
+ disabledTime: PropTypes.func.def(noop),
122
+ renderFooter: PropTypes.func.def(() => null),
123
+ renderSidebar: PropTypes.func.def(() => null),
124
+ dateRender: PropTypes.func,
125
+ clearIcon: PropTypes.any,
126
+ inputReadOnly: PropTypes.bool,
127
+ },
128
+
129
+ mixins: [BaseMixin, CommonMixin],
130
+
131
+ data() {
132
+ const props = this.$props;
133
+ const selectedValue = props.selectedValue || props.defaultSelectedValue;
134
+ const value = normalizeAnchor(props, 1);
135
+ return {
136
+ sSelectedValue: selectedValue,
137
+ prevSelectedValue: selectedValue,
138
+ firstSelectedValue: null,
139
+ sHoverValue: props.hoverValue || [],
140
+ sValue: value,
141
+ sShowTimePicker: false,
142
+ sMode: props.mode || ['date', 'date'],
143
+ sPanelTriggerSource: '', // Trigger by which picker panel: 'start' & 'end'
144
+ };
145
+ },
146
+ watch: {
147
+ value() {
148
+ const newState = {};
149
+ newState.sValue = normalizeAnchor(this.$props, 0);
150
+ this.setState(newState);
151
+ },
152
+ hoverValue(val) {
153
+ if (!isArraysEqual(this.sHoverValue, val)) {
154
+ this.setState({ sHoverValue: val });
155
+ }
156
+ },
157
+ selectedValue(val) {
158
+ const newState = {};
159
+ newState.sSelectedValue = val;
160
+ newState.prevSelectedValue = val;
161
+ this.setState(newState);
162
+ },
163
+ mode(val) {
164
+ if (!isArraysEqual(this.sMode, val)) {
165
+ this.setState({ sMode: val });
166
+ }
167
+ },
168
+ },
169
+
170
+ methods: {
171
+ onDatePanelEnter() {
172
+ if (this.hasSelectedValue()) {
173
+ this.fireHoverValueChange(this.sSelectedValue.concat());
174
+ }
175
+ },
176
+
177
+ onDatePanelLeave() {
178
+ if (this.hasSelectedValue()) {
179
+ this.fireHoverValueChange([]);
180
+ }
181
+ },
182
+
183
+ onSelect(value) {
184
+ const { type, sSelectedValue, prevSelectedValue, firstSelectedValue } = this;
185
+ let nextSelectedValue;
186
+ if (type === 'both') {
187
+ if (!firstSelectedValue) {
188
+ syncTime(prevSelectedValue[0], value);
189
+ nextSelectedValue = [value];
190
+ } else if (this.compare(firstSelectedValue, value) < 0) {
191
+ syncTime(prevSelectedValue[1], value);
192
+ nextSelectedValue = [firstSelectedValue, value];
193
+ } else {
194
+ syncTime(prevSelectedValue[0], value);
195
+ syncTime(prevSelectedValue[1], firstSelectedValue);
196
+ nextSelectedValue = [value, firstSelectedValue];
197
+ }
198
+ } else if (type === 'start') {
199
+ syncTime(prevSelectedValue[0], value);
200
+ const endValue = sSelectedValue[1];
201
+ nextSelectedValue =
202
+ endValue && this.compare(endValue, value) > 0 ? [value, endValue] : [value, null];
203
+ } else {
204
+ // type === 'end'
205
+ const startValue = sSelectedValue[0];
206
+ if (startValue && this.compare(startValue, value) <= 0) {
207
+ syncTime(prevSelectedValue[1], value);
208
+ nextSelectedValue = [startValue, value];
209
+ } else {
210
+ // 先选择结束日期,正确放在第二个位置
211
+ syncTime(prevSelectedValue[1], value);
212
+ nextSelectedValue = [null, value];
213
+ }
214
+ }
215
+
216
+ this.fireSelectValueChange(nextSelectedValue);
217
+ },
218
+
219
+ onKeyDown(event) {
220
+ if (event.target.nodeName.toLowerCase() === 'input') {
221
+ return;
222
+ }
223
+
224
+ const { keyCode } = event;
225
+ const ctrlKey = event.ctrlKey || event.metaKey;
226
+
227
+ const {
228
+ sSelectedValue: selectedValue,
229
+ sHoverValue: hoverValue,
230
+ firstSelectedValue,
231
+ sValue: value, // Value is used for `CalendarPart` current page
232
+ } = this.$data;
233
+ const { disabledDate } = this.$props;
234
+
235
+ // Update last time of the picker
236
+ const updateHoverPoint = func => {
237
+ // Change hover to make focus in UI
238
+ let currentHoverTime;
239
+ let nextHoverTime;
240
+ let nextHoverValue;
241
+
242
+ if (!firstSelectedValue) {
243
+ currentHoverTime = hoverValue[0] || selectedValue[0] || value[0] || moment();
244
+ nextHoverTime = func(currentHoverTime);
245
+ nextHoverValue = [nextHoverTime];
246
+ this.fireHoverValueChange(nextHoverValue);
247
+ } else {
248
+ if (hoverValue.length === 1) {
249
+ currentHoverTime = hoverValue[0].clone();
250
+ nextHoverTime = func(currentHoverTime);
251
+ nextHoverValue = this.onDayHover(nextHoverTime);
252
+ } else {
253
+ currentHoverTime = hoverValue[0].isSame(firstSelectedValue, 'day')
254
+ ? hoverValue[1]
255
+ : hoverValue[0];
256
+ nextHoverTime = func(currentHoverTime);
257
+ nextHoverValue = this.onDayHover(nextHoverTime);
258
+ }
259
+ }
260
+
261
+ // Find origin hover time on value index
262
+ if (nextHoverValue.length >= 2) {
263
+ const miss = nextHoverValue.some(ht => !includesTime(value, ht, 'month'));
264
+ if (miss) {
265
+ const newValue = nextHoverValue.slice().sort((t1, t2) => t1.valueOf() - t2.valueOf());
266
+ if (newValue[0].isSame(newValue[1], 'month')) {
267
+ newValue[1] = newValue[0].clone().add(1, 'month');
268
+ }
269
+ this.fireValueChange(newValue);
270
+ }
271
+ } else if (nextHoverValue.length === 1) {
272
+ // If only one value, let's keep the origin panel
273
+ let oriValueIndex = value.findIndex(time => time.isSame(currentHoverTime, 'month'));
274
+ if (oriValueIndex === -1) oriValueIndex = 0;
275
+
276
+ if (value.every(time => !time.isSame(nextHoverTime, 'month'))) {
277
+ const newValue = value.slice();
278
+ newValue[oriValueIndex] = nextHoverTime.clone();
279
+ this.fireValueChange(newValue);
280
+ }
281
+ }
282
+
283
+ event.preventDefault();
284
+
285
+ return nextHoverTime;
286
+ };
287
+
288
+ switch (keyCode) {
289
+ case KeyCode.DOWN:
290
+ updateHoverPoint(time => goTime(time, 1, 'weeks'));
291
+ return;
292
+ case KeyCode.UP:
293
+ updateHoverPoint(time => goTime(time, -1, 'weeks'));
294
+ return;
295
+ case KeyCode.LEFT:
296
+ if (ctrlKey) {
297
+ updateHoverPoint(time => goTime(time, -1, 'years'));
298
+ } else {
299
+ updateHoverPoint(time => goTime(time, -1, 'days'));
300
+ }
301
+ return;
302
+ case KeyCode.RIGHT:
303
+ if (ctrlKey) {
304
+ updateHoverPoint(time => goTime(time, 1, 'years'));
305
+ } else {
306
+ updateHoverPoint(time => goTime(time, 1, 'days'));
307
+ }
308
+ return;
309
+ case KeyCode.HOME:
310
+ updateHoverPoint(time => goStartMonth(time));
311
+ return;
312
+ case KeyCode.END:
313
+ updateHoverPoint(time => goEndMonth(time));
314
+ return;
315
+ case KeyCode.PAGE_DOWN:
316
+ updateHoverPoint(time => goTime(time, 1, 'month'));
317
+ return;
318
+ case KeyCode.PAGE_UP:
319
+ updateHoverPoint(time => goTime(time, -1, 'month'));
320
+ return;
321
+ case KeyCode.ENTER: {
322
+ let lastValue;
323
+ if (hoverValue.length === 0) {
324
+ lastValue = updateHoverPoint(time => time);
325
+ } else if (hoverValue.length === 1) {
326
+ lastValue = hoverValue[0];
327
+ } else {
328
+ lastValue = hoverValue[0].isSame(firstSelectedValue, 'day')
329
+ ? hoverValue[1]
330
+ : hoverValue[0];
331
+ }
332
+ if (lastValue && (!disabledDate || !disabledDate(lastValue))) {
333
+ this.onSelect(lastValue);
334
+ }
335
+ event.preventDefault();
336
+ return;
337
+ }
338
+ default:
339
+ this.__emit('keydown', event);
340
+ }
341
+ },
342
+
343
+ onDayHover(value) {
344
+ let hoverValue = [];
345
+ const { sSelectedValue, firstSelectedValue, type } = this;
346
+ if (type === 'start' && sSelectedValue[1]) {
347
+ hoverValue =
348
+ this.compare(value, sSelectedValue[1]) < 0 ? [value, sSelectedValue[1]] : [value];
349
+ } else if (type === 'end' && sSelectedValue[0]) {
350
+ hoverValue = this.compare(value, sSelectedValue[0]) > 0 ? [sSelectedValue[0], value] : [];
351
+ } else {
352
+ if (!firstSelectedValue) {
353
+ if (this.sHoverValue.length) {
354
+ this.setState({ sHoverValue: [] });
355
+ }
356
+ return hoverValue;
357
+ }
358
+ hoverValue =
359
+ this.compare(value, firstSelectedValue) < 0
360
+ ? [value, firstSelectedValue]
361
+ : [firstSelectedValue, value];
362
+ }
363
+ this.fireHoverValueChange(hoverValue);
364
+ return hoverValue;
365
+ },
366
+
367
+ // 年份 hover 处理(类似于日期 hover 逻辑)
368
+ onYearHover(value) {
369
+ let hoverValue = [];
370
+ const { sSelectedValue, firstSelectedValue, type } = this;
371
+
372
+ // 比较年份
373
+ const compareYear = (v1, v2) => {
374
+ return v1.year() - v2.year();
375
+ };
376
+
377
+ if (type === 'start' && sSelectedValue[1]) {
378
+ // 选择开始年份时:如果 hover 值小于结束值,显示范围预览
379
+ hoverValue =
380
+ compareYear(value, sSelectedValue[1]) < 0 ? [value, sSelectedValue[1]] : [value];
381
+ } else if (type === 'end' && sSelectedValue[0]) {
382
+ // 选择结束年份时:如果 hover 值大于开始值,显示范围预览
383
+ hoverValue = compareYear(value, sSelectedValue[0]) > 0 ? [sSelectedValue[0], value] : [];
384
+ } else {
385
+ // both 模式
386
+ if (!firstSelectedValue) {
387
+ if (this.sHoverValue.length) {
388
+ this.setState({ sHoverValue: [] });
389
+ }
390
+ return hoverValue;
391
+ }
392
+ hoverValue =
393
+ compareYear(value, firstSelectedValue) < 0
394
+ ? [value, firstSelectedValue]
395
+ : [firstSelectedValue, value];
396
+ }
397
+ this.fireHoverValueChange(hoverValue);
398
+ return hoverValue;
399
+ },
400
+
401
+ // 月份 hover 处理(类似于日期 hover 逻辑)
402
+ onMonthHover(value) {
403
+ let hoverValue = [];
404
+ const { sSelectedValue, firstSelectedValue, type } = this;
405
+
406
+ // 比较年月
407
+ const compareMonth = (v1, v2) => {
408
+ const yearDiff = v1.year() - v2.year();
409
+ if (yearDiff !== 0) return yearDiff;
410
+ return v1.month() - v2.month();
411
+ };
412
+
413
+ if (type === 'start' && sSelectedValue[1]) {
414
+ // 选择开始月份时:如果 hover 值小于结束值,显示范围预览
415
+ hoverValue =
416
+ compareMonth(value, sSelectedValue[1]) < 0 ? [value, sSelectedValue[1]] : [value];
417
+ } else if (type === 'end' && sSelectedValue[0]) {
418
+ // 选择结束月份时:如果 hover 值大于开始值,显示范围预览
419
+ hoverValue = compareMonth(value, sSelectedValue[0]) > 0 ? [sSelectedValue[0], value] : [];
420
+ } else {
421
+ // both 模式
422
+ if (!firstSelectedValue) {
423
+ if (this.sHoverValue.length) {
424
+ this.setState({ sHoverValue: [] });
425
+ }
426
+ return hoverValue;
427
+ }
428
+ hoverValue =
429
+ compareMonth(value, firstSelectedValue) < 0
430
+ ? [value, firstSelectedValue]
431
+ : [firstSelectedValue, value];
432
+ }
433
+ this.fireHoverValueChange(hoverValue);
434
+ return hoverValue;
435
+ },
436
+
437
+ onToday() {
438
+ const startValue = getTodayTime(this.sValue[0]);
439
+ const endValue = startValue.clone().add(1, 'months');
440
+ this.setState({ sValue: [startValue, endValue] });
441
+ },
442
+
443
+ onOpenTimePicker() {
444
+ this.setState({
445
+ sShowTimePicker: true,
446
+ });
447
+ },
448
+ onCloseTimePicker() {
449
+ this.setState({
450
+ sShowTimePicker: false,
451
+ });
452
+ },
453
+
454
+ onOk() {
455
+ const { sSelectedValue } = this;
456
+ if (this.isAllowedDateAndTime(sSelectedValue)) {
457
+ this.__emit('ok', sSelectedValue);
458
+ }
459
+ },
460
+
461
+ onStartInputChange(...oargs) {
462
+ const args = ['left'].concat(oargs);
463
+ return onInputSelect.apply(this, args);
464
+ },
465
+
466
+ onEndInputChange(...oargs) {
467
+ const args = ['right'].concat(oargs);
468
+ return onInputSelect.apply(this, args);
469
+ },
470
+
471
+ onStartInputSelect(value) {
472
+ const args = ['left', value, { source: 'dateInputSelect' }];
473
+ return onInputSelect.apply(this, args);
474
+ },
475
+
476
+ onEndInputSelect(value) {
477
+ const args = ['right', value, { source: 'dateInputSelect' }];
478
+ return onInputSelect.apply(this, args);
479
+ },
480
+
481
+ onStartValueChange(leftValue) {
482
+ const value = [...this.sValue];
483
+ const { sMode: mode } = this;
484
+ value[0] = leftValue;
485
+
486
+ // 年份模式:只在十年范围变化时才联动(即用户导航时,而不是选择时)
487
+ // 使用 getStartValue() 获取面板实际显示的值,而不是 sValue
488
+ if (mode[0] === 'year' && mode[1] === 'year') {
489
+ const displayedLeftValue = this.getStartValue();
490
+ const oldLeftDecade = displayedLeftValue
491
+ ? Math.floor(displayedLeftValue.year() / 10)
492
+ : null;
493
+ const newLeftDecade = Math.floor(leftValue.year() / 10);
494
+ // 只有十年范围变化时才执行联动
495
+ if (oldLeftDecade !== newLeftDecade) {
496
+ const rightDecade = value[1] ? Math.floor(value[1].year() / 10) : newLeftDecade;
497
+ if (rightDecade <= newLeftDecade) {
498
+ value[1] = leftValue.clone().year((newLeftDecade + 1) * 10);
499
+ }
500
+ }
501
+ }
502
+
503
+ // 月份模式:只在年份变化时才联动(即用户导航时)
504
+ if (mode[0] === 'month' && mode[1] === 'month') {
505
+ const displayedLeftValue = this.getStartValue();
506
+ const oldLeftYear = displayedLeftValue ? displayedLeftValue.year() : null;
507
+ const newLeftYear = leftValue.year();
508
+ // 只有年份变化时才执行联动
509
+ if (oldLeftYear !== newLeftYear) {
510
+ const rightYear = value[1] ? value[1].year() : newLeftYear;
511
+ if (rightYear <= newLeftYear) {
512
+ value[1] = leftValue.clone().year(newLeftYear + 1);
513
+ }
514
+ }
515
+ }
516
+
517
+ return this.fireValueChange(value);
518
+ },
519
+
520
+ onEndValueChange(rightValue) {
521
+ const value = [...this.sValue];
522
+ const { sMode: mode } = this;
523
+ value[1] = rightValue;
524
+
525
+ // 年份模式:只在十年范围变化时才联动(即用户导航时,而不是选择时)
526
+ // 使用 getEndValue() 获取面板实际显示的值,而不是 sValue
527
+ if (mode[0] === 'year' && mode[1] === 'year') {
528
+ const displayedRightValue = this.getEndValue();
529
+ const oldRightDecade = displayedRightValue
530
+ ? Math.floor(displayedRightValue.year() / 10)
531
+ : null;
532
+ const newRightDecade = Math.floor(rightValue.year() / 10);
533
+ // 只有十年范围变化时才执行联动
534
+ if (oldRightDecade !== newRightDecade) {
535
+ const leftDecade = value[0] ? Math.floor(value[0].year() / 10) : newRightDecade;
536
+ if (leftDecade >= newRightDecade) {
537
+ value[0] = rightValue.clone().year((newRightDecade - 1) * 10);
538
+ }
539
+ }
540
+ }
541
+
542
+ // 月份模式:只在年份变化时才联动(即用户导航时)
543
+ if (mode[0] === 'month' && mode[1] === 'month') {
544
+ const displayedRightValue = this.getEndValue();
545
+ const oldRightYear = displayedRightValue ? displayedRightValue.year() : null;
546
+ const newRightYear = rightValue.year();
547
+ // 只有年份变化时才执行联动
548
+ if (oldRightYear !== newRightYear) {
549
+ const leftYear = value[0] ? value[0].year() : newRightYear;
550
+ if (leftYear >= newRightYear) {
551
+ value[0] = rightValue.clone().year(newRightYear - 1);
552
+ }
553
+ }
554
+ }
555
+
556
+ return this.fireValueChange(value);
557
+ },
558
+
559
+ onStartPanelChange(value, mode) {
560
+ const { sMode, sValue } = this;
561
+ const newMode = [mode, sMode[1]];
562
+ const newValue = [value || sValue[0], sValue[1]];
563
+ // 传递来源信息,让 RangePicker 知道是哪个面板触发的选择
564
+ this.__emit('panelChange', newValue, newMode, { source: 'start' });
565
+ const newState = {
566
+ sPanelTriggerSource: 'start',
567
+ };
568
+ if (!hasProp(this, 'mode')) {
569
+ newState.sMode = newMode;
570
+ }
571
+ this.setState(newState);
572
+ },
573
+
574
+ onEndPanelChange(value, mode) {
575
+ const { sMode, sValue } = this;
576
+ const newMode = [sMode[0], mode];
577
+ const newValue = [sValue[0], value || sValue[1]];
578
+ // 传递来源信息,让 RangePicker 知道是哪个面板触发的选择
579
+ this.__emit('panelChange', newValue, newMode, { source: 'end' });
580
+ const newState = {
581
+ sPanelTriggerSource: 'end',
582
+ };
583
+ if (!hasProp(this, 'mode')) {
584
+ newState.sMode = newMode;
585
+ }
586
+ this.setState(newState);
587
+ },
588
+
589
+ getStartValue() {
590
+ const {
591
+ sSelectedValue: selectedValue,
592
+ sShowTimePicker: showTimePicker,
593
+ sValue: value,
594
+ sMode: mode,
595
+ sPanelTriggerSource: panelTriggerSource,
596
+ } = this.$data;
597
+ let startValue = value[0];
598
+ // keep selectedTime when select date
599
+ if (selectedValue[0] && this.$props.timePicker) {
600
+ startValue = startValue.clone();
601
+ syncTime(selectedValue[0], startValue);
602
+ }
603
+ if (showTimePicker && selectedValue[0]) {
604
+ startValue = selectedValue[0];
605
+ }
606
+
607
+ // Adjust month if date not align
608
+ if (
609
+ panelTriggerSource === 'end' &&
610
+ mode[0] === 'date' &&
611
+ mode[1] === 'date' &&
612
+ startValue.isSame(value[1], 'month')
613
+ ) {
614
+ startValue = startValue.clone().subtract(1, 'month');
615
+ }
616
+
617
+ return startValue;
618
+ },
619
+
620
+ getEndValue() {
621
+ const {
622
+ sSelectedValue: selectedValue,
623
+ sShowTimePicker: showTimePicker,
624
+ sValue: value,
625
+ sMode: mode,
626
+ sPanelTriggerSource: panelTriggerSource,
627
+ } = this.$data;
628
+ let endValue = value[1] ? value[1].clone() : value[0].clone().add(1, 'month');
629
+ // keep selectedTime when select date
630
+ if (selectedValue[1] && this.$props.timePicker) {
631
+ syncTime(selectedValue[1], endValue);
632
+ }
633
+ if (showTimePicker) {
634
+ endValue = selectedValue[1] ? selectedValue[1] : this.getStartValue();
635
+ }
636
+
637
+ // Adjust month if date not align
638
+ if (
639
+ !showTimePicker &&
640
+ panelTriggerSource !== 'end' &&
641
+ mode[0] === 'date' &&
642
+ mode[1] === 'date' &&
643
+ endValue.isSame(value[0], 'month')
644
+ ) {
645
+ endValue = endValue.clone().add(1, 'month');
646
+ }
647
+
648
+ // 年份模式:确保左右面板显示不同的十年范围
649
+ // 左面板显示 2020-2029,右面板显示 2030-2039
650
+ if (mode[0] === 'year' && mode[1] === 'year') {
651
+ const startValue = this.getStartValue();
652
+ const startDecade = Math.floor(startValue.year() / 10);
653
+ const endDecade = Math.floor(endValue.year() / 10);
654
+ // 如果在同一十年,右面板前进一个十年
655
+ if (startDecade >= endDecade) {
656
+ endValue = endValue.clone().year((startDecade + 1) * 10);
657
+ }
658
+ }
659
+
660
+ // 月份模式:确保左右面板显示不同的年份
661
+ // 左面板显示 2024年,右面板显示 2025年
662
+ if (mode[0] === 'month' && mode[1] === 'month') {
663
+ const startValue = this.getStartValue();
664
+ const startYear = startValue.year();
665
+ const endYear = endValue.year();
666
+ // 如果在同一年,右面板前进一年
667
+ if (startYear >= endYear) {
668
+ endValue = endValue.clone().year(startYear + 1);
669
+ }
670
+ }
671
+
672
+ return endValue;
673
+ },
674
+ // get disabled hours for second picker
675
+ getEndDisableTime() {
676
+ const { sSelectedValue, sValue, disabledTime } = this;
677
+ const userSettingDisabledTime = disabledTime(sSelectedValue, 'end') || {};
678
+ const startValue = (sSelectedValue && sSelectedValue[0]) || sValue[0].clone();
679
+ // if startTime and endTime is same day..
680
+ // the second time picker will not able to pick time before first time picker
681
+ if (!sSelectedValue[1] || startValue.isSame(sSelectedValue[1], 'day')) {
682
+ const hours = startValue.hour();
683
+ const minutes = startValue.minute();
684
+ const second = startValue.second();
685
+ let { disabledHours, disabledMinutes, disabledSeconds } = userSettingDisabledTime;
686
+ const oldDisabledMinutes = disabledMinutes ? disabledMinutes() : [];
687
+ const olddisabledSeconds = disabledSeconds ? disabledSeconds() : [];
688
+ disabledHours = generateOptions(hours, disabledHours);
689
+ disabledMinutes = generateOptions(minutes, disabledMinutes);
690
+ disabledSeconds = generateOptions(second, disabledSeconds);
691
+ return {
692
+ disabledHours() {
693
+ return disabledHours;
694
+ },
695
+ disabledMinutes(hour) {
696
+ if (hour === hours) {
697
+ return disabledMinutes;
698
+ }
699
+ return oldDisabledMinutes;
700
+ },
701
+ disabledSeconds(hour, minute) {
702
+ if (hour === hours && minute === minutes) {
703
+ return disabledSeconds;
704
+ }
705
+ return olddisabledSeconds;
706
+ },
707
+ };
708
+ }
709
+ return userSettingDisabledTime;
710
+ },
711
+
712
+ isAllowedDateAndTime(selectedValue) {
713
+ return (
714
+ isAllowedDate(selectedValue[0], this.disabledDate, this.disabledStartTime) &&
715
+ isAllowedDate(selectedValue[1], this.disabledDate, this.disabledEndTime)
716
+ );
717
+ },
718
+
719
+ isMonthYearPanelShow(mode) {
720
+ return ['month', 'year', 'decade'].indexOf(mode) > -1;
721
+ },
722
+
723
+ hasSelectedValue() {
724
+ const { sSelectedValue } = this;
725
+ return !!sSelectedValue[1] && !!sSelectedValue[0];
726
+ },
727
+
728
+ compare(v1, v2) {
729
+ if (this.timePicker) {
730
+ return v1.diff(v2);
731
+ }
732
+ return v1.diff(v2, 'days');
733
+ },
734
+
735
+ fireSelectValueChange(selectedValue, direct, cause) {
736
+ const { timePicker, prevSelectedValue } = this;
737
+ if (timePicker) {
738
+ const timePickerProps = getOptionProps(timePicker);
739
+ if (timePickerProps.defaultValue) {
740
+ const timePickerDefaultValue = timePickerProps.defaultValue;
741
+ if (!prevSelectedValue[0] && selectedValue[0]) {
742
+ syncTime(timePickerDefaultValue[0], selectedValue[0]);
743
+ }
744
+ if (!prevSelectedValue[1] && selectedValue[1]) {
745
+ syncTime(timePickerDefaultValue[1], selectedValue[1]);
746
+ }
747
+ }
748
+ }
749
+ // 尚未选择过时间,直接输入的话
750
+ if (!this.sSelectedValue[0] || !this.sSelectedValue[1]) {
751
+ const startValue = selectedValue[0] || moment();
752
+ const endValue = selectedValue[1] || startValue.clone().add(1, 'months');
753
+ this.setState({
754
+ sSelectedValue: selectedValue,
755
+ sValue:
756
+ selectedValue && selectedValue.length === 2
757
+ ? getValueFromSelectedValue([startValue, endValue])
758
+ : this.sValue,
759
+ });
760
+ }
761
+
762
+ if (selectedValue[0] && !selectedValue[1]) {
763
+ this.setState({ firstSelectedValue: selectedValue[0] });
764
+ this.fireHoverValueChange(selectedValue.concat());
765
+ }
766
+ this.__emit('change', selectedValue);
767
+ if (direct || (selectedValue[0] && selectedValue[1])) {
768
+ this.setState({
769
+ prevSelectedValue: selectedValue,
770
+ firstSelectedValue: null,
771
+ });
772
+ this.fireHoverValueChange([]);
773
+ this.__emit('select', selectedValue, cause);
774
+ }
775
+ if (!hasProp(this, 'selectedValue')) {
776
+ this.setState({
777
+ sSelectedValue: selectedValue,
778
+ });
779
+ }
780
+ },
781
+
782
+ fireValueChange(value) {
783
+ if (!hasProp(this, 'value')) {
784
+ this.setState({
785
+ sValue: value,
786
+ });
787
+ }
788
+ this.__emit('valueChange', value);
789
+ },
790
+
791
+ fireHoverValueChange(hoverValue) {
792
+ if (!hasProp(this, 'hoverValue')) {
793
+ this.setState({ sHoverValue: hoverValue });
794
+ }
795
+ this.__emit('hoverChange', hoverValue);
796
+ },
797
+
798
+ clear() {
799
+ this.fireSelectValueChange([], true);
800
+ this.__emit('clear');
801
+ },
802
+
803
+ disabledStartTime(time) {
804
+ return this.disabledTime(time, 'start');
805
+ },
806
+
807
+ disabledEndTime(time) {
808
+ return this.disabledTime(time, 'end');
809
+ },
810
+
811
+ disabledStartMonth(month) {
812
+ const { sValue } = this;
813
+ return month.isAfter(sValue[1], 'month');
814
+ },
815
+
816
+ disabledEndMonth(month) {
817
+ const { sValue } = this;
818
+ return month.isBefore(sValue[0], 'month');
819
+ },
820
+
821
+ // 年份模式的禁用逻辑:基于已选择的值,而不是面板显示值
822
+ disabledStartYear(year) {
823
+ const { sSelectedValue } = this;
824
+ // 如果没有选择结束年份,不禁用任何年份
825
+ if (!sSelectedValue[1]) {
826
+ return false;
827
+ }
828
+ // 禁用大于结束年份的年份
829
+ return year.isAfter(sSelectedValue[1], 'year');
830
+ },
831
+
832
+ disabledEndYear(year) {
833
+ const { sSelectedValue } = this;
834
+ // 如果没有选择开始年份,不禁用任何年份
835
+ if (!sSelectedValue[0]) {
836
+ return false;
837
+ }
838
+ // 禁用小于开始年份的年份
839
+ return year.isBefore(sSelectedValue[0], 'year');
840
+ },
841
+
842
+ // 月份模式的禁用逻辑:基于已选择的值,而不是面板显示值
843
+ disabledStartMonthForPicker(month) {
844
+ const { sSelectedValue } = this;
845
+ // 如果没有选择结束月份,不禁用任何月份
846
+ if (!sSelectedValue[1]) {
847
+ return false;
848
+ }
849
+ // 禁用大于结束月份的月份
850
+ return month.isAfter(sSelectedValue[1], 'month');
851
+ },
852
+
853
+ disabledEndMonthForPicker(month) {
854
+ const { sSelectedValue } = this;
855
+ // 如果没有选择开始月份,不禁用任何月份
856
+ if (!sSelectedValue[0]) {
857
+ return false;
858
+ }
859
+ // 禁用小于开始月份的月份
860
+ return month.isBefore(sSelectedValue[0], 'month');
861
+ },
862
+ },
863
+
864
+ render() {
865
+ const props = getOptionProps(this);
866
+ const {
867
+ prefixCls,
868
+ dateInputPlaceholder,
869
+ timePicker,
870
+ showOk,
871
+ locale,
872
+ showClear,
873
+ showToday,
874
+ type,
875
+ separator,
876
+ } = props;
877
+ const clearIcon = getComponentFromProp(this, 'clearIcon');
878
+ const { sHoverValue, sSelectedValue, sMode: mode, sShowTimePicker, sValue } = this;
879
+ const className = {
880
+ [prefixCls]: 1,
881
+ [`${prefixCls}-hidden`]: !props.visible,
882
+ [`${prefixCls}-range`]: 1,
883
+ [`${prefixCls}-show-time-picker`]: sShowTimePicker,
884
+ [`${prefixCls}-week-number`]: props.showWeekNumber,
885
+ };
886
+ const baseProps = {
887
+ props,
888
+ on: getListeners(this),
889
+ };
890
+ const newProps = {
891
+ props: {
892
+ selectedValue: sSelectedValue,
893
+ },
894
+ on: {
895
+ select: this.onSelect,
896
+ dayHover:
897
+ (type === 'start' && sSelectedValue[1]) ||
898
+ (type === 'end' && sSelectedValue[0]) ||
899
+ !!sHoverValue.length
900
+ ? this.onDayHover
901
+ : noop,
902
+ },
903
+ };
904
+
905
+ let placeholder1;
906
+ let placeholder2;
907
+
908
+ if (dateInputPlaceholder) {
909
+ if (Array.isArray(dateInputPlaceholder)) {
910
+ [placeholder1, placeholder2] = dateInputPlaceholder;
911
+ } else {
912
+ placeholder1 = placeholder2 = dateInputPlaceholder;
913
+ }
914
+ }
915
+ const showOkButton = showOk === true || (showOk !== false && !!timePicker);
916
+ const cls = {
917
+ [`${prefixCls}-footer`]: true,
918
+ [`${prefixCls}-range-bottom`]: true,
919
+ [`${prefixCls}-footer-show-ok`]: showOkButton,
920
+ };
921
+
922
+ const startValue = this.getStartValue();
923
+ const endValue = this.getEndValue();
924
+ const todayTime = getTodayTime(startValue);
925
+ const thisMonth = todayTime.month();
926
+ const thisYear = todayTime.year();
927
+ const isTodayInView =
928
+ (startValue.year() === thisYear && startValue.month() === thisMonth) ||
929
+ (endValue.year() === thisYear && endValue.month() === thisMonth);
930
+ const nextMonthOfStart = startValue.clone().add(1, 'months');
931
+ const isClosestMonths =
932
+ nextMonthOfStart.year() === endValue.year() && nextMonthOfStart.month() === endValue.month();
933
+
934
+ // 年份模式:检查是否在相邻十年
935
+ const isClosestDecades =
936
+ mode[0] === 'year' &&
937
+ mode[1] === 'year' &&
938
+ Math.floor(startValue.year() / 10) + 1 === Math.floor(endValue.year() / 10);
939
+
940
+ // 月份模式:检查是否在相邻年份
941
+ const isClosestYears =
942
+ mode[0] === 'month' && mode[1] === 'month' && startValue.year() + 1 === endValue.year();
943
+
944
+ // 判断是否相邻(根据不同模式使用不同判断)
945
+ let isClosest;
946
+ if (mode[0] === 'year' && mode[1] === 'year') {
947
+ isClosest = isClosestDecades;
948
+ } else if (mode[0] === 'month' && mode[1] === 'month') {
949
+ isClosest = isClosestYears;
950
+ } else {
951
+ isClosest = isClosestMonths;
952
+ }
953
+
954
+ // 根据模式选择正确的禁用函数
955
+ let leftDisabledMonth;
956
+ let rightDisabledMonth;
957
+ if (mode[0] === 'year') {
958
+ leftDisabledMonth = this.disabledStartYear;
959
+ } else if (mode[0] === 'month') {
960
+ leftDisabledMonth = this.disabledStartMonthForPicker;
961
+ } else {
962
+ leftDisabledMonth = this.disabledStartMonth;
963
+ }
964
+ if (mode[1] === 'year') {
965
+ rightDisabledMonth = this.disabledEndYear;
966
+ } else if (mode[1] === 'month') {
967
+ rightDisabledMonth = this.disabledEndMonthForPicker;
968
+ } else {
969
+ rightDisabledMonth = this.disabledEndMonth;
970
+ }
971
+
972
+ const leftPartProps = mergeProps(baseProps, newProps, {
973
+ props: {
974
+ hoverValue: sHoverValue,
975
+ direction: 'left',
976
+ disabledTime: this.disabledStartTime,
977
+ disabledMonth: leftDisabledMonth,
978
+ format: this.getFormat(),
979
+ value: startValue,
980
+ mode: mode[0],
981
+ placeholder: placeholder1,
982
+ showDateInput: this.showDateInput,
983
+ timePicker,
984
+ showTimePicker: sShowTimePicker || mode[0] === 'time',
985
+ enablePrev: true,
986
+ enableNext: !isClosest || this.isMonthYearPanelShow(mode[1]),
987
+ clearIcon,
988
+ },
989
+ on: {
990
+ inputChange: this.onStartInputChange,
991
+ inputSelect: this.onStartInputSelect,
992
+ valueChange: this.onStartValueChange,
993
+ panelChange: this.onStartPanelChange,
994
+ yearHover: this.onYearHover,
995
+ monthHover: this.onMonthHover,
996
+ },
997
+ });
998
+ const rightPartProps = mergeProps(baseProps, newProps, {
999
+ props: {
1000
+ hoverValue: sHoverValue,
1001
+ direction: 'right',
1002
+ format: this.getFormat(),
1003
+ timePickerDisabledTime: this.getEndDisableTime(),
1004
+ placeholder: placeholder2,
1005
+ value: endValue,
1006
+ mode: mode[1],
1007
+ showDateInput: this.showDateInput,
1008
+ timePicker,
1009
+ showTimePicker: sShowTimePicker || mode[1] === 'time',
1010
+ disabledTime: this.disabledEndTime,
1011
+ disabledMonth: rightDisabledMonth,
1012
+ enablePrev: !isClosest || this.isMonthYearPanelShow(mode[0]),
1013
+ enableNext: true,
1014
+ clearIcon,
1015
+ },
1016
+ on: {
1017
+ inputChange: this.onEndInputChange,
1018
+ inputSelect: this.onEndInputSelect,
1019
+ valueChange: this.onEndValueChange,
1020
+ panelChange: this.onEndPanelChange,
1021
+ yearHover: this.onYearHover,
1022
+ monthHover: this.onMonthHover,
1023
+ },
1024
+ });
1025
+ let TodayButtonNode = null;
1026
+ if (showToday) {
1027
+ const todayButtonProps = mergeProps(baseProps, {
1028
+ props: {
1029
+ disabled: isTodayInView,
1030
+ value: sValue[0],
1031
+ text: locale.backToToday,
1032
+ },
1033
+ on: {
1034
+ today: this.onToday,
1035
+ },
1036
+ });
1037
+ TodayButtonNode = <TodayButton key="todayButton" {...todayButtonProps} />;
1038
+ }
1039
+
1040
+ let TimePickerButtonNode = null;
1041
+ if (props.timePicker) {
1042
+ const timePickerButtonProps = mergeProps(baseProps, {
1043
+ props: {
1044
+ showTimePicker: sShowTimePicker || (mode[0] === 'time' && mode[1] === 'time'),
1045
+ timePickerDisabled: !this.hasSelectedValue() || sHoverValue.length,
1046
+ },
1047
+ on: {
1048
+ openTimePicker: this.onOpenTimePicker,
1049
+ closeTimePicker: this.onCloseTimePicker,
1050
+ },
1051
+ });
1052
+ TimePickerButtonNode = <TimePickerButton key="timePickerButton" {...timePickerButtonProps} />;
1053
+ }
1054
+
1055
+ let OkButtonNode = null;
1056
+ if (showOkButton) {
1057
+ const okButtonProps = mergeProps(baseProps, {
1058
+ props: {
1059
+ okDisabled:
1060
+ !this.isAllowedDateAndTime(sSelectedValue) ||
1061
+ !this.hasSelectedValue() ||
1062
+ sHoverValue.length,
1063
+ },
1064
+ on: {
1065
+ ok: this.onOk,
1066
+ },
1067
+ });
1068
+ OkButtonNode = <OkButton key="okButtonNode" {...okButtonProps} />;
1069
+ }
1070
+ const extraFooter = this.renderFooter(mode);
1071
+ return (
1072
+ <div ref="rootInstance" class={className} tabIndex="0" onKeydown={this.onKeyDown}>
1073
+ {props.renderSidebar()}
1074
+ <div class={`${prefixCls}-panel`}>
1075
+ {showClear && sSelectedValue[0] && sSelectedValue[1] ? (
1076
+ <a role="button" title={locale.clear} onClick={this.clear}>
1077
+ {clearIcon || <span class={`${prefixCls}-clear-btn`} />}
1078
+ </a>
1079
+ ) : null}
1080
+ <div
1081
+ class={`${prefixCls}-date-panel`}
1082
+ onMouseleave={type !== 'both' ? this.onDatePanelLeave : noop}
1083
+ onMouseenter={type !== 'both' ? this.onDatePanelEnter : noop}
1084
+ >
1085
+ <CalendarPart {...leftPartProps} />
1086
+ <span class={`${prefixCls}-range-middle`}>{separator}</span>
1087
+ <CalendarPart {...rightPartProps} />
1088
+ </div>
1089
+ <div class={cls}>
1090
+ {showToday || props.timePicker || showOkButton || extraFooter ? (
1091
+ <div class={`${prefixCls}-footer-btn`}>
1092
+ {extraFooter}
1093
+ {TodayButtonNode}
1094
+ {TimePickerButtonNode}
1095
+ {OkButtonNode}
1096
+ </div>
1097
+ ) : null}
1098
+ </div>
1099
+ </div>
1100
+ </div>
1101
+ );
1102
+ },
1103
+ };
1104
+
1105
+ export default RangeCalendar;