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 +44 -0
- package/components/u-calendar/calendar.js +6 -2
- package/components/u-calendar/month.vue +41 -6
- package/components/u-calendar/props.js +20 -0
- package/components/u-calendar/u-calendar.vue +295 -2
- package/components/u-input/u-input.vue +10 -1
- package/components/u-picker/u-picker.vue +15 -0
- package/components/u-table2/u-table2.vue +4 -6
- package/package.json +1 -1
- package/types/comps/calendar.d.ts +19 -0
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
|
-
|
|
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(
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
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
|
*/
|