uview-plus 3.8.113 → 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,86 @@
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
+
28
+ ## 3.8.117
29
+ fix: 修复瀑布流分发锁死、u-image 置于 u-swiper 内无法滑动、u-list-item 挂载竞态
30
+
31
+ 本版为三个问题的集中修复,均不涉及组件 API 变更。
32
+
33
+ fix: 修复 u-waterfall 在 App 切换 tabbar 后不再消费新数据(#1056)
34
+
35
+ App 端切换 tabbar 时,原生层会挂起隐藏页面的 setTimeout,`await sleep()` 永不 resolve,`runDistributionQueue` 的 finally 块无法执行,`distributionRunning` 永久为 true;切回页面后新数据只能入队,永远不会被分发。
36
+
37
+ - `clear()` 强制重置 `distributionRunning` 与 `distributionPromise`
38
+ - `redistributeData()` 在调用 clear 之前先解锁
39
+ - 新增 `verify:waterfall-lock-reset` 回归校验
40
+
41
+ fix: 修复 u-image 置于 u-swiper 内导致手势滑动失效(#894)
42
+
43
+ u-transition 根节点绑定的 `@touchmove="noop"` 会执行 `e.stopPropagation()`,使 touchmove 无法冒泡到父级;u-image 以 u-transition 作为根节点,放进 u-swiper 的 slot 后 swiper 收不到 touchmove,滑动手势失效。该拦截对淡入、滑入等过渡动画本身没有作用,仅会阻断父组件的正常手势,故直接移除。
44
+
45
+ fix: 修复 u-list-item 在 parent 未初始化时调用 updateOffsetFromChild 报错(#875)
46
+
47
+ 列表数据由后台异步返回、u-list-item 数量随之变化时,`uList.innerScrollTop` 的 watch 可能在 `mounted()` 之前触发;此时 `this.parent` 仍是 `created()` 中赋值的空对象,调用 `this.parent.updateOffsetFromChild` 会抛出 "is not a function"。
48
+
49
+ - watch 回调中增加守卫,parent 未就绪时直接返回
50
+ - 新增 `verify:list-item-parent-race` 回归校验
51
+
52
+ ## 3.8.116
53
+ fix: 修复组件卸载后查询节点导致 APP 与鸿蒙端报错(#1057)
54
+
55
+ 页面切换时 APP 与鸿蒙端会出现大面积 `Uncaught TypeError: Cannot read properties of undefined (reading '$') at uni-app-view.umd.js`,功能不受影响但日志被淹没,H5 端不出现。
56
+
57
+ 原因是组件卸载后仍在执行的异步回调继续发起节点查询:APP 端 service 层取的是已失效的 `$el.nodeId`,视图层按该 id 查映射表时,对应条目在元素移除时已被删除,读到 undefined 再取 `.$` 即抛错。H5 端直接取 `$el`,没有这层 nodeId 映射,因此不复现。
58
+
59
+ - 在 `upGetRect` 与 `upCreateIntersectionObserver` 中集中拦截,覆盖全部三十余处调用方,卸载后不再把组件交给原生查询
60
+ - 卸载后 `upGetRect` 返回零尺寸节点信息,`upCreateIntersectionObserver` 返回空观察器,均保持原有调用形态,不需要调用方改写
61
+ - 卸载标记由公共 mixin 的 `beforeUnmount` 同步置位,早于 Vue 内部异步置位的 `isUnmounted`;未套用公共 mixin 的组件退回 Vue 自身标记
62
+ - 守卫置于条件编译之外,确保 app-harmony 端同样生效
63
+ - 新增 `verify:unmounted-node-query` 回归校验,同时断言挂载期间必须正常发起查询、卸载后必须完全不发起,并逐平台校验守卫存在
64
+
65
+ 该问题在 tabs、sticky、subsection、waterfall、index-list 等所有依赖节点测量的组件上成因相同,本次为统一修复。
66
+
67
+ ## 3.8.115
68
+ feat: u-select 遮罩默认可见并支持点击遮罩关闭开关
69
+
70
+ - overlayOpacity 默认由 0.01 调整为 0.3,遮罩真正可见,仍比 up-overlay 自身的 0.5 更浅
71
+ - overlayOpacity 类型放宽为 String / Number,可直接以普通属性方式传值
72
+ - 展开状态下把触发区抬到遮罩之上,避免下拉面板亮着而它的锚点被压暗
73
+ - 新增 closeOnClickOverlay 属性(默认 true),与 u-popup 命名一致;此前点击遮罩关闭为硬编码行为,现在可显式禁用
74
+ - 遮罩标签统一为 up-overlay,并补充 verify:select-overlay 回归校验
75
+
76
+ nvue 端对 z-index 支持有限,若被忽略则触发区可能落在遮罩之下,该端建议按需关闭 overlay。
77
+
78
+ ## 3.8.114
79
+ fix: 修复 tabbar 中间按钮圆弧越界(#1052)
80
+
81
+ - 根据 tabbar 与中间按钮的实际位置计算圆弧裁剪高度
82
+ - 兼容文字、偏移、边框和动画场景,并补充回归校验
83
+
1
84
  ## 3.8.113(2026-08-27)
2
85
  fix: 修复启用uniUpRoot,在root组件存在components选项时组件插入错误
3
86
 
@@ -348,7 +348,7 @@
348
348
  overflow: hidden;
349
349
  height: 800rpx;
350
350
 
351
- >view {
351
+ >.u-flex {
352
352
  width: 150%;
353
353
  transition: transform 0.3s ease-in-out 0s;
354
354
  transform: translateX(0);
@@ -7,25 +7,25 @@
7
7
  <view class="oper-wrapper" :style="{ display: styleDisplay }">
8
8
  <view class="oper">
9
9
  <view class="btn-wrapper" v-if="showOper">
10
- <view @click="select" hover-class="hover" :style="{ width: btnWidth }">
10
+ <view class="u-cropper__action" @click="select" hover-class="hover" :style="{ width: btnWidth }">
11
11
  <text>{{ t("up.common.re-select") }}</text>
12
12
  </view>
13
- <view @click="close" hover-class="hover" :style="{ width: btnWidth }">
13
+ <view class="u-cropper__action" @click="close" hover-class="hover" :style="{ width: btnWidth }">
14
14
  <text>{{ t("up.common.close") }}</text>
15
15
  </view>
16
- <view @click="rotate" hover-class="hover" :style="{ width: btnWidth, display: btnDsp }">
16
+ <view class="u-cropper__action" @click="rotate" hover-class="hover" :style="{ width: btnWidth, display: btnDsp }">
17
17
  <text>{{ t("up.common.rotate") }}</text>
18
18
  </view>
19
- <view @click="preview" hover-class="hover" :style="{ width: btnWidth }">
19
+ <view class="u-cropper__action" @click="preview" hover-class="hover" :style="{ width: btnWidth }">
20
20
  <text>{{ t("up.common.preview") }}</text>
21
21
  </view>
22
- <view @click="confirm" hover-class="hover" :style="{ width: btnWidth }">
22
+ <view class="u-cropper__action" @click="confirm" hover-class="hover" :style="{ width: btnWidth }">
23
23
  <text>{{ t("up.common.confirm") }}</text>
24
24
  </view>
25
25
  </view>
26
26
  <view class="clr-wrapper" v-else>
27
27
  <slider class="my-slider" @change="colorChange" block-size="25" value="0" min="-100" max="100" activeColor="red" backgroundColor="green" block-color="grey" show-value></slider>
28
- <view @click="prvUpload" hover-class="hover" :style="{ width: btnWidth }">
28
+ <view class="u-cropper__action" @click="prvUpload" hover-class="hover" :style="{ width: btnWidth }">
29
29
  <text>{{ t("up.common.confirm") }}</text>
30
30
  </view>
31
31
  </view>
@@ -1267,7 +1267,7 @@
1267
1267
  justify-content: space-between;
1268
1268
  }
1269
1269
 
1270
- .btn-wrapper view {
1270
+ .btn-wrapper .u-cropper__action {
1271
1271
  display: flex;
1272
1272
  align-items: center;
1273
1273
  justify-content: center;
@@ -1288,7 +1288,7 @@
1288
1288
  flex-grow: 1;
1289
1289
  }
1290
1290
 
1291
- .clr-wrapper view {
1291
+ .clr-wrapper .u-cropper__action {
1292
1292
  display: flex;
1293
1293
  align-items: center;
1294
1294
  justify-content: center;
@@ -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)}`
@@ -159,7 +159,7 @@ export default {
159
159
  bottom: 0px;
160
160
  display: flex;
161
161
  flex-direction: column;
162
- >view {
162
+ >.u-float-button__item {
163
163
  margin: 5px 0px;
164
164
  }
165
165
  }
@@ -16,7 +16,7 @@
16
16
  <view class="up-goods-sku__header">
17
17
  <slot name="header">
18
18
  <view class="up-goods-sku__header__image">
19
- <image :src="goodsInfo.image || goodsInfo.picture" mode="aspectFill"></image>
19
+ <image class="up-goods-sku__header__image-element" :src="goodsInfo.image || goodsInfo.picture" mode="aspectFill"></image>
20
20
  </view>
21
21
  <view class="up-goods-sku__header__info">
22
22
  <view class="up-goods-sku__header__info__price">
@@ -333,7 +333,7 @@
333
333
  overflow: hidden;
334
334
  margin-right: 20rpx;
335
335
 
336
- image {
336
+ &-element {
337
337
  width: 100%;
338
338
  height: 100%;
339
339
  }
@@ -49,6 +49,10 @@
49
49
  watch: {
50
50
  // #ifndef APP-NVUE
51
51
  'uList.innerScrollTop'(n) {
52
+ // 确保parent已正确初始化,避免在mounted之前触发watch导致报错
53
+ if (!this.parent || !this.parent.updateOffsetFromChild) {
54
+ return
55
+ }
52
56
  const preLoadScreen = this.uList.preLoadScreen
53
57
  const windowHeight = this.sys.windowHeight
54
58
  if(n <= windowHeight * preLoadScreen) {
@@ -1,3 +1,16 @@
1
+ /* #ifdef MP-WEIXIN */
2
+ :host {
3
+ --up-navbar-bg-color: #ffffff;
4
+ }
5
+
6
+ @media (prefers-color-scheme: dark) {
7
+ :host {
8
+ --up-navbar-bg-color: #1c1c1e;
9
+ }
10
+ }
11
+ /* #endif */
12
+
13
+ /* #ifndef MP-WEIXIN */
1
14
  :root,
2
15
  page,
3
16
  body {
@@ -24,3 +37,4 @@ body {
24
37
  --up-navbar-bg-color: #1c1c1e;
25
38
  --up-navbar-glass-bg-color: rgba(28, 28, 30, 0.82);
26
39
  }
40
+ /* #endif */
@@ -1,3 +1,18 @@
1
+ /* #ifdef MP-WEIXIN */
2
+ :host {
3
+ --up-notice-bar-color: #f9ae3d;
4
+ --up-notice-bar-bg-color: #fdf6ec;
5
+ }
6
+
7
+ @media (prefers-color-scheme: dark) {
8
+ :host {
9
+ --up-notice-bar-color: #f9ae3d;
10
+ --up-notice-bar-bg-color: #3d2f1b;
11
+ }
12
+ }
13
+ /* #endif */
14
+
15
+ /* #ifndef MP-WEIXIN */
1
16
  :root,
2
17
  page,
3
18
  body {
@@ -23,3 +38,4 @@ body {
23
38
  --up-notice-bar-color: #f9ae3d;
24
39
  --up-notice-bar-bg-color: #3d2f1b;
25
40
  }
41
+ /* #endif */
@@ -53,14 +53,14 @@
53
53
  <!-- #endif -->
54
54
  <view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
55
55
  <node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
56
- <view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
56
+ <view v-else v-for="(tbody, x) in (n && n.children || [])" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
57
57
  <node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
58
- <block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
58
+ <block v-else v-for="(tr, y) in (tbody && tbody.children || [])" v-bind:key="y">
59
59
  <view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
60
60
  <node :childs="tr.children" :opts="opts" />
61
61
  </view>
62
62
  <view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
63
- <view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
63
+ <view v-for="(td, z) in (tr && tr.children || [])" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
64
64
  <node :childs="td.children" :opts="opts" />
65
65
  </view>
66
66
  </view>
@@ -70,7 +70,7 @@
70
70
  <!-- insert -->
71
71
  <!-- 富文本 -->
72
72
  <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
73
- <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
73
+ <rich-text v-else-if="!n.c&&!isInlineTag(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
74
74
  <!-- #endif -->
75
75
  <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
76
76
  <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
@@ -185,6 +185,25 @@ export default {
185
185
  // #ifdef MP-WEIXIN
186
186
  toJSON () { return this },
187
187
  // #endif
188
+ isInlineTag (tagName, style) {
189
+ return {
190
+ abbr: true,
191
+ b: true,
192
+ big: true,
193
+ code: true,
194
+ del: true,
195
+ em: true,
196
+ i: true,
197
+ ins: true,
198
+ label: true,
199
+ q: true,
200
+ small: true,
201
+ span: true,
202
+ strong: true,
203
+ sub: true,
204
+ sup: true
205
+ }[tagName] || (style || '').indexOf('display:inline') !== -1
206
+ },
188
207
  /**
189
208
  * @description 规范化链接地址
190
209
  * @param {any} href
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <view class="u-select">
3
3
  <view :class="['u-select__content', disabled && 'disabled']">
4
- <view :class="['u-select__label', border && 'u-select__label--border']" :style="selectLabelStyle" @click="openSelect">
4
+ <view :class="['u-select__label', border && 'u-select__label--border']" :style="selectLabelStyle" @click="labelClick">
5
5
  <slot name="text" :currentLabel="currentLabel">
6
6
  <text class="u-select__text" :style="{ color: resolvedTextColor }" v-if="showOptionsLabel">
7
7
  {{ currentLabel }}
@@ -14,8 +14,8 @@
14
14
  <up-icon name="arrow-down" :size="iconSize" :color="resolvedIconColor"></up-icon>
15
15
  </slot>
16
16
  </view>
17
- <u-overlay :show="isOpen" @click="overlayClick" v-if="overlay" :zIndex="zIndex" :duration="duration + 50"
18
- :customStyle="overlayStyle" :opacity="overlayOpacity" @touchmove.stop.prevent="noop"></u-overlay>
17
+ <up-overlay :show="isOpen" @click="overlayClick" v-if="overlay" :zIndex="zIndex" :duration="duration + 50"
18
+ :customStyle="overlayStyle" :opacity="overlayOpacity" @touchmove.stop.prevent="noop"></up-overlay>
19
19
  <view class="u-select__options__wrap" :style="optionsWrapStyle">
20
20
  <view class="u-select__options" :style="optionsStyle" v-if="isOpen">
21
21
  <slot name="options">
@@ -59,8 +59,8 @@
59
59
  default: true
60
60
  },
61
61
  overlayOpacity: {
62
- type: Number,
63
- default: 0.01
62
+ type: [String, Number],
63
+ default: 0.3
64
64
  },
65
65
  overlayStyle: {
66
66
  type: Object,
@@ -68,6 +68,11 @@
68
68
  return {}
69
69
  }
70
70
  },
71
+ // 点击遮罩是否关闭下拉面板
72
+ closeOnClickOverlay: {
73
+ type: Boolean,
74
+ default: true
75
+ },
71
76
  duration: {
72
77
  type: Number,
73
78
  default: 300
@@ -157,11 +162,17 @@
157
162
  return this.optionsWidth;
158
163
  },
159
164
  selectLabelStyle() {
160
- if (!this.border) return {};
161
- return {
162
- borderColor: this.upThemeVar('--up-border-color', '#dadbde'),
163
- backgroundColor: this.upThemeVar('--up-card-bg-color', '#ffffff')
164
- };
165
+ const style = {};
166
+ if (this.border) {
167
+ style.borderColor = this.upThemeVar('--up-border-color', '#dadbde');
168
+ style.backgroundColor = this.upThemeVar('--up-card-bg-color', '#ffffff');
169
+ }
170
+ // 遮罩可见时把触发区抬到遮罩之上,避免下拉面板亮着而它的锚点被压暗
171
+ if (this.isOpen && this.overlay) {
172
+ style.position = 'relative';
173
+ style.zIndex = this.zIndex + 1;
174
+ }
175
+ return style;
165
176
  },
166
177
  optionsWrapStyle() {
167
178
  const style = {
@@ -195,6 +206,14 @@
195
206
  }
196
207
  },
197
208
  methods: {
209
+ labelClick() {
210
+ // 触发区被抬到遮罩之上后,遮罩不再代收这次点击,需要自己处理收起
211
+ if (this.isOpen) {
212
+ this.closeSelect();
213
+ return;
214
+ }
215
+ this.openSelect();
216
+ },
198
217
  openSelect() {
199
218
  if (this.disabled) return;
200
219
  this.isOpen = true;
@@ -208,7 +227,8 @@
208
227
  this.isOpen = false;
209
228
  },
210
229
  overlayClick() {
211
- this.isOpen = false;
230
+ if (!this.closeOnClickOverlay) return;
231
+ this.closeSelect();
212
232
  },
213
233
  selectItem(item) {
214
234
  this.isOpen = false;
@@ -1,3 +1,26 @@
1
+ /* #ifdef MP-WEIXIN */
2
+ :host {
3
+ --up-subsection-bg-color: #eeeeef;
4
+ --up-subsection-bar-color: #ffffff;
5
+ --up-subsection-inactive-color: #303133;
6
+ --up-subsection-disabled-text-color: #c8c9cc;
7
+ --up-subsection-disabled-border-color: #d4d4d4;
8
+ --up-subsection-disabled-bar-color: #f5f5f5;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :host {
13
+ --up-subsection-bg-color: #2b2c30;
14
+ --up-subsection-bar-color: #3a3b40;
15
+ --up-subsection-inactive-color: #d1d5db;
16
+ --up-subsection-disabled-text-color: #6b7280;
17
+ --up-subsection-disabled-border-color: #3a3a3c;
18
+ --up-subsection-disabled-bar-color: #3a3a3c;
19
+ }
20
+ }
21
+ /* #endif */
22
+
23
+ /* #ifndef MP-WEIXIN */
1
24
  :root,
2
25
  page,
3
26
  body {
@@ -39,3 +62,4 @@ body {
39
62
  --up-subsection-disabled-border-color: #3a3a3c;
40
63
  --up-subsection-disabled-bar-color: #3a3a3c;
41
64
  }
65
+ /* #endif */
@@ -1,3 +1,26 @@
1
+ /* #ifdef MP-WEIXIN */
2
+ :host {
3
+ --up-switch-active-color: #3c9cff;
4
+ --up-switch-inactive-color: #ffffff;
5
+ --up-switch-dot-active-color: #ffffff;
6
+ --up-switch-dot-inactive-color: #ffffff;
7
+ --up-switch-loading-inactive-color: #aaabad;
8
+ --up-switch-border-color: rgba(0, 0, 0, 0.12);
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :host {
13
+ --up-switch-active-color: #3c9cff;
14
+ --up-switch-inactive-color: #3a3a3c;
15
+ --up-switch-dot-active-color: #ffffff;
16
+ --up-switch-dot-inactive-color: #d1d5db;
17
+ --up-switch-loading-inactive-color: #9ca3af;
18
+ --up-switch-border-color: #4b5563;
19
+ }
20
+ }
21
+ /* #endif */
22
+
23
+ /* #ifndef MP-WEIXIN */
1
24
  :root,
2
25
  page,
3
26
  body {
@@ -39,3 +62,4 @@ body {
39
62
  --up-switch-loading-inactive-color: #9ca3af;
40
63
  --up-switch-border-color: #4b5563;
41
64
  }
65
+ /* #endif */
@@ -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,9 +105,14 @@
99
105
  this.itemShape,
100
106
  this.iconScale,
101
107
  this.textMode,
102
- this.borderColor
108
+ this.borderColor,
109
+ this.border,
110
+ this.customStyle
103
111
  ]
104
112
  },
113
+ midButtonBorderTopOffset() {
114
+ return this.border ? 0.25 : 0
115
+ },
105
116
  updatePlaceholder() {
106
117
  return [this.fixed, this.placeholder]
107
118
  }
@@ -0,0 +1,19 @@
1
+ const MID_BUTTON_SIZE = 64
2
+
3
+ export function clampMidButtonBorderClipHeight(value) {
4
+ const height = Number(value)
5
+ if (!Number.isFinite(height)) return 0
6
+ return Math.min(Math.max(height, 0), MID_BUTTON_SIZE)
7
+ }
8
+
9
+ export function calculateMidButtonBorderClipHeight({
10
+ contentTop,
11
+ circleTop,
12
+ borderTopOffset = 0
13
+ }) {
14
+ const targetTop = Number(contentTop)
15
+ const currentTop = Number(circleTop)
16
+ const borderOffset = Number(borderTopOffset)
17
+ if (![targetTop, currentTop, borderOffset].every(Number.isFinite)) return 0
18
+ return clampMidButtonBorderClipHeight(targetTop + borderOffset - currentTop)
19
+ }