uview-plus 3.8.117 → 3.8.119

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,30 @@
1
+ ## 3.8.119
2
+ fix: 修复 datetime-picker format 兼容性与 tabbar 边框切页丢失
3
+
4
+ 本版修复两个独立问题,均不涉及组件 API 变更。
5
+
6
+ fix: datetime-picker format 兼容库自身的 yyyy-mm-dd 写法,字符串绑定值不再退回 minDate (#537)
7
+
8
+ 库内置的日期格式化工具 `timeFormat` 支持 `yyyy-mm-dd` 写法(全小写),但 u-datetime-picker 的 format 解析只识别 `YYYY-MM-DD`(全大写),导致用户按文档示例传入小写 format 时,传入的字符串绑定值无法被正确解析,组件退回到 minDate 而非用户期望的日期。
9
+
10
+ - format 解析逻辑补充小写月日模式 `mm` / `dd`,与库自身的 timeFormat 对齐
11
+ - 保持对原有大写 `MM` / `DD` 的兼容
12
+ - 新增 `verify:datetime-picker-format` 回归校验
13
+
14
+ fix: tabbar 顶部边框改为组件内联绘制,修复切换页面后边框丢失 (#873)
15
+
16
+ u-tabbar 的顶部边框原先靠模板上的全局工具类 `u-border-top` 绘制,组件自身 scoped 样式表没有任何 border 声明。该类只存在于宿主项目的全局样式表,而小程序自定义组件有样式隔离,能否命中取决于宿主引入全局样式的方式,于是出现同一个 tabbar 在首页有边框、切到另一个 tab 页边框消失。
17
+
18
+ - 改为在 tabbarStyle 里内联 borderTopWidth/Style/Color,边框跟着组件走
19
+ - 去掉 borderColor 上用来压全局类 !important 的 hack(nvue/weex 无法解析)
20
+ - 让 borderColor 为空时的主题边框色回退真正生效
21
+ - 新增 `verify:tabbar-border` 回归校验
22
+
23
+ ## 3.8.118
24
+ 修复 u-waterfall 在强制解锁后旧分配循环误释放新循环分发锁的问题。
25
+
26
+ 旧循环在 await 后恢复时,现仅当前运行令牌的持有者可释放分发锁,避免接管循环与旧循环并发分配造成数据重复、列高错乱及回调重复触发。
27
+
1
28
  ## 3.8.117
2
29
  fix: 修复瀑布流分发锁死、u-image 置于 u-swiper 内无法滑动、u-list-item 挂载竞态
3
30
 
@@ -63,8 +63,7 @@
63
63
  import { mpMixin } from '../../libs/mixin/mpMixin';
64
64
  import { mixin } from '../../libs/mixin/mixin';
65
65
  import dayjs from './dayjs.esm.min.js';
66
- import { range, error, padZero } from '../../libs/function/index';
67
- import test from '../../libs/function/test';
66
+ import { range, error, padZero, timeFormat } from '../../libs/function/index';
68
67
  /**
69
68
  * DatetimePicker 时间日期选择器
70
69
  * @description 此选择器用于时间日期
@@ -72,6 +71,8 @@
72
71
  * @property {Boolean} show 用于控制选择器的弹出与收起 ( 默认 false )
73
72
  * @property {Boolean} showToolbar 是否显示顶部的操作栏 ( 默认 true )
74
73
  * @property {String | Number} modelValue 绑定值
74
+ * @property {Boolean} hasInput 是否自带input输入框 ( 默认 false )
75
+ * @property {String} format hasInput模式下输入框的日期显示格式,同时支持 yyyy-mm-dd(uview-plus timeFormat 写法) 与 YYYY-MM-DD(dayjs 写法)
75
76
  * @property {String} title 顶部标题
76
77
  * @property {String} mode 展示格式 mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择,mode=datehour为日期小时选择,mode=timesecond为时分秒选择,mode=datetimesecond为日期时分秒选择 ( 默认 ‘datetime )
77
78
  * @property {Number} maxDate 可选的最大时间 默认值为后10年
@@ -210,6 +211,16 @@
210
211
  const value = column[index]
211
212
  return value == null ? fallback : value
212
213
  },
214
+ // format 有两种写法:uview-plus 自身 timeFormat($u.timeFormat) 的规则(yyyy-mm-dd hh:MM:ss,
215
+ // 小写 y 表示年、MM 表示分钟),以及 dayjs 的规则(YYYY-MM-DD HH:mm:ss)。
216
+ // dayjs 没有小写 y 的 token,以此区分两者:否则按库里通用写法传 'yyyy-mm-dd',
217
+ // dayjs 只认得其中的 mm(分钟)与 dd(星期),会得到 'yyyy-00-Th' 这种结果(issue #537)
218
+ formatByPattern(value, pattern) {
219
+ if (/y/.test(pattern)) {
220
+ return timeFormat(value, pattern)
221
+ }
222
+ return dayjs(value).format(pattern)
223
+ },
213
224
  getInputValue(newValue) {
214
225
  if (newValue == '' || !newValue || newValue == undefined) {
215
226
  this.inputValue = ''
@@ -219,7 +230,7 @@
219
230
  this.inputValue = newValue
220
231
  } else {
221
232
  if (this.format) {
222
- this.inputValue = dayjs(newValue).format(this.format)
233
+ this.inputValue = this.formatByPattern(newValue, this.format)
223
234
  } else {
224
235
  let format = ''
225
236
  switch (this.mode) {
@@ -529,14 +540,39 @@
529
540
  generateArray(start, end) {
530
541
  return Array.from(new Array(end + 1).keys()).slice(start)
531
542
  },
543
+ // 把绑定值统一解析成毫秒时间戳,识别不出日期时返回null
544
+ // 支持毫秒时间戳(number或纯数字字符串),以及'2024-10-24'、'2024/10/24 15:08:09'这类
545
+ // 文档里写明支持的String绑定值
546
+ parseDateValue(value) {
547
+ if (value === '' || value === null || value === undefined) {
548
+ return null
549
+ }
550
+ if (typeof value === 'number') {
551
+ return Number.isFinite(value) ? value : null
552
+ }
553
+ const text = String(value).trim()
554
+ if (!text) {
555
+ return null
556
+ }
557
+ // 纯数字按时间戳处理,避免dayjs把'1729699200000'当成日期字符串去解析
558
+ if (/^-?\d+$/.test(text)) {
559
+ const timestamp = Number(text)
560
+ return Number.isFinite(timestamp) ? timestamp : null
561
+ }
562
+ const parsed = dayjs(text)
563
+ return parsed.isValid() ? parsed.valueOf() : null
564
+ },
532
565
  // 得出合法的时间
533
566
  correctValue(value) {
534
567
  const isDateMode = !['time', 'timesecond'].includes(this.mode)
535
- // if (isDateMode && !test.date(value)) {
536
- if (isDateMode && !dayjs.unix(value).isValid()) {
537
- // 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
538
- value = this.minDate
539
- } else if (!isDateMode && !value) {
568
+ if (isDateMode) {
569
+ // 日期类型统一解析成毫秒时间戳,没有设置合法的当前时间时才使用最小时间。
570
+ // 这里不能用dayjs.unix(value).isValid()判断:dayjs.unix只接受秒级数字,
571
+ // 日期字符串乘1000后是NaN,'2024-10-24'这类合法值会被判为非法并被替换成
572
+ // minDate(默认当前年份-10),选择器于是停在十年前(issue #537)
573
+ const timestamp = this.parseDateValue(value)
574
+ value = timestamp === null ? this.minDate : timestamp
575
+ } else if (!value) {
540
576
  // 如果是时间类型,而又没有默认值的话,就用最小时间
541
577
  value = this.mode === 'timesecond'
542
578
  ? `${padZero(this.minHour)}:${padZero(this.minMinute)}:${padZero(this.minSecond)}`
@@ -5,7 +5,6 @@
5
5
  ref="u-tabbar__content"
6
6
  @touchmove.stop.prevent="noop"
7
7
  :class="[
8
- border && 'u-border-top',
9
8
  fixed && 'u-tabbar--fixed',
10
9
  `u-tabbar--${styleType}`,
11
10
  textMode === 'active' && 'u-tabbar--text-active'
@@ -67,6 +66,10 @@
67
66
  }
68
67
  },
69
68
  computed: {
69
+ // 顶部边框颜色,未指定borderColor时回退到主题边框色
70
+ tabbarBorderColor() {
71
+ return this.borderColor || this.upThemeVar('--up-border-color', '#dadbde')
72
+ },
70
73
  tabbarStyle() {
71
74
  const style = {
72
75
  zIndex: this.zIndex,
@@ -75,10 +78,13 @@
75
78
  '--up-tabbar-inactive-bg': this.inactiveBackgroundColor || 'transparent',
76
79
  '--up-tabbar-icon-scale': `${this.iconScale}`
77
80
  }
78
- if (this.borderColor) {
79
- style.borderColor = this.borderColor + ' !important'
80
- } else {
81
- style.borderColor = this.upThemeVar('--up-border-color', '#dadbde')
81
+ // 顶部边框由组件自身内联样式绘制,不再依赖全局的u-border-top工具类。
82
+ // 小程序自定义组件存在样式隔离,全局类名是否能作用到组件内部节点取决于宿主项目
83
+ // 引入全局样式的方式,会出现同一个tabbar在部分页面有边框、切换页面后边框丢失的问题。
84
+ if (this.border) {
85
+ style.borderTopWidth = '0.5px'
86
+ style.borderTopStyle = 'solid'
87
+ style.borderTopColor = this.tabbarBorderColor
82
88
  }
83
89
  if (['pill', 'card', 'glow', 'convex'].includes(this.styleType)) {
84
90
  style.padding = '8rpx 12rpx 12rpx'
@@ -99,7 +99,9 @@
99
99
  distributionQueue: [],
100
100
  distributionRunning: false,
101
101
  distributionPromise: null,
102
- distributionGeneration: 0
102
+ distributionGeneration: 0,
103
+ // 每个分配循环持有独立令牌:锁被强制重置后新循环接管,旧循环恢复时只能安静退出
104
+ distributionRunToken: 0
103
105
  }
104
106
  },
105
107
  watch: {
@@ -266,39 +268,50 @@
266
268
  async runDistributionQueue() {
267
269
  if (this.distributionRunning) return;
268
270
  this.distributionRunning = true;
271
+ const runToken = ++this.distributionRunToken;
269
272
  try {
270
273
  while (this.distributionQueue.length > 0) {
274
+ if (runToken !== this.distributionRunToken) return;
271
275
  const task = this.distributionQueue.shift();
272
276
  if (task.generation !== this.distributionGeneration) continue;
273
- await this.distributeData(task.data, task.generation);
277
+ await this.distributeData(task.data, task.generation, runToken);
274
278
  }
275
279
  } finally {
276
- this.distributionRunning = false;
277
- this.distributionPromise = null;
278
- if (this.distributionQueue.length > 0) {
279
- this.distributionPromise = this.runDistributionQueue();
280
+ // 仅当自己仍是当前循环时才释放锁,否则会清掉接管者的运行状态
281
+ if (runToken === this.distributionRunToken) {
282
+ this.distributionRunning = false;
283
+ this.distributionPromise = null;
284
+ if (this.distributionQueue.length > 0) {
285
+ this.distributionPromise = this.runDistributionQueue();
286
+ }
280
287
  }
281
288
  }
282
289
  },
283
290
 
284
- // 逐项测量并分配数据,代数变化时立即退出
285
- async distributeData(newData, generation) {
291
+ // 数据被重置或分配循环被接管时,当前循环应立即停止写入
292
+ isStaleDistribution(generation, runToken) {
293
+ return generation !== this.distributionGeneration
294
+ || runToken !== this.distributionRunToken;
295
+ },
296
+
297
+ // 逐项测量并分配数据,代数或令牌变化时立即退出
298
+ async distributeData(newData, generation, runToken) {
286
299
  let columnHeights = new Array(this.columnList.length).fill(0);
287
300
  for (const item of newData) {
288
- if (generation !== this.distributionGeneration) return;
301
+ if (this.isStaleDistribution(generation, runToken)) return;
289
302
  columnHeights = await this.getColumnHeights();
290
- if (generation !== this.distributionGeneration) return;
303
+ if (this.isStaleDistribution(generation, runToken)) return;
291
304
 
292
305
  const minHeightIndex = this.getMinHeightColumnIndex(columnHeights);
293
306
  this.columnList[minHeightIndex].push(item);
294
307
 
295
308
  await sleep(this.addTime);
296
- if (generation !== this.distributionGeneration) return;
309
+ if (this.isStaleDistribution(generation, runToken)) return;
297
310
  await this.$nextTick();
298
- if (generation !== this.distributionGeneration) return;
311
+ if (this.isStaleDistribution(generation, runToken)) return;
299
312
  try {
300
313
  const rect = await this.$uGetRect(`#u-column-${minHeightIndex}`);
301
- if (generation !== this.distributionGeneration) return;
314
+ if (this.isStaleDistribution(generation, runToken)) return;
302
315
  if (rect.height) {
303
316
  columnHeights[minHeightIndex] = rect.height;
304
317
  this.$emit('after-add-one', {
@@ -310,7 +323,7 @@
310
323
  // 获取不到列高时,下一个元素会重新测量所有列
311
324
  }
312
325
  }
313
- if (generation !== this.distributionGeneration) return;
326
+ if (this.isStaleDistribution(generation, runToken)) return;
314
327
  this.$emit('after-add-all', {
315
328
  columnHeights: columnHeights,
316
329
  newData: newData
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.117",
5
+ "version": "3.8.119",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",