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 +88 -0
- package/components/u-calendar/calendar.js +9 -4
- package/components/u-calendar/month.vue +41 -6
- package/components/u-calendar/props.js +25 -0
- package/components/u-calendar/u-calendar.vue +307 -2
- package/components/u-datetime-picker/datetimePicker.js +2 -0
- package/components/u-datetime-picker/props.js +15 -5
- package/components/u-datetime-picker/u-datetime-picker.vue +117 -42
- package/components/u-input/u-input.vue +10 -1
- package/components/u-number-box/u-number-box.vue +8 -5
- package/components/u-picker/u-picker.vue +15 -0
- package/components/u-tabbar-item/u-tabbar-item.vue +2 -2
- package/components/u-table2/u-table2.vue +4 -6
- package/components/u-tooltip/props.js +5 -0
- package/components/u-tooltip/tooltip.js +2 -1
- package/components/u-tooltip/u-tooltip.vue +26 -13
- package/package.json +1 -1
- package/types/comps/calendar.d.ts +24 -0
- package/types/comps/datetimePicker.d.ts +15 -5
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
|
-
|
|
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(
|
|
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)
|
|
202
|
-
style.border = `1px solid ${this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|