uview-plus 3.8.49 → 3.8.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -1,3 +1,47 @@
1
+ ## 3.8.55(2026-06-12)
2
+ fix: 完善日历今天按钮的区间模式表现及修复小程序多输入框切换时 blur 格式化视图不刷新
3
+
4
+ - 问题出现在小程序真机多个输入框切换场景中,A 输入框在 blur 回调里完成格式化后,点击 B 输入框会导致数据已更新但 A 的显示值未及时同步。本次修改在 u-input 内部记录 blur 时的原始值,并在 modelValue 回流阶段识别 blur 后的外部格式化结果,允许该结果覆盖 innerValue,避免被 changeFromInner 保护逻辑拦截。这样在点击其他输入框触发 blur 时,格式化结果也能正确更新到视图。
5
+ - 避免日期区间模式点击今天时清空已有选中范围,同时提供今天日期的独立高亮能力。新增 todayColor 配置、组件透传与类型声明,并调整 range 模式下今天按钮仅定位不选中。single 模式保持点击今天直接选中,range 模式保留原区间并定位到今天。
6
+
7
+ ## 3.8.54(2026-06-11)
8
+ fix(calendar): 修复今天按钮点击后未选中日期的问题
9
+
10
+ 今天按钮在跳转到今天所在月份后,会复用日历原有日期点击逻辑选中今天,并按 showConfirm 配置保持确认行为一致。
11
+
12
+ ## 3.8.53(2026-06-11)
13
+ feat(calendar): 日历顶部增加时分秒选择能力
14
+
15
+ 新增 enableTime、timePrecision、defaultTime 配置,支持单选与区间首尾模式在顶部点击时间区域后弹出居中 picker 进行时间编辑。
16
+ 确认返回仍为字符串数组:单选与区间首尾模式返回日期时间字符串;区间 all 模式按约定不展示时间选择并保持原日期数组返回;同日区间增加结束时间不能早于开始时间的校验。
17
+
18
+ ## 3.8.52(2026-06-10)
19
+ fix(table2): 修复微信小程序中重复 slot name="cell" 导致的报错 (#839)
20
+
21
+ 在固定列浮动视图(u-table-fixed-shadow)的 MP-WEIXIN 条件编译块中,
22
+ 将 <slot name="cell"> 改为直接内联渲染,消除同组件内同名 slot 的冲突。
23
+ 微信小程序不允许单个组件实例中出现多个同名 slot,而主表体已占用 "cell",
24
+ 固定列视图区仅用于视觉叠加,直接渲染 item.row[col.key] 即可满足需求。
25
+
26
+ ## 3.8.51(2026-06-09)
27
+ fix(picker): 修复异步columns加载时defaultIndex位置不更新的问题 (#841)
28
+
29
+ 问题:当columns从接口动态返回时(初始为空数组,延迟后赋值),
30
+ defaultIndex已提前设置好innerIndex,columns变化时innerIndex值未改变,
31
+ picker-view的:value绑定不触发重新渲染,导致滚动位置停留在第0项。
32
+
33
+ 修复:在setColumns中检测是否有列从空变为有数据,若是则先清空innerIndex,
34
+ 再在$nextTick中恢复目标值,强制picker-view重新滚动到defaultIndex指定位置。
35
+
36
+ ## 3.8.50(2026-06-08)
37
+ fix: 修复 pageInline 日历隐藏挂载时首日偏移异常
38
+
39
+ 问题原因:u-calendar 在隐藏容器中挂载时可能获取到 width=0,导致首日 margin-left 计算为 0。
40
+ 改动内容:
41
+ 1) 非 APP-NVUE 场景首日偏移改为百分比计算,避免依赖运行时宽度。
42
+ 2) 宽度获取增加 windowWidth 兜底,降低隐藏挂载导致的布局异常风险。
43
+ 影响范围:仅调整 u-calendar/month.vue 的首日偏移与宽度兜底逻辑,不改变选择交互行为。
44
+
1
45
  ## 3.8.49(2026-06-05)
2
46
  fix(datetime-picker): 修复hasInput模式下点击遮罩无法关闭弹窗的问题
3
47
 
@@ -34,14 +34,18 @@ export default {
34
34
  readonly: false,
35
35
  showConfirm: true,
36
36
  maxRange: Number.MAX_SAFE_INTEGER, // Infinity
37
- rangePrompt: '',
37
+ rangePrompt: '',
38
38
  showRangePrompt: true,
39
39
  allowSameDay: false,
40
+ rangeResultMode: 'all',
41
+ enableTime: false,
42
+ timePrecision: 'minute',
43
+ defaultTime: '',
40
44
  round: 0,
41
45
  monthNum: 3,
42
46
  monthSwitch: false,
43
47
  showToday: true,
44
- rangeResultMode: 'all',
48
+ todayColor: '',
45
49
  weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
46
50
  forbidDays: [],
47
51
  forbidDaysToast: t("up.calendar.disabled"),
@@ -33,7 +33,7 @@
33
33
  // #endif
34
34
  import { mpMixin } from '../../libs/mixin/mpMixin';
35
35
  import { mixin } from '../../libs/mixin/mixin';
36
- import { addUnit, deepClone, toast, sleep } from '../../libs/function/index';
36
+ import { addUnit, deepClone, toast, sleep, getWindowInfo } from '../../libs/function/index';
37
37
  import { colorGradient } from '../../libs/function/colorGradient';
38
38
  import test from '../../libs/function/test';
39
39
  import defProps from '../../libs/config/props';
@@ -140,6 +140,11 @@
140
140
  todayDate: {
141
141
  type: String,
142
142
  default: ''
143
+ },
144
+ // 今天日期的独立高亮颜色
145
+ todayColor: {
146
+ type: String,
147
+ default: ''
143
148
  }
144
149
  },
145
150
  data() {
@@ -168,8 +173,10 @@
168
173
  return (index1, index2, item) => {
169
174
  const style = {}
170
175
  let week = item.week
176
+ // 隐藏挂载场景下节点宽度可能拿到0,兜底使用窗口宽度避免首日偏移归零
177
+ const wrapperWidth = this.width > 0 ? this.width : (getWindowInfo().windowWidth || 0)
171
178
  // 不进行四舍五入的形式保留2位小数
172
- const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
179
+ const dayWidth = Number(parseFloat(wrapperWidth / 7).toFixed(3).slice(0, -1))
173
180
  // 得出每个日期的宽度
174
181
  // #ifdef APP-NVUE
175
182
  style.width = addUnit(dayWidth, 'px')
@@ -178,7 +185,12 @@
178
185
  if (index2 === 0) {
179
186
  // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
180
187
  week = (week === 0 ? 7 : week) - 1
188
+ // #ifdef APP-NVUE
181
189
  style.marginLeft = addUnit(week * dayWidth, 'px')
190
+ // #endif
191
+ // #ifndef APP-NVUE
192
+ style.marginLeft = `${(week / 7) * 100}%`
193
+ // #endif
182
194
  }
183
195
  if (this.mode === 'range') {
184
196
  // 之所以需要这么写,是因为DCloud公司的iOS客户端导致的bug
@@ -198,8 +210,8 @@
198
210
  if (this.selected.some(item => this.dateSame(item, date))) {
199
211
  style.backgroundColor = this.color
200
212
  }
201
- if (this.todayDate && this.dateSame(date, this.todayDate) && !this.selected.some(item => this.dateSame(item, date))) {
202
- style.border = `1px solid ${this.color}`
213
+ if (this.todayDate && this.dateSame(date, this.todayDate)) {
214
+ style.border = `1px solid ${this.resolvedTodayColor}`
203
215
  style.boxSizing = 'border-box'
204
216
  }
205
217
  if (this.mode === 'single') {
@@ -248,6 +260,9 @@
248
260
  return style
249
261
  }
250
262
  },
263
+ resolvedTodayColor() {
264
+ return this.todayColor || this.color
265
+ },
251
266
  // 某个日期是否被选中
252
267
  textStyle() {
253
268
  return (item) => {
@@ -265,6 +280,9 @@
265
280
  style.color = this.color
266
281
  }
267
282
  }
283
+ if (this.todayDate && this.dateSame(date, this.todayDate) && !this.isSelectedDate(date)) {
284
+ style.color = this.resolvedTodayColor
285
+ }
268
286
  return style
269
287
  }
270
288
  },
@@ -335,16 +353,21 @@
335
353
  dateSame(date1, date2) {
336
354
  return dayjs(date1).isSame(dayjs(date2))
337
355
  },
356
+ isSelectedDate(date) {
357
+ return this.selected.some(item => this.dateSame(item, date))
358
+ },
338
359
  // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
339
360
  getWrapperWidth() {
340
361
  // #ifdef APP-NVUE
341
362
  dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
342
- this.width = res.size.width
363
+ const width = res && res.size ? Number(res.size.width) : 0
364
+ this.width = width > 0 ? width : (getWindowInfo().windowWidth || 0)
343
365
  })
344
366
  // #endif
345
367
  // #ifndef APP-NVUE
346
368
  this.$uGetRect('.u-calendar-month-wrapper').then(size => {
347
- this.width = size.width
369
+ const width = size ? Number(size.width) : 0
370
+ this.width = width > 0 ? width : (getWindowInfo().windowWidth || 0)
348
371
  })
349
372
  // #endif
350
373
  },
@@ -493,6 +516,18 @@
493
516
  setSelected(selected, event = true) {
494
517
  this.selected = selected
495
518
  event && this.$emit('monthSelected', this.selected,'tap')
519
+ },
520
+ selectDate(date) {
521
+ const targetDate = dayjs(date).format("YYYY-MM-DD")
522
+ for (let monthIndex = 0; monthIndex < this.months.length; monthIndex++) {
523
+ const dayIndex = this.months[monthIndex].date.findIndex(item => {
524
+ return this.dateSame(item.date, targetDate)
525
+ })
526
+ if (dayIndex !== -1) {
527
+ this.clickHandler(monthIndex, dayIndex, this.months[monthIndex].date[dayIndex])
528
+ return
529
+ }
530
+ }
496
531
  }
497
532
  }
498
533
  }
@@ -137,6 +137,21 @@ export const props = defineMixin({
137
137
  rangeResultMode: {
138
138
  type: String,
139
139
  default: () => defProps.calendar.rangeResultMode
140
+ },
141
+ // 是否开启时分秒选择
142
+ enableTime: {
143
+ type: Boolean,
144
+ default: () => defProps.calendar.enableTime
145
+ },
146
+ // 时间精度:hour-仅时,minute-时分,second-时分秒
147
+ timePrecision: {
148
+ type: String,
149
+ default: () => defProps.calendar.timePrecision
150
+ },
151
+ // 默认时间,格式支持 HH / HH:mm / HH:mm:ss
152
+ defaultTime: {
153
+ type: String,
154
+ default: () => defProps.calendar.defaultTime
140
155
  },
141
156
  // 圆角值
142
157
  round: {
@@ -158,6 +173,11 @@ export const props = defineMixin({
158
173
  type: Boolean,
159
174
  default: () => defProps.calendar.showToday
160
175
  },
176
+ // 今天日期的独立高亮颜色,默认跟随主题色
177
+ todayColor: {
178
+ type: String,
179
+ default: () => defProps.calendar.todayColor
180
+ },
161
181
  // 星期文案
162
182
  weekText: {
163
183
  type: Array,
@@ -29,6 +29,28 @@
29
29
  @nextYear="nextYear"
30
30
  @today="jumpToToday"
31
31
  ></uHeader>
32
+ <view v-if="showTimePanel" class="u-calendar__time-panel">
33
+ <view v-if="mode === 'single'" class="u-calendar__time-row">
34
+ <text class="u-calendar__time-date">{{ singleDateLabel }}</text>
35
+ <view class="u-calendar__time-trigger" @click="openTimePicker('single')">
36
+ <text class="u-calendar__time-text">{{ singleTime }}</text>
37
+ </view>
38
+ </view>
39
+ <view v-else-if="mode === 'range' && rangeResultMode === 'boundary'">
40
+ <view class="u-calendar__time-row">
41
+ <text class="u-calendar__time-date">{{ rangeStartDateLabel }}</text>
42
+ <view class="u-calendar__time-trigger" @click="openTimePicker('start')">
43
+ <text class="u-calendar__time-text">{{ rangeStartTime }}</text>
44
+ </view>
45
+ </view>
46
+ <view class="u-calendar__time-row">
47
+ <text class="u-calendar__time-date">{{ rangeEndDateLabel }}</text>
48
+ <view class="u-calendar__time-trigger" @click="openTimePicker('end')">
49
+ <text class="u-calendar__time-text">{{ rangeEndTime }}</text>
50
+ </view>
51
+ </view>
52
+ </view>
53
+ </view>
32
54
  <scroll-view
33
55
  v-if="!monthSwitch"
34
56
  :style="{
@@ -61,6 +83,7 @@
61
83
  :forbidDaysToast="forbidDaysToast"
62
84
  :monthFormat="monthFormat"
63
85
  :todayDate="todayDate"
86
+ :todayColor="todayColor"
64
87
  ref="month"
65
88
  @monthSelected="monthSelected"
66
89
  @updateMonthTop="onUpdateMonthTop"
@@ -94,6 +117,7 @@
94
117
  :forbidDaysToast="forbidDaysToast"
95
118
  :monthFormat="monthFormat"
96
119
  :todayDate="todayDate"
120
+ :todayColor="todayColor"
97
121
  ref="month"
98
122
  @monthSelected="monthSelected"
99
123
  @updateMonthTop="onUpdateMonthTop"
@@ -114,6 +138,32 @@
114
138
  </slot>
115
139
  </view>
116
140
  </u-popup>
141
+ <u-popup
142
+ :show="timePickerShow"
143
+ mode="center"
144
+ :round="8"
145
+ :closeOnClickOverlay="true"
146
+ @close="closeTimePicker"
147
+ >
148
+ <view class="u-calendar__time-picker">
149
+ <view class="u-calendar__time-picker__header">
150
+ <text class="u-calendar__time-picker__cancel" @click="closeTimePicker">取消</text>
151
+ <text class="u-calendar__time-picker__title">选择时间</text>
152
+ <text class="u-calendar__time-picker__confirm" @click="confirmTimePicker">确定</text>
153
+ </view>
154
+ <picker-view class="u-calendar__time-picker__body" :value="timePickerValue" @change="onTimePickerChange">
155
+ <picker-view-column>
156
+ <view v-for="(item, index) in hourOptions" :key="`h-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
157
+ </picker-view-column>
158
+ <picker-view-column v-if="timePrecision !== 'hour'">
159
+ <view v-for="(item, index) in minuteOptions" :key="`m-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
160
+ </picker-view-column>
161
+ <picker-view-column v-if="timePrecision === 'second'">
162
+ <view v-for="(item, index) in secondOptions" :key="`s-${index}`" class="u-calendar__time-picker__item">{{ item }}</view>
163
+ </picker-view-column>
164
+ </picker-view>
165
+ </view>
166
+ </u-popup>
117
167
  </template>
118
168
 
119
169
  <script>
@@ -159,9 +209,13 @@ import { t } from '../../libs/i18n'
159
209
  * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
160
210
  * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
161
211
  * @property {String} rangeResultMode 区间模式下确认返回值格式,all-返回区间内所有日期,boundary-仅返回起止日期 (默认 'all' )
212
+ * @property {Boolean} enableTime 是否开启时分秒选择 (默认 false )
213
+ * @property {String} timePrecision 时间精度,hour-仅时,minute-时分,second-时分秒 (默认 'minute' )
214
+ * @property {String} defaultTime 默认时间,支持HH/HH:mm/HH:mm:ss,不传则按00:00补齐
162
215
  * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
163
216
  * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
164
217
  * @property {Boolean} monthSwitch 是否启用非滚动的单月切换模式 (默认 false )
218
+ * @property {String} todayColor 今天日期的独立高亮颜色,默认跟随主题色
165
219
  * @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
166
220
  *
167
221
  * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
@@ -189,6 +243,15 @@ export default {
189
243
  scrollIntoView: '',
190
244
  scrollIntoViewScroll: '',
191
245
  scrollTop:0,
246
+ timePickerShow: false,
247
+ timePickerTarget: 'single',
248
+ timePickerValue: [0, 0, 0],
249
+ hourOptions: [],
250
+ minuteOptions: [],
251
+ secondOptions: [],
252
+ singleTime: '00:00',
253
+ rangeStartTime: '00:00',
254
+ rangeEndTime: '00:00',
192
255
  // 过滤处理方法
193
256
  innerFormatter: (value) => value
194
257
  }
@@ -206,6 +269,13 @@ export default {
206
269
  this.setMonth()
207
270
  }
208
271
  },
272
+ timePrecision() {
273
+ this.initTimeOptions()
274
+ this.initTimeValues()
275
+ },
276
+ defaultTime() {
277
+ this.initTimeValues()
278
+ },
209
279
  // 打开弹窗时,设置月份数据
210
280
  show: {
211
281
  immediate: true,
@@ -288,6 +358,26 @@ export default {
288
358
  switchNextYearDisabled() {
289
359
  return this.monthIndex + 12 > this.months.length - 1
290
360
  },
361
+ showTimePanel() {
362
+ if (!this.enableTime) return false
363
+ if (this.mode === 'single') return true
364
+ if (this.mode === 'range' && this.rangeResultMode === 'boundary') {
365
+ return true
366
+ }
367
+ return false
368
+ },
369
+ singleDateLabel() {
370
+ return this.selected[0] || '--'
371
+ },
372
+ rangeStartDateLabel() {
373
+ return this.selected[0] || '--'
374
+ },
375
+ rangeEndDateLabel() {
376
+ if (this.selected.length >= 2) {
377
+ return this.selected[this.selected.length - 1]
378
+ }
379
+ return '--'
380
+ },
291
381
  buttonDisabled() {
292
382
  // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
293
383
  if (this.mode === 'range') {
@@ -308,16 +398,129 @@ export default {
308
398
  emits: ["confirm", "close"],
309
399
  methods: {
310
400
  addUnit,
401
+ padTime(num) {
402
+ return String(num).padStart(2, '0')
403
+ },
404
+ initTimeOptions() {
405
+ this.hourOptions = Array.from({ length: 24 }, (_, i) => this.padTime(i))
406
+ this.minuteOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i))
407
+ this.secondOptions = Array.from({ length: 60 }, (_, i) => this.padTime(i))
408
+ },
409
+ parseTimeValue(value = '') {
410
+ const raw = String(value || '').trim()
411
+ const parts = raw.length ? raw.split(':') : []
412
+ const getNumber = (index, max) => {
413
+ const current = Number(parts[index] || 0)
414
+ if (Number.isNaN(current)) return 0
415
+ return Math.max(0, Math.min(max, current))
416
+ }
417
+ const hour = getNumber(0, 23)
418
+ const minute = getNumber(1, 59)
419
+ const second = getNumber(2, 59)
420
+ return [hour, minute, second]
421
+ },
422
+ getDefaultTimeValue() {
423
+ const [hour, minute, second] = this.parseTimeValue(this.defaultTime)
424
+ if (this.timePrecision === 'hour') {
425
+ return this.padTime(hour)
426
+ }
427
+ if (this.timePrecision === 'second') {
428
+ return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}`
429
+ }
430
+ return `${this.padTime(hour)}:${this.padTime(minute)}`
431
+ },
432
+ initTimeValues() {
433
+ const value = this.getDefaultTimeValue()
434
+ this.singleTime = value
435
+ this.rangeStartTime = value
436
+ this.rangeEndTime = value
437
+ },
438
+ timeToPickerValue(value = '') {
439
+ const [hour, minute, second] = this.parseTimeValue(value)
440
+ return [hour, minute, second]
441
+ },
442
+ pickerValueToTime(value = []) {
443
+ const hour = Number(value[0] || 0)
444
+ const minute = Number(value[1] || 0)
445
+ const second = Number(value[2] || 0)
446
+ if (this.timePrecision === 'hour') {
447
+ return this.padTime(hour)
448
+ }
449
+ if (this.timePrecision === 'second') {
450
+ return `${this.padTime(hour)}:${this.padTime(minute)}:${this.padTime(second)}`
451
+ }
452
+ return `${this.padTime(hour)}:${this.padTime(minute)}`
453
+ },
454
+ openTimePicker(target) {
455
+ this.timePickerTarget = target
456
+ let currentValue = this.singleTime
457
+ if (target === 'start') currentValue = this.rangeStartTime
458
+ if (target === 'end') currentValue = this.rangeEndTime
459
+ this.timePickerValue = this.timeToPickerValue(currentValue)
460
+ this.timePickerShow = true
461
+ },
462
+ onTimePickerChange(e) {
463
+ this.timePickerValue = e.detail.value
464
+ },
465
+ closeTimePicker() {
466
+ this.timePickerShow = false
467
+ },
468
+ confirmTimePicker() {
469
+ const value = this.pickerValueToTime(this.timePickerValue)
470
+ if (this.timePickerTarget === 'single') this.singleTime = value
471
+ if (this.timePickerTarget === 'start') this.rangeStartTime = value
472
+ if (this.timePickerTarget === 'end') this.rangeEndTime = value
473
+ this.timePickerShow = false
474
+ },
475
+ timeToSecond(timeText = '') {
476
+ const [hour, minute, second] = this.parseTimeValue(timeText)
477
+ return hour * 3600 + minute * 60 + second
478
+ },
479
+ validateSameDayRangeTime() {
480
+ if (!this.enableTime || this.mode !== 'range' || this.rangeResultMode !== 'boundary') {
481
+ return true
482
+ }
483
+ if (this.selected.length < 2) return true
484
+ const startDate = this.selected[0]
485
+ const endDate = this.selected[this.selected.length - 1]
486
+ if (startDate !== endDate) return true
487
+ const startSeconds = this.timeToSecond(this.rangeStartTime)
488
+ const endSeconds = this.timeToSecond(this.rangeEndTime)
489
+ if (endSeconds < startSeconds) {
490
+ uni.showToast({
491
+ title: '结束时间不能早于开始时间',
492
+ icon: 'none'
493
+ })
494
+ return false
495
+ }
496
+ return true
497
+ },
498
+ appendTime(dateText, timeText) {
499
+ return `${dateText} ${timeText}`
500
+ },
311
501
  getConfirmValue(selected = this.selected) {
502
+ let result = selected
312
503
  if (
313
504
  this.mode === 'range' &&
314
505
  this.rangeResultMode === 'boundary' &&
315
506
  selected.length >= 2
316
507
  ) {
317
508
  const len = selected.length - 1
318
- return [selected[0], selected[len]]
509
+ result = [selected[0], selected[len]]
510
+ }
511
+ if (!this.showTimePanel || !this.enableTime) {
512
+ return result
513
+ }
514
+ if (this.mode === 'single' && result.length >= 1) {
515
+ return [this.appendTime(result[0], this.singleTime)]
516
+ }
517
+ if (this.mode === 'range' && this.rangeResultMode === 'boundary' && result.length >= 2) {
518
+ return [
519
+ this.appendTime(result[0], this.rangeStartTime),
520
+ this.appendTime(result[1], this.rangeEndTime)
521
+ ]
319
522
  }
320
- return selected
523
+ return result
321
524
  },
322
525
  // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
323
526
  setFormatter(e) {
@@ -337,6 +540,7 @@ export default {
337
540
  return
338
541
  }
339
542
  if( scene === 'tap') {
543
+ if (!this.validateSameDayRangeTime()) return
340
544
  this.$emit('confirm', this.getConfirmValue())
341
545
  }
342
546
  }
@@ -359,6 +563,8 @@ export default {
359
563
  bottomPadding = 30
360
564
  }
361
565
  this.listHeight = this.rowHeight * (this.monthSwitch ? 6 : 5) + bottomPadding
566
+ this.initTimeOptions()
567
+ this.initTimeValues()
362
568
  this.setMonth()
363
569
  },
364
570
  close() {
@@ -367,6 +573,7 @@ export default {
367
573
  // 点击确定按钮
368
574
  confirm() {
369
575
  if (!this.buttonDisabled) {
576
+ if (!this.validateSameDayRangeTime()) return
370
577
  this.$emit('confirm', this.getConfirmValue())
371
578
  }
372
579
  },
@@ -495,16 +702,24 @@ export default {
495
702
  return
496
703
  }
497
704
  const targetMonth = dayjs(this.todayDate).format('YYYY-MM')
705
+ const selectToday = () => {
706
+ if (this.mode === 'range') {
707
+ return
708
+ }
709
+ this.$refs.month && this.$refs.month.selectDate(this.todayDate)
710
+ }
498
711
  if (this.monthSwitch) {
499
712
  const todayMonthIndex = this.months.findIndex(({ year, month }) => {
500
713
  return `${year}-${padZero(month)}` === targetMonth
501
714
  })
502
715
  if (todayMonthIndex !== -1) {
503
716
  this.monthIndex = todayMonthIndex
717
+ this.$nextTick(selectToday)
504
718
  }
505
719
  return
506
720
  }
507
721
  this.scrollIntoDefaultMonth(targetMonth)
722
+ this.$nextTick(selectToday)
508
723
  },
509
724
  // 滚动到默认设置的月份
510
725
  scrollIntoDefaultMonth(selected) {
@@ -579,6 +794,84 @@ export default {
579
794
  color: var(--up-main-color, $u-main-color);
580
795
  background-color: var(--up-card-bg-color, #ffffff);
581
796
 
797
+ &__time-panel {
798
+ padding: 8px 16px 0;
799
+ }
800
+
801
+ &__time-row {
802
+ height: 34px;
803
+ display: flex;
804
+ align-items: center;
805
+ justify-content: space-between;
806
+ margin-bottom: 6px;
807
+ }
808
+
809
+ &__time-date {
810
+ font-size: 13px;
811
+ color: #303133;
812
+ }
813
+
814
+ &__time-trigger {
815
+ min-width: 92px;
816
+ height: 30px;
817
+ padding: 0 10px;
818
+ border-radius: 15px;
819
+ border: 1px solid #dcdfe6;
820
+ display: flex;
821
+ align-items: center;
822
+ justify-content: center;
823
+ }
824
+
825
+ &__time-text {
826
+ font-size: 14px;
827
+ color: #303133;
828
+ }
829
+
830
+ &__time-picker {
831
+ width: 320px;
832
+ background-color: #ffffff;
833
+ border-radius: 8px;
834
+ overflow: hidden;
835
+ }
836
+
837
+ &__time-picker__header {
838
+ height: 44px;
839
+ padding: 0 14px;
840
+ display: flex;
841
+ align-items: center;
842
+ justify-content: space-between;
843
+ border-bottom: 1px solid #f0f0f0;
844
+ }
845
+
846
+ &__time-picker__cancel {
847
+ font-size: 14px;
848
+ color: #909399;
849
+ }
850
+
851
+ &__time-picker__title {
852
+ font-size: 15px;
853
+ color: #303133;
854
+ font-weight: 600;
855
+ }
856
+
857
+ &__time-picker__confirm {
858
+ font-size: 14px;
859
+ color: #3c9cff;
860
+ }
861
+
862
+ &__time-picker__body {
863
+ width: 320px;
864
+ height: 200px;
865
+ }
866
+
867
+ &__time-picker__item {
868
+ height: 40px;
869
+ line-height: 40px;
870
+ text-align: center;
871
+ font-size: 16px;
872
+ color: #303133;
873
+ }
874
+
582
875
  &__confirm {
583
876
  padding: 7px 18px;
584
877
  }
@@ -150,6 +150,8 @@ export default {
150
150
  firstChange: true,
151
151
  // value绑定值的变化是由内部还是外部引起的
152
152
  changeFromInner: false,
153
+ // 记录blur时原生输入框的值,用于识别blur阶段的外部格式化回流
154
+ blurValue: null,
153
155
  // 过滤处理方法
154
156
  innerFormatter: value => value,
155
157
  showPassword: false
@@ -166,11 +168,17 @@ export default {
166
168
  immediate: true,
167
169
  handler(newVal, oldVal) {
168
170
  // console.log(newVal, oldVal)
169
- if (this.changeFromInner || this.innerValue === newVal) {
171
+ const isBlurFormattedValue =
172
+ this.blurValue !== null &&
173
+ newVal !== this.blurValue &&
174
+ newVal !== this.innerValue;
175
+ if ((this.changeFromInner && !isBlurFormattedValue) || this.innerValue === newVal) {
170
176
  this.changeFromInner = false; // 重要否则会出现双向绑定失效问题https://github.com/ijry/uview-plus/issues/419
177
+ this.blurValue = null;
171
178
  return;
172
179
  }
173
180
  this.innerValue = newVal;
181
+ this.blurValue = null;
174
182
  // 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法
175
183
  if (
176
184
  this.firstChange === false &&
@@ -302,6 +310,7 @@ export default {
302
310
  },
303
311
  // 输入框失去焦点时触发
304
312
  onBlur(event) {
313
+ this.blurValue = event?.detail?.value;
305
314
  this.$emit("blur", event.detail.value);
306
315
  // H5端的blur会先于点击清除控件的点击click事件触发,导致focused
307
316
  // 瞬间为false,从而隐藏了清除控件而无法被点击到
@@ -407,10 +407,25 @@ export default {
407
407
  // 设置整体各列的columns的值
408
408
  setColumns(columns) {
409
409
  // console.log(columns)
410
+ const prevColumns = this.innerColumns
410
411
  this.innerColumns = deepClone(columns)
411
412
  // 如果在设置各列数据时,没有被设置默认的各列索引defaultIndex,那么用0去填充它,数组长度为列的数量
412
413
  if (this.innerIndex.length === 0) {
413
414
  this.innerIndex = new Array(columns.length).fill(0)
415
+ } else {
416
+ // 修复异步columns加载时defaultIndex位置不更新的问题 (issue #841)
417
+ // 当某列从空数组变为有数据时,picker-view不会因为:value未变化而重新滚动到指定位置
418
+ // 需要通过先清空再赋值的方式强制触发picker-view的滚动更新
419
+ const hasColumnsChangedFromEmpty = columns.some(
420
+ (col, i) => col && col.length > 0 && (!prevColumns[i] || prevColumns[i].length === 0)
421
+ )
422
+ if (hasColumnsChangedFromEmpty) {
423
+ const targetIndex = deepClone(this.innerIndex)
424
+ this.innerIndex = []
425
+ this.$nextTick(() => {
426
+ this.innerIndex = targetIndex
427
+ })
428
+ }
414
429
  }
415
430
  },
416
431
  // 获取各列选中值对应的索引
@@ -165,12 +165,10 @@
165
165
  {{ isExpanded(item.row) ? '▼' : '▶' }}
166
166
  </view>
167
167
  </view>
168
- <slot name="cell" :row="item.row" :column="col" :prow="item.parentRow"
169
- :rowIndex="item.rowIndex" :columnIndex="colIndex" :level="item.level">
170
- <view class="u-table-cell_content">
171
- {{ item.row[col.key] }}
172
- </view>
173
- </slot>
168
+ <!-- 固定列浮动视图直接内联渲染,不使用 slot,避免与主表体 slot name="cell" 重名报错(微信小程序限制) -->
169
+ <view class="u-table-cell_content">
170
+ {{ item.row[col.key] }}
171
+ </view>
174
172
  </template>
175
173
  </view>
176
174
  </view>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定。",
5
- "version": "3.8.49",
5
+ "version": "3.8.55",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",
@@ -129,6 +129,21 @@ declare interface CalendarProps {
129
129
  * @default "all"
130
130
  */
131
131
  rangeResultMode?: 'all' | 'boundary'
132
+ /**
133
+ * 是否开启时分秒选择
134
+ * @default false
135
+ */
136
+ enableTime?: boolean
137
+ /**
138
+ * 时间精度:`hour`仅时、`minute`时分、`second`时分秒
139
+ * @default "minute"
140
+ */
141
+ timePrecision?: 'hour' | 'minute' | 'second'
142
+ /**
143
+ * 默认时间,支持 `HH` / `HH:mm` / `HH:mm:ss`
144
+ * @default ""
145
+ */
146
+ defaultTime?: string
132
147
  /**
133
148
  * 圆角值,默认无圆角
134
149
  * @default 0
@@ -144,6 +159,10 @@ declare interface CalendarProps {
144
159
  * @default true
145
160
  */
146
161
  showToday?: boolean
162
+ /**
163
+ * 今天日期的独立高亮颜色,默认跟随主题色
164
+ */
165
+ todayColor?: string
147
166
  /**
148
167
  * 日期选择完成后触发,若`show-confirm`为`true`,则点击确认按钮后触发
149
168
  */