uview-plus 3.8.42 → 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,91 @@
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
+
45
+ ## 3.8.49(2026-06-05)
46
+ fix(datetime-picker): 修复hasInput模式下点击遮罩无法关闭弹窗的问题
47
+
48
+ - 在close()方法中补充将showByClickInput置为false的逻辑,
49
+ - 与cancel()和confirm()的处理保持一致,修复issue #759
50
+
51
+ ## 3.8.48(2026-06-04)
52
+ feat(calendar): 新增区间模式返回值格式配置
53
+
54
+ - 新增 rangeResultMode 配置项,支持 range 模式在 confirm 时返回全部区间日期或仅返回起止日期。
55
+ - 本次改动覆盖默认配置、组件 props、确认回调返回逻辑与 TypeScript 类型声明,默认值为 all 以保持现有行为兼容。
56
+
57
+ ## 3.8.47(2026-06-03)
58
+ 扩展 u-datetime-picker 支持小时与秒级时间选择
59
+
60
+ - 新增 datehour、timesecond、datetimesecond 三种模式,覆盖年月日时与秒级场景\n- 增加 minSecond/maxSecond 配置,并同步列生成、边界计算、回显与变更事件处理\n- 更新 TypeScript 类型定义,确保新模式和秒级属性在项目中可被正确约束
61
+
62
+ ## 3.8.46(2026-06-02)
63
+ fix: 修复DatetimePicker在pageInline模式下无法滚动到默认值问题 #941
64
+
65
+ pageInline模式下picker-view原生组件常驻显示,Android/HarmonyOS端
66
+ 原生组件尚未完成初始化时已渲染为索引0位置,后续更新无法可靠触发滚动。
67
+
68
+ - updateColumnValue改用$nextTick+条件延时:pageInline时等待100ms
69
+ - mounted新增pageInline兜底:200ms后再次设置索引确保原生组件就绪
70
+
71
+ ## 3.8.45(2026-06-01)
72
+ fix: 修复NumberBox最小值限制导致无法输入新值问题 #914
73
+
74
+ ## 3.8.44(2026-05-31)
75
+ fix: 修复up-tooltip未暴露open/close方法及manual模式失效问题 #1021
76
+
77
+ - 新增 show prop,支持 triggerMode="manual" 时通过 :show 控制显示
78
+ - 新增 open() / close() 方法并对外暴露
79
+ - 监听 show prop 变化,manual 模式下自动调用 open/close
80
+ - 新增 open / close emit 事件,u-popover 的 @open @close 监听可正常工作
81
+ - 统一内部显示/隐藏逻辑均走 open()/close() 方法
82
+
83
+ ## 3.8.43(2026-05-30)
84
+ fix: 修复小程序中 tabbar midButton 模式图标不显示问题
85
+
86
+ - 使用 :deep() 选择器替代普通后代选择器,解决 virtualHost:true 导致
87
+ - 父组件 scoped 样式无法传播到子组件根元素的问题。
88
+
1
89
  ## 3.8.42(2026-05-29)
2
90
  feat: 新增单行日历组件并支持下拉展开完整月历及修复签名组件 clear 方法实现
3
91
 
@@ -34,13 +34,18 @@ export default {
34
34
  readonly: false,
35
35
  showConfirm: true,
36
36
  maxRange: Number.MAX_SAFE_INTEGER, // Infinity
37
- rangePrompt: '',
38
- showRangePrompt: true,
39
- allowSameDay: false,
40
- round: 0,
37
+ rangePrompt: '',
38
+ showRangePrompt: true,
39
+ allowSameDay: false,
40
+ rangeResultMode: 'all',
41
+ enableTime: false,
42
+ timePrecision: 'minute',
43
+ defaultTime: '',
44
+ round: 0,
41
45
  monthNum: 3,
42
46
  monthSwitch: false,
43
47
  showToday: true,
48
+ todayColor: '',
44
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")],
45
50
  forbidDays: [],
46
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
  }
@@ -132,6 +132,26 @@ export const props = defineMixin({
132
132
  allowSameDay: {
133
133
  type: Boolean,
134
134
  default: () => defProps.calendar.allowSameDay
135
+ },
136
+ // 区间模式下确认返回值格式,all-返回区间内所有日期,boundary-仅返回起止日期
137
+ rangeResultMode: {
138
+ type: String,
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
135
155
  },
136
156
  // 圆角值
137
157
  round: {
@@ -153,6 +173,11 @@ export const props = defineMixin({
153
173
  type: Boolean,
154
174
  default: () => defProps.calendar.showToday
155
175
  },
176
+ // 今天日期的独立高亮颜色,默认跟随主题色
177
+ todayColor: {
178
+ type: String,
179
+ default: () => defProps.calendar.todayColor
180
+ },
156
181
  // 星期文案
157
182
  weekText: {
158
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>
@@ -158,9 +208,14 @@ import { t } from '../../libs/i18n'
158
208
  * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
159
209
  * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
160
210
  * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
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补齐
161
215
  * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
162
216
  * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
163
217
  * @property {Boolean} monthSwitch 是否启用非滚动的单月切换模式 (默认 false )
218
+ * @property {String} todayColor 今天日期的独立高亮颜色,默认跟随主题色
164
219
  * @property {Array} weekText 星期文案 (默认 ['一', '二', '三', '四', '五', '六', '日'] )
165
220
  *
166
221
  * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
@@ -188,6 +243,15 @@ export default {
188
243
  scrollIntoView: '',
189
244
  scrollIntoViewScroll: '',
190
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',
191
255
  // 过滤处理方法
192
256
  innerFormatter: (value) => value
193
257
  }
@@ -205,6 +269,13 @@ export default {
205
269
  this.setMonth()
206
270
  }
207
271
  },
272
+ timePrecision() {
273
+ this.initTimeOptions()
274
+ this.initTimeValues()
275
+ },
276
+ defaultTime() {
277
+ this.initTimeValues()
278
+ },
208
279
  // 打开弹窗时,设置月份数据
209
280
  show: {
210
281
  immediate: true,
@@ -287,6 +358,26 @@ export default {
287
358
  switchNextYearDisabled() {
288
359
  return this.monthIndex + 12 > this.months.length - 1
289
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
+ },
290
381
  buttonDisabled() {
291
382
  // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
292
383
  if (this.mode === 'range') {
@@ -307,6 +398,130 @@ export default {
307
398
  emits: ["confirm", "close"],
308
399
  methods: {
309
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
+ },
501
+ getConfirmValue(selected = this.selected) {
502
+ let result = selected
503
+ if (
504
+ this.mode === 'range' &&
505
+ this.rangeResultMode === 'boundary' &&
506
+ selected.length >= 2
507
+ ) {
508
+ const len = selected.length - 1
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
+ ]
522
+ }
523
+ return result
524
+ },
310
525
  // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
311
526
  setFormatter(e) {
312
527
  this.innerFormatter = e
@@ -325,7 +540,8 @@ export default {
325
540
  return
326
541
  }
327
542
  if( scene === 'tap') {
328
- this.$emit('confirm', this.selected)
543
+ if (!this.validateSameDayRangeTime()) return
544
+ this.$emit('confirm', this.getConfirmValue())
329
545
  }
330
546
  }
331
547
  }
@@ -347,6 +563,8 @@ export default {
347
563
  bottomPadding = 30
348
564
  }
349
565
  this.listHeight = this.rowHeight * (this.monthSwitch ? 6 : 5) + bottomPadding
566
+ this.initTimeOptions()
567
+ this.initTimeValues()
350
568
  this.setMonth()
351
569
  },
352
570
  close() {
@@ -355,7 +573,8 @@ export default {
355
573
  // 点击确定按钮
356
574
  confirm() {
357
575
  if (!this.buttonDisabled) {
358
- this.$emit('confirm', this.selected)
576
+ if (!this.validateSameDayRangeTime()) return
577
+ this.$emit('confirm', this.getConfirmValue())
359
578
  }
360
579
  },
361
580
  // 获得两个日期之间的月份数
@@ -483,16 +702,24 @@ export default {
483
702
  return
484
703
  }
485
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
+ }
486
711
  if (this.monthSwitch) {
487
712
  const todayMonthIndex = this.months.findIndex(({ year, month }) => {
488
713
  return `${year}-${padZero(month)}` === targetMonth
489
714
  })
490
715
  if (todayMonthIndex !== -1) {
491
716
  this.monthIndex = todayMonthIndex
717
+ this.$nextTick(selectToday)
492
718
  }
493
719
  return
494
720
  }
495
721
  this.scrollIntoDefaultMonth(targetMonth)
722
+ this.$nextTick(selectToday)
496
723
  },
497
724
  // 滚动到默认设置的月份
498
725
  scrollIntoDefaultMonth(selected) {
@@ -567,6 +794,84 @@ export default {
567
794
  color: var(--up-main-color, $u-main-color);
568
795
  background-color: var(--up-card-bg-color, #ffffff);
569
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
+
570
875
  &__confirm {
571
876
  padding: 7px 18px;
572
877
  }
@@ -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,