uview-plus 3.8.39 → 3.8.49

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,507 @@
1
+ <template>
2
+ <view class="u-calendar-strip">
3
+ <view v-if="!fullCalendar || !innerShowFull">
4
+ <view class="u-calendar-strip__header u-border-bottom">
5
+ <text
6
+ class="u-calendar-strip__header__switch"
7
+ :class="{ 'u-calendar-strip__header__switch--disabled': switchPrevDisabled }"
8
+ @tap="prevMonth"
9
+ >‹</text>
10
+ <text class="u-calendar-strip__header__title">{{ monthLabel }}</text>
11
+ <text
12
+ class="u-calendar-strip__header__switch"
13
+ :class="{ 'u-calendar-strip__header__switch--disabled': switchNextDisabled }"
14
+ @tap="nextMonth"
15
+ >›</text>
16
+ <text
17
+ v-if="fullCalendar"
18
+ class="u-calendar-strip__header__toggle"
19
+ @click="toggleFull('button')"
20
+ >▾</text>
21
+ </view>
22
+ <view
23
+ class="u-calendar-strip__scroll-wrap"
24
+ @touchstart="onTouchStart"
25
+ @touchend="onTouchEnd"
26
+ >
27
+ <scroll-view
28
+ class="u-calendar-strip__scroll"
29
+ scroll-x
30
+ enable-flex
31
+ :show-scrollbar="false"
32
+ :scroll-into-view="scrollIntoView"
33
+ >
34
+ <view class="u-calendar-strip__scroll__inner">
35
+ <view
36
+ v-for="(item, index) in monthDays"
37
+ :key="index"
38
+ class="u-calendar-strip__day"
39
+ :id="getDateId(item.date)"
40
+ :class="[
41
+ item.disabled && 'u-calendar-strip__day--disabled',
42
+ item.selected && 'u-calendar-strip__day--selected',
43
+ item.today && showToday && !item.selected && 'u-calendar-strip__day--today',
44
+ ]"
45
+ :style="[dayStyle(item)]"
46
+ @tap="onDayTap(item)"
47
+ >
48
+ <text class="u-calendar-strip__day__date">{{ item.day }}</text>
49
+ <text class="u-calendar-strip__day__week">{{ getWeekLabel(item.week) }}</text>
50
+ </view>
51
+ </view>
52
+ </scroll-view>
53
+ </view>
54
+ </view>
55
+ <view
56
+ v-if="fullCalendar && innerShowFull"
57
+ class="u-calendar-strip__panel-wrap"
58
+ @touchstart="onTouchStart"
59
+ @touchend="onTouchEnd"
60
+ >
61
+ <view class="u-calendar-strip__panel">
62
+ <u-calendar
63
+ :show="true"
64
+ :pageInline="true"
65
+ :showTitle="false"
66
+ :showConfirm="false"
67
+ :closeOnClickOverlay="false"
68
+ :monthSwitch="true"
69
+ mode="single"
70
+ :defaultDate="innerSelectedDate"
71
+ :minDate="panelMinDate"
72
+ :maxDate="panelMaxDate"
73
+ :monthNum="panelMonthNum"
74
+ :readonly="readonly"
75
+ :showToday="showToday"
76
+ :color="color"
77
+ v-bind="fullCalendarProps"
78
+ @confirm="onPanelConfirm"
79
+ ></u-calendar>
80
+ </view>
81
+ <view class="u-calendar-strip__hint u-calendar-strip__hint--panel">
82
+ <text class="u-calendar-strip__hint__text" @click="toggleFull('hint')">{{ collapseHint }}</text>
83
+ </view>
84
+ </view>
85
+ </view>
86
+ </template>
87
+
88
+ <script>
89
+ import { props } from './props'
90
+ import { mpMixin } from '../../libs/mixin/mpMixin'
91
+ import { mixin } from '../../libs/mixin/mixin'
92
+ import dayjs from '../u-datetime-picker/dayjs.esm.min.js'
93
+ import test from '../../libs/function/test'
94
+
95
+ /**
96
+ * CalendarStrip 单行日历
97
+ * @description 单行横向日期日历,支持切月、下拉展开完整月历
98
+ */
99
+ export default {
100
+ name: 'u-calendar-strip',
101
+ mixins: [mpMixin, mixin, props],
102
+ data() {
103
+ return {
104
+ innerSelectedDate: '',
105
+ currentMonth: '',
106
+ scrollIntoView: '',
107
+ innerShowFull: false,
108
+ touchStartX: 0,
109
+ touchStartY: 0
110
+ }
111
+ },
112
+ computed: {
113
+ innerMaxDate() {
114
+ return test.number(this.maxDate) ? Number(this.maxDate) : this.maxDate
115
+ },
116
+ innerMinDate() {
117
+ return test.number(this.minDate) ? Number(this.minDate) : this.minDate
118
+ },
119
+ rangeChange() {
120
+ return [this.innerMinDate, this.innerMaxDate]
121
+ },
122
+ hasMaxDate() {
123
+ return !!this.innerMaxDate && dayjs(this.innerMaxDate).isValid()
124
+ },
125
+ hasMinDate() {
126
+ return !!this.innerMinDate && dayjs(this.innerMinDate).isValid()
127
+ },
128
+ minDateDay() {
129
+ if (!this.hasMinDate) return ''
130
+ return dayjs(this.innerMinDate).format('YYYY-MM-DD')
131
+ },
132
+ maxDateDay() {
133
+ if (!this.hasMaxDate) return ''
134
+ return dayjs(this.innerMaxDate).format('YYYY-MM-DD')
135
+ },
136
+ todayDate() {
137
+ return dayjs().format('YYYY-MM-DD')
138
+ },
139
+ monthLabel() {
140
+ if (!this.currentMonth) return ''
141
+ const date = dayjs(`${this.currentMonth}-01`)
142
+ if (this.monthFormat) return date.format(this.monthFormat)
143
+ if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
144
+ return date.format('YYYY年MM月')
145
+ }
146
+ return date.format('MM/YYYY')
147
+ },
148
+ monthDays() {
149
+ if (!this.currentMonth) return []
150
+ const start = dayjs(`${this.currentMonth}-01`)
151
+ const days = start.daysInMonth()
152
+ return new Array(days).fill(0).map((item, index) => {
153
+ const date = start.date(index + 1).format('YYYY-MM-DD')
154
+ return {
155
+ day: index + 1,
156
+ date,
157
+ week: start.date(index + 1).day(),
158
+ disabled: this.isDateDisabled(date),
159
+ selected: this.dateSame(date, this.innerSelectedDate),
160
+ today: this.dateSame(date, this.todayDate)
161
+ }
162
+ })
163
+ },
164
+ switchPrevDisabled() {
165
+ if (!this.hasMinDate || !this.currentMonth) return false
166
+ const current = dayjs(`${this.currentMonth}-01`)
167
+ const minMonth = dayjs(this.minDateDay).startOf('month')
168
+ return current.isSame(minMonth, 'month') || current.isBefore(minMonth, 'month')
169
+ },
170
+ switchNextDisabled() {
171
+ if (!this.hasMaxDate || !this.currentMonth) return false
172
+ const current = dayjs(`${this.currentMonth}-01`)
173
+ const maxMonth = dayjs(this.maxDateDay).startOf('month')
174
+ return current.isSame(maxMonth, 'month') || current.isAfter(maxMonth, 'month')
175
+ },
176
+ panelMinDate() {
177
+ if (this.hasMinDate) return this.minDateDay
178
+ const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
179
+ const anchor = this.currentMonth || dayjs().format('YYYY-MM')
180
+ return dayjs(`${anchor}-01`).subtract(monthNum - 1, 'month').startOf('month').format('YYYY-MM-DD')
181
+ },
182
+ panelMaxDate() {
183
+ if (this.hasMaxDate) return this.maxDateDay
184
+ const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
185
+ const anchor = this.currentMonth || dayjs().format('YYYY-MM')
186
+ return dayjs(`${anchor}-01`).add(monthNum - 1, 'month').endOf('month').format('YYYY-MM-DD')
187
+ },
188
+ panelMonthNum() {
189
+ return this.getMonths(this.panelMinDate, this.panelMaxDate)
190
+ },
191
+ pullHintText() {
192
+ return this.innerShowFull ? this.collapseHint : this.expandHint
193
+ }
194
+ },
195
+ watch: {
196
+ modelValue: {
197
+ immediate: true,
198
+ handler(n) {
199
+ this.syncByValue(n, false, 'sync')
200
+ }
201
+ },
202
+ rangeChange() {
203
+ this.syncByValue(this.innerSelectedDate || this.modelValue, true, 'range')
204
+ }
205
+ },
206
+ emits: ['change', 'confirm', 'monthChange', 'toggleFull', 'update:modelValue'],
207
+ methods: {
208
+ dateSame(date1, date2) {
209
+ if (!date1 || !date2) return false
210
+ return dayjs(date1).isSame(dayjs(date2), 'day')
211
+ },
212
+ normalizeDate(value) {
213
+ if (!value) return ''
214
+ const parsed = dayjs(value)
215
+ if (!parsed.isValid()) return ''
216
+ return parsed.format('YYYY-MM-DD')
217
+ },
218
+ clampDate(date) {
219
+ let next = this.normalizeDate(date)
220
+ if (!next) return ''
221
+ if (this.hasMinDate && dayjs(next).isBefore(dayjs(this.minDateDay), 'day')) {
222
+ next = this.minDateDay
223
+ }
224
+ if (this.hasMaxDate && dayjs(next).isAfter(dayjs(this.maxDateDay), 'day')) {
225
+ next = this.maxDateDay
226
+ }
227
+ return next
228
+ },
229
+ isDateDisabled(date) {
230
+ if (!date) return true
231
+ if (this.hasMinDate && dayjs(date).isBefore(dayjs(this.minDateDay), 'day')) {
232
+ return true
233
+ }
234
+ if (this.hasMaxDate && dayjs(date).isAfter(dayjs(this.maxDateDay), 'day')) {
235
+ return true
236
+ }
237
+ return false
238
+ },
239
+ getDateId(date) {
240
+ return `u-calendar-strip-day-${dayjs(date).format('YYYYMMDD')}`
241
+ },
242
+ getWeekLabel(week) {
243
+ const index = week === 0 ? 6 : week - 1
244
+ return this.weekText[index] || ''
245
+ },
246
+ dayStyle(item) {
247
+ const style = {}
248
+ if (item.selected) {
249
+ style.backgroundColor = this.color
250
+ }
251
+ if (!item.selected && item.today && this.showToday) {
252
+ style.borderColor = this.color
253
+ }
254
+ return style
255
+ },
256
+ scrollToDate(date) {
257
+ if (!date) {
258
+ this.scrollIntoView = ''
259
+ return
260
+ }
261
+ const target = this.getDateId(date)
262
+ this.scrollIntoView = ''
263
+ this.$nextTick(() => {
264
+ this.scrollIntoView = target
265
+ })
266
+ },
267
+ syncByValue(value, emit = false, scene = 'sync') {
268
+ let next = this.clampDate(value)
269
+ if (!next) {
270
+ next = this.clampDate(this.todayDate)
271
+ }
272
+ if (!next) return
273
+ this.setSelectedDate(next, scene, emit)
274
+ },
275
+ setSelectedDate(date, scene = 'tap', emit = true) {
276
+ const next = this.clampDate(date)
277
+ if (!next || this.isDateDisabled(next)) return
278
+ const prevDate = this.innerSelectedDate
279
+ const prevMonth = this.currentMonth
280
+ this.innerSelectedDate = next
281
+ this.currentMonth = dayjs(next).format('YYYY-MM')
282
+ this.scrollToDate(next)
283
+ if (!emit) return
284
+ const payload = {
285
+ date: next,
286
+ month: this.currentMonth,
287
+ scene
288
+ }
289
+ if (!this.dateSame(prevDate, next)) {
290
+ this.$emit('update:modelValue', next)
291
+ }
292
+ this.$emit('change', payload)
293
+ this.$emit('confirm', payload)
294
+ if (prevMonth !== this.currentMonth) {
295
+ this.$emit('monthChange', {
296
+ month: this.currentMonth,
297
+ scene
298
+ })
299
+ }
300
+ },
301
+ getMonths(minDate, maxDate) {
302
+ const minYear = dayjs(minDate).year()
303
+ const minMonth = dayjs(minDate).month() + 1
304
+ const maxYear = dayjs(maxDate).year()
305
+ const maxMonth = dayjs(maxDate).month() + 1
306
+ return Math.max(1, (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1)
307
+ },
308
+ findFirstEnabledDate(month) {
309
+ const start = dayjs(`${month}-01`)
310
+ const days = start.daysInMonth()
311
+ for (let i = 1; i <= days; i++) {
312
+ const date = start.date(i).format('YYYY-MM-DD')
313
+ if (!this.isDateDisabled(date)) {
314
+ return date
315
+ }
316
+ }
317
+ return ''
318
+ },
319
+ switchMonth(step = 0) {
320
+ if (step < 0 && this.switchPrevDisabled) return
321
+ if (step > 0 && this.switchNextDisabled) return
322
+ const baseMonth = this.currentMonth || dayjs(this.todayDate).format('YYYY-MM')
323
+ const target = dayjs(`${baseMonth}-01`).add(step, 'month')
324
+ const targetMonth = target.format('YYYY-MM')
325
+ const selectedDay = dayjs(this.innerSelectedDate || this.todayDate).date()
326
+ const dayInTargetMonth = Math.min(selectedDay, target.daysInMonth())
327
+ let next = target.date(dayInTargetMonth).format('YYYY-MM-DD')
328
+ next = this.clampDate(next)
329
+ if (!next || !dayjs(next).isSame(target, 'month') || this.isDateDisabled(next)) {
330
+ next = this.findFirstEnabledDate(targetMonth)
331
+ }
332
+ if (!next) return
333
+ this.setSelectedDate(next, 'switch', true)
334
+ },
335
+ prevMonth() {
336
+ this.switchMonth(-1)
337
+ },
338
+ nextMonth() {
339
+ this.switchMonth(1)
340
+ },
341
+ onDayTap(item) {
342
+ if (this.readonly || item.disabled) return
343
+ this.setSelectedDate(item.date, 'tap', true)
344
+ },
345
+ setFullVisible(show, source = 'button') {
346
+ if (!this.fullCalendar) return
347
+ if (this.innerShowFull === show) return
348
+ this.innerShowFull = show
349
+ this.$emit('toggleFull', {
350
+ show,
351
+ source
352
+ })
353
+ },
354
+ toggleFull(source = 'button') {
355
+ this.setFullVisible(!this.innerShowFull, source)
356
+ },
357
+ onPanelConfirm(e) {
358
+ if (!Array.isArray(e) || !e.length) return
359
+ this.setSelectedDate(e[0], 'full', true)
360
+ if (this.collapseAfterSelect) {
361
+ this.setFullVisible(false, 'auto')
362
+ }
363
+ },
364
+ onTouchStart(event) {
365
+ if (!this.fullCalendar) return
366
+ const point = event?.changedTouches?.[0] || event?.touches?.[0]
367
+ if (!point) return
368
+ this.touchStartX = point.clientX
369
+ this.touchStartY = point.clientY
370
+ },
371
+ onTouchEnd(event) {
372
+ if (!this.fullCalendar) return
373
+ const point = event?.changedTouches?.[0] || event?.touches?.[0]
374
+ if (!point) return
375
+ const deltaX = point.clientX - this.touchStartX
376
+ const deltaY = point.clientY - this.touchStartY
377
+ const threshold = Number(this.pullDownThreshold) || 40
378
+ if (Math.abs(deltaY) < threshold || Math.abs(deltaY) <= Math.abs(deltaX)) {
379
+ return
380
+ }
381
+ if (deltaY > 0 && !this.innerShowFull) {
382
+ this.setFullVisible(true, 'pull-down')
383
+ }
384
+ if (deltaY < 0 && this.innerShowFull) {
385
+ this.setFullVisible(false, 'pull-up')
386
+ }
387
+ }
388
+ }
389
+ }
390
+ </script>
391
+
392
+ <style lang="scss" scoped>
393
+ .u-calendar-strip {
394
+ background-color: var(--up-card-bg-color, #ffffff);
395
+ border-radius: 10px;
396
+ margin: 0 8px;
397
+
398
+ &__header {
399
+ @include flex;
400
+ align-items: center;
401
+ padding: 0 4px;
402
+ height: 40px;
403
+
404
+ &__switch {
405
+ width: 40px;
406
+ text-align: center;
407
+ font-size: 20px;
408
+ line-height: 40px;
409
+ color: var(--up-main-color, $u-main-color);
410
+
411
+ &--disabled {
412
+ opacity: 0.35;
413
+ }
414
+ }
415
+
416
+ &__title {
417
+ flex: 1;
418
+ text-align: center;
419
+ font-size: 14px;
420
+ font-weight: bold;
421
+ color: var(--up-main-color, $u-main-color);
422
+ }
423
+
424
+ &__toggle {
425
+ width: 40px;
426
+ text-align: center;
427
+ font-size: 18px;
428
+ line-height: 40px;
429
+ color: var(--up-content-color, $u-content-color);
430
+ }
431
+ }
432
+
433
+ &__scroll-wrap {
434
+ padding: 10px 0 6px;
435
+ }
436
+
437
+ &__scroll {
438
+ &__inner {
439
+ @include flex;
440
+ padding: 0 8px;
441
+ }
442
+ }
443
+
444
+ &__day {
445
+ width: 54px;
446
+ height: 62px;
447
+ margin-right: 8px;
448
+ border-radius: 8px;
449
+ border: 1px solid transparent;
450
+ @include flex;
451
+ flex-direction: column;
452
+ align-items: center;
453
+ justify-content: center;
454
+ flex-shrink: 0;
455
+
456
+ &__date {
457
+ font-size: 18px;
458
+ line-height: 22px;
459
+ color: var(--up-main-color, $u-main-color);
460
+ }
461
+
462
+ &__week {
463
+ margin-top: 4px;
464
+ font-size: 12px;
465
+ line-height: 16px;
466
+ color: var(--up-content-color, $u-content-color);
467
+ }
468
+
469
+ &--selected {
470
+ .u-calendar-strip__day__date,
471
+ .u-calendar-strip__day__week {
472
+ color: #ffffff;
473
+ }
474
+ }
475
+
476
+ &--today {
477
+ border-style: solid;
478
+ }
479
+
480
+ &--disabled {
481
+ opacity: 0.4;
482
+ }
483
+ }
484
+
485
+ &__hint {
486
+ padding: 4px 0 10px;
487
+ text-align: center;
488
+
489
+ &__text {
490
+ font-size: 12px;
491
+ color: var(--up-tips-color, $u-tips-color);
492
+ }
493
+ }
494
+
495
+ &__panel {
496
+ padding: 0 8px 8px;
497
+ }
498
+
499
+ &__panel-wrap {
500
+ padding-top: 4px;
501
+ }
502
+
503
+ &__hint--panel {
504
+ padding: 0 0 8px;
505
+ }
506
+ }
507
+ </style>
@@ -23,6 +23,8 @@ export default {
23
23
  maxHour: 23,
24
24
  minMinute: 0,
25
25
  maxMinute: 59,
26
+ minSecond: 0,
27
+ maxSecond: 59,
26
28
  filter: null,
27
29
  formatter: null,
28
30
  loading: false,
@@ -72,7 +72,7 @@ export const props = defineMixin({
72
72
  type: String,
73
73
  default: () => defProps.datetimePicker.title
74
74
  },
75
- // 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择
75
+ // 展示格式,支持datetimeyear-monthdatetime、datehour、timesecond、datetimesecond
76
76
  mode: {
77
77
  type: String,
78
78
  default: () => defProps.datetimePicker.mode
@@ -89,26 +89,36 @@ export const props = defineMixin({
89
89
  // 最小默认值为前10年
90
90
  default: () => defProps.datetimePicker.minDate
91
91
  },
92
- // 可选的最小小时,仅mode=time有效
92
+ // 可选的最小小时,仅mode=time/timesecond有效
93
93
  minHour: {
94
94
  type: Number,
95
95
  default: () => defProps.datetimePicker.minHour
96
96
  },
97
- // 可选的最大小时,仅mode=time有效
97
+ // 可选的最大小时,仅mode=time/timesecond有效
98
98
  maxHour: {
99
99
  type: Number,
100
100
  default: () => defProps.datetimePicker.maxHour
101
101
  },
102
- // 可选的最小分钟,仅mode=time有效
102
+ // 可选的最小分钟,仅mode=time/timesecond有效
103
103
  minMinute: {
104
104
  type: Number,
105
105
  default: () => defProps.datetimePicker.minMinute
106
106
  },
107
- // 可选的最大分钟,仅mode=time有效
107
+ // 可选的最大分钟,仅mode=time/timesecond有效
108
108
  maxMinute: {
109
109
  type: Number,
110
110
  default: () => defProps.datetimePicker.maxMinute
111
111
  },
112
+ // 可选的最小秒,仅mode=timesecond有效
113
+ minSecond: {
114
+ type: Number,
115
+ default: () => defProps.datetimePicker.minSecond
116
+ },
117
+ // 可选的最大秒,仅mode=timesecond有效
118
+ maxSecond: {
119
+ type: Number,
120
+ default: () => defProps.datetimePicker.maxSecond
121
+ },
112
122
  // 选项过滤函数
113
123
  filter: {
114
124
  type: [Function, null],