uview-plus 3.8.113 → 3.8.117
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 +56 -0
- package/components/u-cascader/u-cascader.vue +1 -1
- package/components/u-cropper/u-cropper.vue +8 -8
- package/components/u-float-button/u-float-button.vue +1 -1
- package/components/u-goods-sku/u-goods-sku.vue +2 -2
- package/components/u-list-item/u-list-item.vue +4 -0
- package/components/u-navbar/theme-vars.scss +14 -0
- package/components/u-notice-bar/theme-vars.scss +16 -0
- package/components/u-parse/node/node.vue +23 -4
- package/components/u-select/u-select.vue +31 -11
- package/components/u-subsection/theme-vars.scss +24 -0
- package/components/u-switch/theme-vars.scss +24 -0
- package/components/u-tabbar/u-tabbar.vue +6 -1
- package/components/u-tabbar-item/midButtonGeometry.js +19 -0
- package/components/u-tabbar-item/u-tabbar-item.vue +118 -41
- package/components/u-tag/theme-vars.scss +14 -0
- package/components/u-transition/u-transition.vue +0 -1
- package/components/u-waterfall/u-waterfall.vue +168 -162
- package/libs/function/index.js +33 -0
- package/libs/mixin/mixin.js +3 -0
- package/package.json +2 -2
package/changelog.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
## 3.8.117
|
|
2
|
+
fix: 修复瀑布流分发锁死、u-image 置于 u-swiper 内无法滑动、u-list-item 挂载竞态
|
|
3
|
+
|
|
4
|
+
本版为三个问题的集中修复,均不涉及组件 API 变更。
|
|
5
|
+
|
|
6
|
+
fix: 修复 u-waterfall 在 App 切换 tabbar 后不再消费新数据(#1056)
|
|
7
|
+
|
|
8
|
+
App 端切换 tabbar 时,原生层会挂起隐藏页面的 setTimeout,`await sleep()` 永不 resolve,`runDistributionQueue` 的 finally 块无法执行,`distributionRunning` 永久为 true;切回页面后新数据只能入队,永远不会被分发。
|
|
9
|
+
|
|
10
|
+
- `clear()` 强制重置 `distributionRunning` 与 `distributionPromise`
|
|
11
|
+
- `redistributeData()` 在调用 clear 之前先解锁
|
|
12
|
+
- 新增 `verify:waterfall-lock-reset` 回归校验
|
|
13
|
+
|
|
14
|
+
fix: 修复 u-image 置于 u-swiper 内导致手势滑动失效(#894)
|
|
15
|
+
|
|
16
|
+
u-transition 根节点绑定的 `@touchmove="noop"` 会执行 `e.stopPropagation()`,使 touchmove 无法冒泡到父级;u-image 以 u-transition 作为根节点,放进 u-swiper 的 slot 后 swiper 收不到 touchmove,滑动手势失效。该拦截对淡入、滑入等过渡动画本身没有作用,仅会阻断父组件的正常手势,故直接移除。
|
|
17
|
+
|
|
18
|
+
fix: 修复 u-list-item 在 parent 未初始化时调用 updateOffsetFromChild 报错(#875)
|
|
19
|
+
|
|
20
|
+
列表数据由后台异步返回、u-list-item 数量随之变化时,`uList.innerScrollTop` 的 watch 可能在 `mounted()` 之前触发;此时 `this.parent` 仍是 `created()` 中赋值的空对象,调用 `this.parent.updateOffsetFromChild` 会抛出 "is not a function"。
|
|
21
|
+
|
|
22
|
+
- watch 回调中增加守卫,parent 未就绪时直接返回
|
|
23
|
+
- 新增 `verify:list-item-parent-race` 回归校验
|
|
24
|
+
|
|
25
|
+
## 3.8.116
|
|
26
|
+
fix: 修复组件卸载后查询节点导致 APP 与鸿蒙端报错(#1057)
|
|
27
|
+
|
|
28
|
+
页面切换时 APP 与鸿蒙端会出现大面积 `Uncaught TypeError: Cannot read properties of undefined (reading '$') at uni-app-view.umd.js`,功能不受影响但日志被淹没,H5 端不出现。
|
|
29
|
+
|
|
30
|
+
原因是组件卸载后仍在执行的异步回调继续发起节点查询:APP 端 service 层取的是已失效的 `$el.nodeId`,视图层按该 id 查映射表时,对应条目在元素移除时已被删除,读到 undefined 再取 `.$` 即抛错。H5 端直接取 `$el`,没有这层 nodeId 映射,因此不复现。
|
|
31
|
+
|
|
32
|
+
- 在 `upGetRect` 与 `upCreateIntersectionObserver` 中集中拦截,覆盖全部三十余处调用方,卸载后不再把组件交给原生查询
|
|
33
|
+
- 卸载后 `upGetRect` 返回零尺寸节点信息,`upCreateIntersectionObserver` 返回空观察器,均保持原有调用形态,不需要调用方改写
|
|
34
|
+
- 卸载标记由公共 mixin 的 `beforeUnmount` 同步置位,早于 Vue 内部异步置位的 `isUnmounted`;未套用公共 mixin 的组件退回 Vue 自身标记
|
|
35
|
+
- 守卫置于条件编译之外,确保 app-harmony 端同样生效
|
|
36
|
+
- 新增 `verify:unmounted-node-query` 回归校验,同时断言挂载期间必须正常发起查询、卸载后必须完全不发起,并逐平台校验守卫存在
|
|
37
|
+
|
|
38
|
+
该问题在 tabs、sticky、subsection、waterfall、index-list 等所有依赖节点测量的组件上成因相同,本次为统一修复。
|
|
39
|
+
|
|
40
|
+
## 3.8.115
|
|
41
|
+
feat: u-select 遮罩默认可见并支持点击遮罩关闭开关
|
|
42
|
+
|
|
43
|
+
- overlayOpacity 默认由 0.01 调整为 0.3,遮罩真正可见,仍比 up-overlay 自身的 0.5 更浅
|
|
44
|
+
- overlayOpacity 类型放宽为 String / Number,可直接以普通属性方式传值
|
|
45
|
+
- 展开状态下把触发区抬到遮罩之上,避免下拉面板亮着而它的锚点被压暗
|
|
46
|
+
- 新增 closeOnClickOverlay 属性(默认 true),与 u-popup 命名一致;此前点击遮罩关闭为硬编码行为,现在可显式禁用
|
|
47
|
+
- 遮罩标签统一为 up-overlay,并补充 verify:select-overlay 回归校验
|
|
48
|
+
|
|
49
|
+
nvue 端对 z-index 支持有限,若被忽略则触发区可能落在遮罩之下,该端建议按需关闭 overlay。
|
|
50
|
+
|
|
51
|
+
## 3.8.114
|
|
52
|
+
fix: 修复 tabbar 中间按钮圆弧越界(#1052)
|
|
53
|
+
|
|
54
|
+
- 根据 tabbar 与中间按钮的实际位置计算圆弧裁剪高度
|
|
55
|
+
- 兼容文字、偏移、边框和动画场景,并补充回归校验
|
|
56
|
+
|
|
1
57
|
## 3.8.113(2026-08-27)
|
|
2
58
|
fix: 修复启用uniUpRoot,在root组件存在components选项时组件插入错误
|
|
3
59
|
|
|
@@ -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
|
|
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
|
|
1291
|
+
.clr-wrapper .u-cropper__action {
|
|
1292
1292
|
display: flex;
|
|
1293
1293
|
align-items: center;
|
|
1294
1294
|
justify-content: center;
|
|
@@ -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
|
-
|
|
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&&!
|
|
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="
|
|
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
|
-
<
|
|
18
|
-
:customStyle="overlayStyle" :opacity="overlayOpacity" @touchmove.stop.prevent="noop"></
|
|
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.
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
borderColor
|
|
163
|
-
backgroundColor
|
|
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.
|
|
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 */
|
|
@@ -99,9 +99,14 @@
|
|
|
99
99
|
this.itemShape,
|
|
100
100
|
this.iconScale,
|
|
101
101
|
this.textMode,
|
|
102
|
-
this.borderColor
|
|
102
|
+
this.borderColor,
|
|
103
|
+
this.border,
|
|
104
|
+
this.customStyle
|
|
103
105
|
]
|
|
104
106
|
},
|
|
107
|
+
midButtonBorderTopOffset() {
|
|
108
|
+
return this.border ? 0.25 : 0
|
|
109
|
+
},
|
|
105
110
|
updatePlaceholder() {
|
|
106
111
|
return [this.fixed, this.placeholder]
|
|
107
112
|
}
|
|
@@ -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
|
+
}
|
|
@@ -8,38 +8,41 @@
|
|
|
8
8
|
<view class="u-tabbar-item__content" :class="contentClassNames">
|
|
9
9
|
<view class="u-tabbar-item__bubble" :class="bubbleClassNames">
|
|
10
10
|
<view
|
|
11
|
+
ref="u-tabbar-item__icon--mid-button"
|
|
11
12
|
class="u-tabbar-item__icon"
|
|
12
13
|
:class="iconClassNames"
|
|
13
14
|
>
|
|
14
|
-
<view v-if="isMidButton" class="u-tabbar-item__mid-button-border">
|
|
15
|
+
<view v-if="isMidButton" class="u-tabbar-item__mid-button-border" :style="midButtonBorderStyle">
|
|
15
16
|
<view class="u-tabbar-item__mid-button-border-circle" :style="midButtonBorderCircleStyle"></view>
|
|
16
17
|
</view>
|
|
17
18
|
<view v-if="isMidButton" class="u-tabbar-item__mid-button-inner"></view>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
19
|
+
<view class="u-tabbar-item__icon-content" :class="iconContentClassNames">
|
|
20
|
+
<up-icon
|
|
21
|
+
v-if="resolvedIconName"
|
|
22
|
+
:name="resolvedIconName"
|
|
23
|
+
:color="isMidButton ? resolvedMidButtonIconColor : (isActive ? resolvedActiveColor : resolvedInactiveColor)"
|
|
24
|
+
:size="isMidButton ? midButtonIconSize : 24"
|
|
25
|
+
:customStyle="midButtonIconStyle"
|
|
26
|
+
></up-icon>
|
|
27
|
+
<template v-else>
|
|
28
|
+
<slot
|
|
29
|
+
v-if="isActive"
|
|
30
|
+
name="active-icon"
|
|
31
|
+
/>
|
|
32
|
+
<slot
|
|
33
|
+
v-else
|
|
34
|
+
name="inactive-icon"
|
|
35
|
+
/>
|
|
36
|
+
</template>
|
|
37
|
+
<u-badge
|
|
38
|
+
absolute
|
|
39
|
+
:offset="[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']"
|
|
40
|
+
:customStyle="badgeStyle"
|
|
41
|
+
:isDot="dot"
|
|
42
|
+
:value="badge || (dot ? 1 : null)"
|
|
43
|
+
:show="dot || badge > 0"
|
|
44
|
+
></u-badge>
|
|
45
|
+
</view>
|
|
43
46
|
</view>
|
|
44
47
|
|
|
45
48
|
<slot name="text">
|
|
@@ -62,7 +65,8 @@
|
|
|
62
65
|
import { props } from './props';
|
|
63
66
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
|
64
67
|
import { mixin } from '../../libs/mixin/mixin';
|
|
65
|
-
import { addStyle, error } from '../../libs/function/index';
|
|
68
|
+
import { addStyle, error, sleep } from '../../libs/function/index';
|
|
69
|
+
import { calculateMidButtonBorderClipHeight } from './midButtonGeometry';
|
|
66
70
|
/**
|
|
67
71
|
* TabbarItem 底部导航栏子组件
|
|
68
72
|
* @description 此组件提供了自定义tabbar的能力。
|
|
@@ -73,6 +77,13 @@
|
|
|
73
77
|
* @property {Boolean} dot 是否显示圆点,将会覆盖badge参数(默认 false )
|
|
74
78
|
* @property {String} text 描述文本
|
|
75
79
|
* @property {Object | String} badgeStyle 控制徽标的位置,对象或者字符串形式,可以设置top和right属性(默认 'top: 6px;right:2px;' )
|
|
80
|
+
* @property {String} mode 模式,默认普通模式,midButton中间按钮模式
|
|
81
|
+
* @property {String} midButtonBgColor 中间按钮背景色
|
|
82
|
+
* @property {String} midButtonIconColor 中间按钮图标颜色
|
|
83
|
+
* @property {String | Number} midButtonIconSize 中间按钮图标大小(默认 26 )
|
|
84
|
+
* @property {String} midButtonBoxShadow 中间按钮外层阴影
|
|
85
|
+
* @property {String} midButtonInnerBoxShadow 中间按钮内层阴影
|
|
86
|
+
* @property {String | Number} midButtonOffsetY 中间按钮垂直偏移,负值为上移(默认 -10 )
|
|
76
87
|
* @property {Object} customStyle 定义需要用到的外部样式
|
|
77
88
|
*
|
|
78
89
|
* @example <u-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><u-tabbar-item text="首页" icon="home" dot ></u-tabbar-item></u-tabbar>
|
|
@@ -83,6 +94,7 @@
|
|
|
83
94
|
data() {
|
|
84
95
|
return {
|
|
85
96
|
isActive: false, // 是否处于激活状态
|
|
97
|
+
midButtonBorderClipHeightValue: 0,
|
|
86
98
|
parentData: {
|
|
87
99
|
value: null,
|
|
88
100
|
activeColor: '',
|
|
@@ -94,7 +106,9 @@
|
|
|
94
106
|
itemShape: 'default',
|
|
95
107
|
iconScale: 1.1,
|
|
96
108
|
textMode: 'always',
|
|
97
|
-
borderColor: ''
|
|
109
|
+
borderColor: '',
|
|
110
|
+
border: true,
|
|
111
|
+
midButtonBorderTopOffset: 0.25
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
},
|
|
@@ -154,9 +168,12 @@
|
|
|
154
168
|
return `${this.resolvedMidButtonOffsetY}px`
|
|
155
169
|
},
|
|
156
170
|
midButtonBorderClipHeight() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
171
|
+
return `${this.midButtonBorderClipHeightValue}px`
|
|
172
|
+
},
|
|
173
|
+
midButtonBorderStyle() {
|
|
174
|
+
return {
|
|
175
|
+
height: this.midButtonBorderClipHeight
|
|
176
|
+
}
|
|
160
177
|
},
|
|
161
178
|
midButtonBorderCircleStyle() {
|
|
162
179
|
return this.isMidButton && this.parentData.borderColor
|
|
@@ -180,7 +197,15 @@
|
|
|
180
197
|
return [
|
|
181
198
|
this.isMidButton ? 'u-tabbar-item__icon--mid-button' : '',
|
|
182
199
|
`u-tabbar-item__icon--${this.resolvedStyleType}`,
|
|
183
|
-
this.isActive && this.resolvedAnimationType !== 'none' ? `u-tabbar-item__icon--anim-${this.resolvedAnimationType}` : ''
|
|
200
|
+
this.isActive && this.resolvedAnimationType !== 'none' && !this.isMidButton ? `u-tabbar-item__icon--anim-${this.resolvedAnimationType}` : ''
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
iconContentClassNames() {
|
|
204
|
+
return [
|
|
205
|
+
this.isMidButton ? 'u-tabbar-item__icon-content--mid-button' : '',
|
|
206
|
+
this.isMidButton && this.isActive && this.resolvedAnimationType !== 'none'
|
|
207
|
+
? `u-tabbar-item__icon-content--anim-${this.resolvedAnimationType}`
|
|
208
|
+
: ''
|
|
184
209
|
]
|
|
185
210
|
},
|
|
186
211
|
contentClassNames() {
|
|
@@ -213,6 +238,20 @@
|
|
|
213
238
|
created() {
|
|
214
239
|
this.init()
|
|
215
240
|
},
|
|
241
|
+
mounted() {
|
|
242
|
+
this.scheduleMidButtonBorderMeasure()
|
|
243
|
+
},
|
|
244
|
+
watch: {
|
|
245
|
+
mode() {
|
|
246
|
+
this.scheduleMidButtonBorderMeasure()
|
|
247
|
+
},
|
|
248
|
+
text() {
|
|
249
|
+
this.scheduleMidButtonBorderMeasure()
|
|
250
|
+
},
|
|
251
|
+
midButtonOffsetY() {
|
|
252
|
+
this.scheduleMidButtonBorderMeasure()
|
|
253
|
+
}
|
|
254
|
+
},
|
|
216
255
|
emits: ["click", "change"],
|
|
217
256
|
methods: {
|
|
218
257
|
addStyle,
|
|
@@ -235,6 +274,28 @@
|
|
|
235
274
|
updateFromParent() {
|
|
236
275
|
// 重新初始化
|
|
237
276
|
this.init()
|
|
277
|
+
this.scheduleMidButtonBorderMeasure()
|
|
278
|
+
},
|
|
279
|
+
scheduleMidButtonBorderMeasure() {
|
|
280
|
+
if (!this.isMidButton) return
|
|
281
|
+
this.$nextTick(() => this.updateMidButtonBorderClip())
|
|
282
|
+
},
|
|
283
|
+
async updateMidButtonBorderClip() {
|
|
284
|
+
if (!this.isMidButton || !this.parent) return
|
|
285
|
+
await sleep(20)
|
|
286
|
+
const [contentRect, circleRect] = await Promise.all([
|
|
287
|
+
this.parent.$uGetRect('.u-tabbar__content'),
|
|
288
|
+
this.$uGetRect('.u-tabbar-item__icon--mid-button')
|
|
289
|
+
])
|
|
290
|
+
if ([contentRect, circleRect].some(rect => !rect || !Number.isFinite(rect.top) || rect.height <= 0)) return
|
|
291
|
+
const clipHeight = calculateMidButtonBorderClipHeight({
|
|
292
|
+
contentTop: contentRect.top,
|
|
293
|
+
circleTop: circleRect.top,
|
|
294
|
+
borderTopOffset: this.parentData.border ? this.parentData.midButtonBorderTopOffset : 0
|
|
295
|
+
})
|
|
296
|
+
if (clipHeight !== this.midButtonBorderClipHeightValue) {
|
|
297
|
+
this.midButtonBorderClipHeightValue = clipHeight
|
|
298
|
+
}
|
|
238
299
|
},
|
|
239
300
|
clickHandler() {
|
|
240
301
|
this.$nextTick(() => {
|
|
@@ -281,6 +342,19 @@
|
|
|
281
342
|
width: 100%;
|
|
282
343
|
}
|
|
283
344
|
|
|
345
|
+
&-content {
|
|
346
|
+
@include flex;
|
|
347
|
+
align-items: center;
|
|
348
|
+
justify-content: center;
|
|
349
|
+
|
|
350
|
+
&--mid-button {
|
|
351
|
+
position: relative;
|
|
352
|
+
z-index: 2;
|
|
353
|
+
width: 100%;
|
|
354
|
+
height: 100%;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
284
358
|
&--anim-scale {
|
|
285
359
|
transform: scale(var(--up-tabbar-icon-scale, 1.1));
|
|
286
360
|
}
|
|
@@ -402,7 +476,7 @@
|
|
|
402
476
|
transform: none;
|
|
403
477
|
}
|
|
404
478
|
|
|
405
|
-
.u-tabbar-item--lift.u-tabbar-item--active .u-tabbar-item__icon {
|
|
479
|
+
.u-tabbar-item--lift.u-tabbar-item--active .u-tabbar-item__icon:not(.u-tabbar-item__icon--mid-button) {
|
|
406
480
|
transform: translateY(-6rpx) scale(1.04);
|
|
407
481
|
}
|
|
408
482
|
|
|
@@ -439,7 +513,7 @@
|
|
|
439
513
|
left: 0;
|
|
440
514
|
top: 0;
|
|
441
515
|
width: 64px;
|
|
442
|
-
height:
|
|
516
|
+
height: 0;
|
|
443
517
|
box-sizing: border-box;
|
|
444
518
|
background: transparent;
|
|
445
519
|
overflow: hidden;
|
|
@@ -447,15 +521,17 @@
|
|
|
447
521
|
z-index: 0;
|
|
448
522
|
}
|
|
449
523
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
height: 15.5px;
|
|
524
|
+
.u-tabbar-item__icon-content--anim-scale {
|
|
525
|
+
transform: scale(var(--up-tabbar-icon-scale, 1.1));
|
|
453
526
|
}
|
|
454
527
|
|
|
455
|
-
.u-tabbar-
|
|
456
|
-
|
|
528
|
+
.u-tabbar-item__icon-content--anim-lift {
|
|
529
|
+
transform: translateY(-6rpx) scale(var(--up-tabbar-icon-scale, 1.08));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.u-tabbar-item__icon-content--anim-swing {
|
|
533
|
+
transform: rotate(-10deg) scale(var(--up-tabbar-icon-scale, 1.08));
|
|
457
534
|
}
|
|
458
|
-
/* #endif */
|
|
459
535
|
|
|
460
536
|
.u-tabbar-item__mid-button-border-circle {
|
|
461
537
|
width: 64px;
|
|
@@ -497,7 +573,8 @@
|
|
|
497
573
|
transform: translateY(-4rpx);
|
|
498
574
|
}
|
|
499
575
|
|
|
500
|
-
.u-tabbar-item--anim-pulse .u-tabbar-item__icon
|
|
576
|
+
.u-tabbar-item--anim-pulse .u-tabbar-item__icon:not(.u-tabbar-item__icon--mid-button),
|
|
577
|
+
.u-tabbar-item__icon-content--anim-pulse {
|
|
501
578
|
animation: u-tabbar-item-pulse 1.4s ease-in-out infinite;
|
|
502
579
|
}
|
|
503
580
|
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/* #ifdef MP-WEIXIN */
|
|
2
|
+
:host {
|
|
3
|
+
--up-tag-close-bg-color: #c8c9cc;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@media (prefers-color-scheme: dark) {
|
|
7
|
+
:host {
|
|
8
|
+
--up-tag-close-bg-color: #4b5563;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/* #endif */
|
|
12
|
+
|
|
13
|
+
/* #ifndef MP-WEIXIN */
|
|
1
14
|
:root,
|
|
2
15
|
page,
|
|
3
16
|
body {
|
|
@@ -19,3 +32,4 @@ body {
|
|
|
19
32
|
[data-up-theme='dark'] {
|
|
20
33
|
--up-tag-close-bg-color: #4b5563;
|
|
21
34
|
}
|
|
35
|
+
/* #endif */
|
|
@@ -92,14 +92,14 @@
|
|
|
92
92
|
return {
|
|
93
93
|
columnList: [[]], // 存储每列的数据
|
|
94
94
|
children: [],
|
|
95
|
-
// 用于标记是否已经初始化
|
|
96
|
-
initialized: false,
|
|
97
|
-
windowWidth: 375,
|
|
98
|
-
windowHeight: 0,
|
|
99
|
-
distributionQueue: [],
|
|
100
|
-
distributionRunning: false,
|
|
101
|
-
distributionPromise: null,
|
|
102
|
-
distributionGeneration: 0
|
|
95
|
+
// 用于标记是否已经初始化
|
|
96
|
+
initialized: false,
|
|
97
|
+
windowWidth: 375,
|
|
98
|
+
windowHeight: 0,
|
|
99
|
+
distributionQueue: [],
|
|
100
|
+
distributionRunning: false,
|
|
101
|
+
distributionPromise: null,
|
|
102
|
+
distributionGeneration: 0
|
|
103
103
|
}
|
|
104
104
|
},
|
|
105
105
|
watch: {
|
|
@@ -111,27 +111,27 @@
|
|
|
111
111
|
if (this.columnList.length == 1) {
|
|
112
112
|
this.initColumnList()
|
|
113
113
|
}
|
|
114
|
-
if (!this.isPureAppend(nVal, oVal)) {
|
|
115
|
-
this.redistributeData(nVal);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
// 取差值,即这一次数组变化新增的部分
|
|
119
|
-
const startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
|
|
120
|
-
// 纯追加时只将新增数据加入共享分配队列
|
|
121
|
-
this.handleData(nVal.slice(startIndex));
|
|
114
|
+
if (!this.isPureAppend(nVal, oVal)) {
|
|
115
|
+
this.redistributeData(nVal);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// 取差值,即这一次数组变化新增的部分
|
|
119
|
+
const startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
|
|
120
|
+
// 纯追加时只将新增数据加入共享分配队列
|
|
121
|
+
this.handleData(nVal.slice(startIndex));
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
immediate: true
|
|
125
125
|
},
|
|
126
|
-
columns: {
|
|
127
|
-
handler() {
|
|
128
|
-
// 重新分配数据
|
|
129
|
-
if (this.copyFlowList.length > 0) {
|
|
130
|
-
this.redistributeData(this.copyFlowList);
|
|
131
|
-
} else {
|
|
132
|
-
this.clear(false);
|
|
133
|
-
}
|
|
134
|
-
},
|
|
126
|
+
columns: {
|
|
127
|
+
handler() {
|
|
128
|
+
// 重新分配数据
|
|
129
|
+
if (this.copyFlowList.length > 0) {
|
|
130
|
+
this.redistributeData(this.copyFlowList);
|
|
131
|
+
} else {
|
|
132
|
+
this.clear(false);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
135
|
immediate: false
|
|
136
136
|
}
|
|
137
137
|
},
|
|
@@ -219,144 +219,150 @@
|
|
|
219
219
|
const newColumnsCount = this.getColumnsCount();
|
|
220
220
|
const oldColumnsCount = this.columnList.length;
|
|
221
221
|
|
|
222
|
-
// 只有列数发生变化时才重新分配数据
|
|
223
|
-
if (newColumnsCount !== oldColumnsCount) {
|
|
224
|
-
this.redistributeData(this.copyFlowList);
|
|
225
|
-
}
|
|
226
|
-
}, 300);
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// 重新分配所有数据
|
|
230
|
-
redistributeData(data = this.copyFlowList) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
222
|
+
// 只有列数发生变化时才重新分配数据
|
|
223
|
+
if (newColumnsCount !== oldColumnsCount) {
|
|
224
|
+
this.redistributeData(this.copyFlowList);
|
|
225
|
+
}
|
|
226
|
+
}, 300);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// 重新分配所有数据
|
|
230
|
+
redistributeData(data = this.copyFlowList) {
|
|
231
|
+
// 强制重置锁状态,确保能重新开始分配
|
|
232
|
+
this.distributionRunning = false;
|
|
233
|
+
this.distributionPromise = null;
|
|
234
|
+
this.clear(false);
|
|
235
|
+
// 保存所有数据
|
|
236
|
+
const allData = this.cloneData(data || []);
|
|
237
|
+
// 重新分配数据
|
|
238
|
+
return this.handleData(allData);
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// 判断新数据是否只在原数组末尾追加
|
|
242
|
+
isPureAppend(newData, oldData) {
|
|
243
|
+
if (!Array.isArray(oldData) || oldData.length === 0) return true;
|
|
244
|
+
if (!Array.isArray(newData) || newData.length < oldData.length) return false;
|
|
245
|
+
return oldData.every((item, index) => {
|
|
246
|
+
return JSON.stringify(item) === JSON.stringify(newData[index]);
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
// 将新增数据加入共享队列,确保只有一个分配循环运行
|
|
251
|
+
handleData(newData) {
|
|
252
|
+
if (!newData || newData.length === 0) {
|
|
253
|
+
return this.distributionPromise || Promise.resolve();
|
|
254
|
+
}
|
|
255
|
+
this.distributionQueue.push({
|
|
256
|
+
generation: this.distributionGeneration,
|
|
257
|
+
data: this.cloneData(newData)
|
|
258
|
+
});
|
|
259
|
+
if (!this.distributionRunning) {
|
|
260
|
+
this.distributionPromise = this.runDistributionQueue();
|
|
261
|
+
}
|
|
262
|
+
return this.distributionPromise;
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// 串行消费所有待分配数据
|
|
266
|
+
async runDistributionQueue() {
|
|
267
|
+
if (this.distributionRunning) return;
|
|
268
|
+
this.distributionRunning = true;
|
|
269
|
+
try {
|
|
270
|
+
while (this.distributionQueue.length > 0) {
|
|
271
|
+
const task = this.distributionQueue.shift();
|
|
272
|
+
if (task.generation !== this.distributionGeneration) continue;
|
|
273
|
+
await this.distributeData(task.data, task.generation);
|
|
274
|
+
}
|
|
275
|
+
} finally {
|
|
276
|
+
this.distributionRunning = false;
|
|
277
|
+
this.distributionPromise = null;
|
|
278
|
+
if (this.distributionQueue.length > 0) {
|
|
279
|
+
this.distributionPromise = this.runDistributionQueue();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// 逐项测量并分配数据,代数变化时立即退出
|
|
285
|
+
async distributeData(newData, generation) {
|
|
286
|
+
let columnHeights = new Array(this.columnList.length).fill(0);
|
|
287
|
+
for (const item of newData) {
|
|
288
|
+
if (generation !== this.distributionGeneration) return;
|
|
289
|
+
columnHeights = await this.getColumnHeights();
|
|
290
|
+
if (generation !== this.distributionGeneration) return;
|
|
291
|
+
|
|
292
|
+
const minHeightIndex = this.getMinHeightColumnIndex(columnHeights);
|
|
293
|
+
this.columnList[minHeightIndex].push(item);
|
|
294
|
+
|
|
295
|
+
await sleep(this.addTime);
|
|
296
|
+
if (generation !== this.distributionGeneration) return;
|
|
297
|
+
await this.$nextTick();
|
|
298
|
+
if (generation !== this.distributionGeneration) return;
|
|
299
|
+
try {
|
|
300
|
+
const rect = await this.$uGetRect(`#u-column-${minHeightIndex}`);
|
|
301
|
+
if (generation !== this.distributionGeneration) return;
|
|
302
|
+
if (rect.height) {
|
|
303
|
+
columnHeights[minHeightIndex] = rect.height;
|
|
304
|
+
this.$emit('after-add-one', {
|
|
305
|
+
...item,
|
|
306
|
+
height: rect.height
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {
|
|
310
|
+
// 获取不到列高时,下一个元素会重新测量所有列
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (generation !== this.distributionGeneration) return;
|
|
314
|
+
this.$emit('after-add-all', {
|
|
315
|
+
columnHeights: columnHeights,
|
|
316
|
+
newData: newData
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// 获取当前所有列的真实高度
|
|
321
|
+
async getColumnHeights() {
|
|
322
|
+
const columnHeights = new Array(this.columnList.length).fill(0);
|
|
323
|
+
for (let i = 0; i < this.columnList.length; i++) {
|
|
324
|
+
try {
|
|
325
|
+
const rect = await this.$uGetRect(`#u-column-${i}`);
|
|
326
|
+
columnHeights[i] = rect.height || 0;
|
|
327
|
+
} catch (e) {
|
|
328
|
+
columnHeights[i] = 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return columnHeights;
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// 获取最短列;高度相同或暂不可测时按列数据量打散,避免全部落到第一列
|
|
335
|
+
getMinHeightColumnIndex(columnHeights) {
|
|
336
|
+
let minIndex = 0;
|
|
337
|
+
for (let i = 1; i < columnHeights.length; i++) {
|
|
338
|
+
const currentHeight = Number(columnHeights[i]) || 0;
|
|
339
|
+
const minHeight = Number(columnHeights[minIndex]) || 0;
|
|
340
|
+
if (currentHeight < minHeight) {
|
|
341
|
+
minIndex = i;
|
|
342
|
+
} else if (currentHeight === minHeight) {
|
|
343
|
+
const currentLength = this.columnList[i] ? this.columnList[i].length : 0;
|
|
344
|
+
const minLength = this.columnList[minIndex] ? this.columnList[minIndex].length : 0;
|
|
345
|
+
if (currentLength < minLength) {
|
|
346
|
+
minIndex = i;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return minIndex;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
// 复制而不是引用对象和数组
|
|
354
|
+
cloneData(data) {
|
|
355
|
+
return JSON.parse(JSON.stringify(data));
|
|
353
356
|
},
|
|
354
357
|
|
|
355
|
-
// 清空数据列表
|
|
356
|
-
clear(bak = true) {
|
|
357
|
-
this.distributionGeneration += 1;
|
|
358
|
-
this.distributionQueue.splice(0);
|
|
359
|
-
|
|
358
|
+
// 清空数据列表
|
|
359
|
+
clear(bak = true) {
|
|
360
|
+
this.distributionGeneration += 1;
|
|
361
|
+
this.distributionQueue.splice(0);
|
|
362
|
+
// 强制重置分配锁状态,避免页面隐藏导致的永久锁死
|
|
363
|
+
this.distributionRunning = false;
|
|
364
|
+
this.distributionPromise = null;
|
|
365
|
+
this.initColumnList();
|
|
360
366
|
// 同时清除父组件列表中的数据
|
|
361
367
|
if (bak) {
|
|
362
368
|
// #ifdef VUE2
|
|
@@ -465,4 +471,4 @@
|
|
|
465
471
|
max-width: 100%;
|
|
466
472
|
/* #endif */
|
|
467
473
|
}
|
|
468
|
-
</style>
|
|
474
|
+
</style>
|
package/libs/function/index.js
CHANGED
|
@@ -17,6 +17,20 @@ function emptyNodeInfo() {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// 组件卸载后仍把它交给原生查询,APP 端会拿已失效的 nodeId 去查视图层映射表,
|
|
21
|
+
// 读到 undefined 再取 .$ 就抛 Cannot read properties of undefined (reading '$')
|
|
22
|
+
function isCompUnmounted(comp) {
|
|
23
|
+
if (!comp) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
// 公共 mixin 的 beforeUnmount 里同步置位
|
|
27
|
+
if (comp.__upUnmounted) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
// 未应用公共 mixin 时退回 Vue 自身标记:公开实例挂在 $ 上,内部实例直接持有
|
|
31
|
+
return !!(comp.$ ? comp.$.isUnmounted : comp.isUnmounted)
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
// #ifdef APP-NVUE
|
|
21
35
|
const dom = (typeof uni !== 'undefined' && uni && typeof uni.requireNativePlugin === 'function')
|
|
22
36
|
? uni.requireNativePlugin('dom')
|
|
@@ -37,6 +51,10 @@ function getNvueRect(ref) {
|
|
|
37
51
|
|
|
38
52
|
// 查询节点信息
|
|
39
53
|
export function upGetRect(selector, all = false, comp = null) {
|
|
54
|
+
// 判定必须放在条件编译之外,否则 app-harmony 上整段会被裁掉
|
|
55
|
+
if (isCompUnmounted(comp)) {
|
|
56
|
+
return Promise.resolve(all ? [] : emptyNodeInfo())
|
|
57
|
+
}
|
|
40
58
|
return new Promise((resolve) => {
|
|
41
59
|
// #ifndef APP-NVUE
|
|
42
60
|
const query = uni.createSelectorQuery()
|
|
@@ -70,6 +88,17 @@ export function upGetRect(selector, all = false, comp = null) {
|
|
|
70
88
|
})
|
|
71
89
|
}
|
|
72
90
|
|
|
91
|
+
// 卸载后替代真实观察器,保持链式调用不报错
|
|
92
|
+
function noopIntersectionObserver() {
|
|
93
|
+
const observer = {
|
|
94
|
+
relativeTo: () => observer,
|
|
95
|
+
relativeToViewport: () => observer,
|
|
96
|
+
observe: () => {},
|
|
97
|
+
disconnect: () => {}
|
|
98
|
+
}
|
|
99
|
+
return observer
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
// 创建交叉观察器
|
|
74
103
|
// 小程序端 uni.createIntersectionObserver(vm) 会把 Vue 实例代理直接透传给原生 API,
|
|
75
104
|
// 原生内部的 Object.keys(vm) 会触发告警:
|
|
@@ -77,6 +106,10 @@ export function upGetRect(selector, all = false, comp = null) {
|
|
|
77
106
|
// (uni-app 只给 createSelectorQuery 做了 $scope 解包,没给 createIntersectionObserver 做)
|
|
78
107
|
// 组件实例上的同名方法内部使用 $scope(原生组件实例),不会把代理外泄,因此优先使用
|
|
79
108
|
export function upCreateIntersectionObserver(comp, options) {
|
|
109
|
+
// 和 upGetRect 同理:APP 端 addIntersectionObserver 也走 window.__$__(id).$ 查表
|
|
110
|
+
if (isCompUnmounted(comp)) {
|
|
111
|
+
return noopIntersectionObserver()
|
|
112
|
+
}
|
|
80
113
|
// 各家小程序与 H5 都在组件实例上暴露了该方法,内部已完成实例解包
|
|
81
114
|
if (comp && typeof comp.createIntersectionObserver === 'function') {
|
|
82
115
|
return options ? comp.createIntersectionObserver(options) : comp.createIntersectionObserver()
|
package/libs/mixin/mixin.js
CHANGED
|
@@ -251,6 +251,9 @@ export const mixin = defineMixin({
|
|
|
251
251
|
uni.$emit('uOnReachBottom')
|
|
252
252
|
},
|
|
253
253
|
beforeUnmount() {
|
|
254
|
+
// mixin 的钩子先于组件自身钩子同步执行,比 Vue 内部的 $.isUnmounted
|
|
255
|
+
// (在 post-render 队列里异步置位)更早,异步回调据此拦截节点查询
|
|
256
|
+
this.__upUnmounted = true
|
|
254
257
|
// 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
|
|
255
258
|
// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
|
|
256
259
|
if (this.parent && test.array(this.parent.children)) {
|
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.117",
|
|
6
6
|
"description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"uview",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"echarts",
|
|
12
12
|
"ui",
|
|
13
13
|
"可视化"
|
|
14
|
-
],
|
|
14
|
+
],
|
|
15
15
|
"main": "index.js",
|
|
16
16
|
"repository": "https://github.com/ijry/uview-plus",
|
|
17
17
|
"engines": {
|