uview-ultra 4.5.48 → 4.5.71
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 +1650 -1435
- package/components/up-button/up-button.uvue +9 -8
- package/components/up-button/up-button.vue +4 -4
- package/components/up-cell/props.js +6 -0
- package/components/up-cell/up-cell.uvue +21 -8
- package/components/up-cell/up-cell.vue +9 -2
- package/components/up-cell-group/up-cell-group.uvue +7 -0
- package/components/up-checkbox/up-checkbox.uvue +4 -3
- package/components/up-empty/up-empty.uvue +4 -0
- package/components/up-empty/up-empty.vue +3 -0
- package/components/up-goods-sku/up-goods-sku.uvue +32 -3
- package/components/up-goods-sku/up-goods-sku.vue +19 -3
- package/components/up-icon/icons.uts +1 -1
- package/components/up-navbar/navbar.uts +2 -0
- package/components/up-navbar/up-navbar.uvue +199 -28
- package/components/up-navbar/up-navbar.vue +14 -1
- package/components/up-popup/props.js +10 -0
- package/components/up-popup/up-popup.uvue +89 -1
- package/components/up-popup/up-popup.vue +69 -1
- package/components/up-radio/up-radio.uvue +6 -4
- package/components/up-radio-group/radioGroup.js +1 -1
- package/components/up-radio-group/radioGroup.uts +1 -1
- package/components/up-root/props.js +28 -0
- package/components/up-root/props.uts +28 -0
- package/components/up-root/root.js +14 -0
- package/components/up-root/root.uts +14 -0
- package/components/up-root/up-root.uvue +57 -0
- package/components/up-root/up-root.vue +35 -0
- package/components/up-root-toast-host/up-root-toast-host.uvue +27 -0
- package/components/up-switch/switch.uts +2 -1
- package/components/up-tag/up-tag.uvue +1 -1
- package/components/up-tag/up-tag.vue +1 -1
- package/components/up-transition/up-transition.uvue +10 -0
- package/components/up-transition/up-transition.vue +7 -0
- package/components/up-transition/vue.ani-style.scss +39 -10
- package/index.uts +47 -0
- package/libs/root/rootState.uts +74 -0
- package/libs/theme/theme.js +12 -2
- package/libs/theme/theme.uts +578 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<view class="up-navbar" :class="[customClass]">
|
|
2
|
+
<view class="up-navbar" :class="[customClass]" :style="rootStyle">
|
|
3
3
|
<!-- ios 模式:in-flow 层,恒定渲染,随页面原生滚动被带走 -->
|
|
4
4
|
<view v-if="isIosMode" class="up-navbar__flow">
|
|
5
5
|
<view :style="flowSpacerStyle"></view>
|
|
@@ -14,26 +14,33 @@
|
|
|
14
14
|
>{{ title }}</text>
|
|
15
15
|
</view>
|
|
16
16
|
</view>
|
|
17
|
-
<!--
|
|
17
|
+
<!--
|
|
18
|
+
default 模式:占位块。
|
|
19
|
+
注意高度是「根节点 + 这里」两份同一个值(见 rootStyle 的注释):H5 / 小程序
|
|
20
|
+
靠这一块撑开,App 端靠根节点那一份,两边都不会塌。
|
|
21
|
+
-->
|
|
18
22
|
<view
|
|
19
23
|
class="up-navbar__placeholder"
|
|
20
|
-
v-if="
|
|
24
|
+
v-if="placeholderActive"
|
|
21
25
|
:style="placeholderStyle"
|
|
22
26
|
></view>
|
|
23
27
|
<view :class="[(isIosMode || fixed) ? 'up-navbar--fixed' : '']">
|
|
24
|
-
<!-- ios
|
|
28
|
+
<!-- 磨砂层:随滚动淡入,位于内容之下。ios 模式与 frosted 模式共用 -->
|
|
25
29
|
<view
|
|
26
|
-
v-if="
|
|
30
|
+
v-if="showGlass"
|
|
27
31
|
class="up-navbar__glass"
|
|
32
|
+
:class="[frostedGlass ? 'up-navbar__glass--frosted' : '', frostedBorder ? 'up-navbar__glass--border' : '']"
|
|
28
33
|
:style="glassStyle"
|
|
29
34
|
></view>
|
|
30
|
-
|
|
35
|
+
<!-- 状态栏占位:高度可被 statusBarInsetTop 覆盖(iOS standalone 传 0) -->
|
|
36
|
+
<view
|
|
31
37
|
v-if="safeAreaInsetTop"
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
class="up-navbar__statusbar"
|
|
39
|
+
:style="statusBarSpacerStyle"
|
|
40
|
+
></view>
|
|
34
41
|
<view
|
|
35
42
|
class="up-navbar__content"
|
|
36
|
-
:class="[(border && !
|
|
43
|
+
:class="[(border && !showGlass) ? 'up-border-bottom' : '']"
|
|
37
44
|
:style="contentStyle"
|
|
38
45
|
>
|
|
39
46
|
<view
|
|
@@ -102,7 +109,7 @@
|
|
|
102
109
|
* @property {Boolean} safeAreaInsetTop 是否开启顶部安全区适配 (默认 true )
|
|
103
110
|
* @property {Boolean} placeholder 固定在顶部时,是否生成一个等高元素,以防止塌陷 (默认 false )
|
|
104
111
|
* @property {Boolean} fixed 导航栏是否固定在顶部 (默认 false )
|
|
105
|
-
* @property {Boolean} border
|
|
112
|
+
* @property {Boolean} border 导航栏底部是否显示下边框(磨砂层的分隔线也服从它)(默认 false )
|
|
106
113
|
* @property {String} leftIcon 左边返回图标的名称,只能为uView自带的图标 (默认 'arrow-left' )
|
|
107
114
|
* @property {String} leftText 左边的提示文字
|
|
108
115
|
* @property {String} rightText 右边的提示文字
|
|
@@ -111,6 +118,8 @@
|
|
|
111
118
|
* @property {String} bgColor 导航栏背景设置 (默认 '#ffffff' )
|
|
112
119
|
* @property {String | Number} titleWidth 导航栏标题的最大宽度,内容超出会以省略号隐藏 (默认 '400rpx' )
|
|
113
120
|
* @property {String | Number} height 导航栏高度(不包括状态栏高度在内,内部自动加上)(默认 '44px' )
|
|
121
|
+
* @property {Boolean} frosted 滚动时自动磨砂(高仿 iOS 材质;ios 模式自带,这个是给 H5 / Android 用的)(默认 false )
|
|
122
|
+
* @property {String | Number} statusBarInsetTop 状态栏占位高度覆盖(px):-1 用系统值、0 不占位(iOS standalone 传 0)(默认 -1 )
|
|
114
123
|
* @property {String | Number} leftIconSize 左侧返回图标的大小(默认 20px )
|
|
115
124
|
* @property {String | Number} leftIconColor 左侧返回图标的颜色(默认 #303133 )
|
|
116
125
|
* @property {Boolean} autoBack 点击左侧区域(返回图标),是否自动返回上一页(默认 false )
|
|
@@ -171,10 +180,11 @@
|
|
|
171
180
|
type: [String, Number],
|
|
172
181
|
default: defProps.getAny('navbar.title')
|
|
173
182
|
},
|
|
174
|
-
//
|
|
183
|
+
// 背景颜色。`|| '#ffffff'` 是兜底:`defProps` 在某些构建下取不到该键会返回空值,
|
|
184
|
+
// 内联背景一旦为空,`.up-navbar__content` 的调试遗留色就会露出来(曾表现为顶栏变蓝)。
|
|
175
185
|
bgColor: {
|
|
176
186
|
type: String,
|
|
177
|
-
default: defProps.getString('navbar.bgColor')
|
|
187
|
+
default: defProps.getString('navbar.bgColor') || '#ffffff'
|
|
178
188
|
},
|
|
179
189
|
// 标题的宽度
|
|
180
190
|
titleWidth: {
|
|
@@ -211,11 +221,24 @@
|
|
|
211
221
|
type: String,
|
|
212
222
|
default: defProps.getString('navbar.mode')
|
|
213
223
|
},
|
|
214
|
-
//
|
|
224
|
+
// 页面滚动距离,由页面 onPageScroll 传入;ios 模式与 frosted 模式都用它
|
|
215
225
|
scrollTop: {
|
|
216
226
|
type: [String, Number],
|
|
217
227
|
default: defProps.getAny('navbar.scrollTop')
|
|
218
228
|
},
|
|
229
|
+
// 滚动时自动磨砂:滚过阈值后淡入一层半透明模糊(高仿 iOS 材质)。
|
|
230
|
+
// ios 模式本来就有磨砂层,这个开关是给 H5 / Android 这些没有系统玻璃带的端用的。
|
|
231
|
+
frosted: {
|
|
232
|
+
type: Boolean,
|
|
233
|
+
default: defProps.getBoolean('navbar.frosted')
|
|
234
|
+
},
|
|
235
|
+
// 状态栏占位高度覆盖(px)。-1 = 用系统值;0 = 不占位。
|
|
236
|
+
// iOS standalone(PWA,meta=black)下 webview 本就从状态栏下方开始,必须传 0,
|
|
237
|
+
// 否则导航行被推出系统的 Liquid Glass 滚动边缘带。
|
|
238
|
+
statusBarInsetTop: {
|
|
239
|
+
type: [String, Number],
|
|
240
|
+
default: defProps.getAny('navbar.statusBarInsetTop')
|
|
241
|
+
},
|
|
219
242
|
// 微信小程序右侧区域是否避让原生胶囊
|
|
220
243
|
avoidCapsule: {
|
|
221
244
|
type: Boolean,
|
|
@@ -228,8 +251,53 @@
|
|
|
228
251
|
})
|
|
229
252
|
|
|
230
253
|
const emit = defineEmits(['leftClick', 'rightClick'])
|
|
231
|
-
const
|
|
232
|
-
|
|
254
|
+
const systemStatusBarHeight = ref(uni.getSystemInfoSync().statusBarHeight ?? 0)
|
|
255
|
+
|
|
256
|
+
// #ifdef H5 || WEB
|
|
257
|
+
// web 端 `statusBarHeight` 恒 0(浏览器视口不含系统状态栏),但 iOS Safari
|
|
258
|
+
// (viewport-fit=cover,地址栏收起)的页面视口顶部包含状态栏区域——真实占位
|
|
259
|
+
// 高度是 CSS `env(safe-area-inset-top)` 的像素值。探针元素读一次实际值:
|
|
260
|
+
// iPhone Safari ≈ 59,桌面 / Android 浏览器 env 为 0、行为不变。App 端原生
|
|
261
|
+
// statusBarHeight 是准的,不进这段。
|
|
262
|
+
try {
|
|
263
|
+
const doc: any = (globalThis as any).document
|
|
264
|
+
const probe = doc.createElement('div')
|
|
265
|
+
probe.setAttribute(
|
|
266
|
+
'style',
|
|
267
|
+
'position:absolute;visibility:hidden;pointer-events:none;height:env(safe-area-inset-top);padding:0;border:0;'
|
|
268
|
+
)
|
|
269
|
+
doc.body.appendChild(probe)
|
|
270
|
+
const measured = probe.getBoundingClientRect().height
|
|
271
|
+
doc.body.removeChild(probe)
|
|
272
|
+
if (typeof measured === 'number' && measured > 0) {
|
|
273
|
+
systemStatusBarHeight.value = measured
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
// 探测失败保持 0:与既有行为一致
|
|
277
|
+
}
|
|
278
|
+
// #endif
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 实际使用的状态栏占位高度。
|
|
282
|
+
*
|
|
283
|
+
* `statusBarInsetTop >= 0` 时以它为准,否则用系统值。iOS standalone(PWA,meta=black)
|
|
284
|
+
* 下 webview 本就从系统状态栏下方开始,占位必须传 0 —— 再填一段会把导航行推出
|
|
285
|
+
* iOS 26/27 在页面顶部画的 Liquid Glass 滚动边缘带(约 75pt)。
|
|
286
|
+
*/
|
|
287
|
+
const effectiveStatusBarHeight = computed((): number => {
|
|
288
|
+
// 非数字 / undefined 会得到 NaN,`NaN >= 0` 为 false,自然回落到系统值。
|
|
289
|
+
const override = Number(props.statusBarInsetTop)
|
|
290
|
+
if (override >= 0) return override
|
|
291
|
+
return systemStatusBarHeight.value
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const placeholderHeight = computed((): string => {
|
|
295
|
+
// `height` 取不到时 `parseInt` 会得到 NaN,占位高度就是无效值 → 塌成 0 →
|
|
296
|
+
// fixed 层不占流,内容直接顶到导航栏上。兜 uview 的默认 44。
|
|
297
|
+
const raw = parseInt(getPx(props.height))
|
|
298
|
+
const height = raw > 0 ? raw : 44
|
|
299
|
+
return addUnit(height + effectiveStatusBarHeight.value, 'px')
|
|
300
|
+
})
|
|
233
301
|
|
|
234
302
|
// iOS 大标题行高,同时是压缩进度的分母
|
|
235
303
|
const LARGE_TITLE_HEIGHT = 52
|
|
@@ -254,6 +322,31 @@
|
|
|
254
322
|
return props.mode == 'ios'
|
|
255
323
|
})
|
|
256
324
|
|
|
325
|
+
/** 是否处于「fixed + 等高占位」档。 */
|
|
326
|
+
const placeholderActive = computed((): boolean => {
|
|
327
|
+
return !isIosMode.value && props.fixed && props.placeholder
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 根节点样式:占位高度**同时**落在根节点上。
|
|
332
|
+
*
|
|
333
|
+
* 只把高度给内部的 `.up-navbar__placeholder` 是不够的 —— `<up-navbar>` 的根节点
|
|
334
|
+
* 才是父容器的 flex item(会话列表页是 `display: flex; flex-direction: column` 的
|
|
335
|
+
* `.conversations-shell`)。App 端(uvue 原生渲染)下,自定义组件的根节点不会被子
|
|
336
|
+
* 节点的显式高度撑开:占位块在,但它贡献的高度传不到父级;而 fixed 层又不占流,
|
|
337
|
+
* 于是列表第一行直接压在导航行下面 —— 这正是「开了 placeholder 也没用」的实况。
|
|
338
|
+
*
|
|
339
|
+
* 高度值只有一份(`placeholderHeight`),根节点与内部占位块共用,不会叠成两份。
|
|
340
|
+
*/
|
|
341
|
+
const rootStyle = computed((): UTSJSONObject => {
|
|
342
|
+
const style = {} as UTSJSONObject
|
|
343
|
+
if (!placeholderActive.value) {
|
|
344
|
+
return style
|
|
345
|
+
}
|
|
346
|
+
style['height'] = placeholderHeight.value
|
|
347
|
+
return style
|
|
348
|
+
})
|
|
349
|
+
|
|
257
350
|
// 有效大标题行高:title 为空时不渲染大标题行,行高塌陷为 0
|
|
258
351
|
const largeTitleHeight = computed((): number => {
|
|
259
352
|
return props.title != '' ? LARGE_TITLE_HEIGHT : 0
|
|
@@ -272,12 +365,48 @@
|
|
|
272
365
|
return Math.min(Math.max(offset / distance, 0), 1)
|
|
273
366
|
})
|
|
274
367
|
|
|
275
|
-
//
|
|
368
|
+
// 非 iOS 模式的磨砂参数:滚过 2px 开始淡入,28px 走满。
|
|
369
|
+
// 「半透明」由 `.up-navbar__glass--frosted` 的底色(主题里的 75%/78% rgba)保证,
|
|
370
|
+
// 这里只负责淡入,不再额外压 opacity —— 否则两层衰减叠起来会淡得看不见。
|
|
371
|
+
//
|
|
372
|
+
// 行程刻意短:iOS 的导航栏在内容压到栏下时是**立刻**出背景(scrollEdge → standard),
|
|
373
|
+
// 过渡只是让离散的滚动事件看起来连续。40px 那种长行程在「列表只滚了一点点」的场景
|
|
374
|
+
// 下永远走不满,看上去就像磨砂没开。
|
|
375
|
+
const FROSTED_START = 2
|
|
376
|
+
const FROSTED_FADE = 28
|
|
377
|
+
|
|
378
|
+
/** 磨砂层要不要渲染。 */
|
|
379
|
+
const showGlass = computed((): boolean => {
|
|
380
|
+
return isIosMode.value || props.frosted
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
/** 是否「非 iOS 模式的滚动磨砂」(决定要不要额外画底部细线)。 */
|
|
384
|
+
const frostedGlass = computed((): boolean => {
|
|
385
|
+
return props.frosted && !isIosMode.value
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 磨砂层要不要画底部分隔线。
|
|
390
|
+
*
|
|
391
|
+
* 与 `border` 同一语义 —— `border` 的描述就是「导航栏底部是否显示下边框」,
|
|
392
|
+
* 磨砂层那条线就是下边框,必须服从它。早先这条线写死在磨砂层样式里,
|
|
393
|
+
* 调用方明明传了 `:border="false"`,滚动时照样会冒出一条,等于这个开关半失效。
|
|
394
|
+
*/
|
|
395
|
+
const frostedBorder = computed((): boolean => {
|
|
396
|
+
return frostedGlass.value && props.border
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// iOS 模式:磨砂在前半段走完,为居中标题的出现铺好不透明底。
|
|
400
|
+
// frosted 模式:按滚动距离从 0 淡入到 1(「半透明」由底色负责,不是靠 opacity)。
|
|
276
401
|
const glassOpacity = computed((): number => {
|
|
277
|
-
if (
|
|
402
|
+
if (isIosMode.value) {
|
|
403
|
+
return Math.min(Math.max(iosProgress.value / 0.5, 0), 1)
|
|
404
|
+
}
|
|
405
|
+
if (!props.frosted) {
|
|
278
406
|
return 0
|
|
279
407
|
}
|
|
280
|
-
|
|
408
|
+
const offset = parseInt(getPx(props.scrollTop))
|
|
409
|
+
return Math.min(Math.max((offset - FROSTED_START) / FROSTED_FADE, 0), 1)
|
|
281
410
|
})
|
|
282
411
|
|
|
283
412
|
// 居中标题在后段才启动,此时磨砂层已满不透明,不会与大标题互相透出
|
|
@@ -288,16 +417,25 @@
|
|
|
288
417
|
return Math.min(Math.max((iosProgress.value - 0.75) / 0.25, 0), 1)
|
|
289
418
|
})
|
|
290
419
|
|
|
291
|
-
//
|
|
420
|
+
// 磨砂层显示时状态栏占位透明,背景由磨砂层统一承担
|
|
292
421
|
const statusBarBgColor = computed((): string => {
|
|
293
|
-
return
|
|
422
|
+
return showGlass.value ? 'transparent' : props.bgColor
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
/** 状态栏占位块的样式(高度可被 `statusBarInsetTop` 覆盖)。 */
|
|
426
|
+
const statusBarSpacerStyle = computed((): UTSJSONObject => {
|
|
427
|
+
const style = {} as UTSJSONObject
|
|
428
|
+
style['height'] = addUnit(effectiveStatusBarHeight.value, 'px')
|
|
429
|
+
style['backgroundColor'] = statusBarBgColor.value
|
|
430
|
+
return style
|
|
294
431
|
})
|
|
295
432
|
|
|
296
433
|
const flowSpacerStyle = computed((): UTSJSONObject => {
|
|
297
434
|
const style = {} as UTSJSONObject
|
|
298
|
-
|
|
435
|
+
const rawTop = parseInt(getPx(props.height))
|
|
436
|
+
let top = rawTop > 0 ? rawTop : 44
|
|
299
437
|
if (props.safeAreaInsetTop) {
|
|
300
|
-
top +=
|
|
438
|
+
top += effectiveStatusBarHeight.value
|
|
301
439
|
}
|
|
302
440
|
style['height'] = addUnit(top, 'px')
|
|
303
441
|
return style
|
|
@@ -314,12 +452,18 @@
|
|
|
314
452
|
return UTSJSONObject.assign(style, addStyle(props.titleStyle) as UTSJSONObject)
|
|
315
453
|
})
|
|
316
454
|
|
|
317
|
-
// 磨砂层。App 原生端不支持 backdrop-filter
|
|
455
|
+
// 磨砂层。App 原生端不支持 backdrop-filter 时,底色本身就是可读性下限;
|
|
318
456
|
// backdrop-filter 仍然写入,在 WebView 类端上会自动生效。
|
|
457
|
+
//
|
|
458
|
+
// frosted 模式**不写** backgroundColor:调用方为了透明顶栏会把 bgColor 传成
|
|
459
|
+
// `transparent`,那样磨砂层就没有底色了。底色交给 `.up-navbar__glass--frosted`
|
|
460
|
+
// 的 CSS(卡片色 72%),与主题变量联动。
|
|
319
461
|
const glassStyle = computed((): UTSJSONObject => {
|
|
320
462
|
const style = {} as UTSJSONObject
|
|
321
463
|
style['opacity'] = glassOpacity.value
|
|
322
|
-
|
|
464
|
+
if (!frostedGlass.value) {
|
|
465
|
+
style['backgroundColor'] = props.bgColor
|
|
466
|
+
}
|
|
323
467
|
return style
|
|
324
468
|
})
|
|
325
469
|
|
|
@@ -344,8 +488,8 @@
|
|
|
344
488
|
const contentStyle = computed((): UTSJSONObject => {
|
|
345
489
|
const style = {} as UTSJSONObject
|
|
346
490
|
style['height'] = addUnit(props.height)
|
|
347
|
-
//
|
|
348
|
-
style['backgroundColor'] =
|
|
491
|
+
// 磨砂层显示时内容区透明,背景交给磨砂层,避免遮住其淡入效果
|
|
492
|
+
style['backgroundColor'] = showGlass.value ? 'transparent' : props.bgColor
|
|
349
493
|
return style
|
|
350
494
|
})
|
|
351
495
|
|
|
@@ -376,7 +520,9 @@
|
|
|
376
520
|
}
|
|
377
521
|
|
|
378
522
|
defineExpose({
|
|
379
|
-
statusBarHeight
|
|
523
|
+
// 名字跟着内部的 `systemStatusBarHeight` 走。早先这里还叫 `statusBarHeight`,
|
|
524
|
+
// 重命名后忘了同步 —— uts 里引用不存在的标识符,整段 setup 直接编译不过。
|
|
525
|
+
systemStatusBarHeight,
|
|
380
526
|
placeholderHeight,
|
|
381
527
|
isIosMode,
|
|
382
528
|
iosProgress,
|
|
@@ -389,6 +535,9 @@
|
|
|
389
535
|
@import "../../libs/css/components.scss";
|
|
390
536
|
|
|
391
537
|
.up-navbar {
|
|
538
|
+
// 占位模式下根节点自己带高度;作为 flex 列容器的子项时不能被压缩,
|
|
539
|
+
// 否则内容一多,占位段先被挤掉,列表又顶回导航行下面。
|
|
540
|
+
flex-shrink: 0;
|
|
392
541
|
|
|
393
542
|
&--fixed {
|
|
394
543
|
position: fixed;
|
|
@@ -426,11 +575,33 @@
|
|
|
426
575
|
backdrop-filter: saturate(180%) blur(var(--up-navbar-glass-blur, 20px));
|
|
427
576
|
}
|
|
428
577
|
|
|
578
|
+
// 滚动磨砂(非 iOS 模式):底色是半透明的卡片色,配合 backdrop-filter 才是
|
|
579
|
+
// 「高仿 iOS」的材质 —— 不透明底会把背后的模糊完全盖掉。
|
|
580
|
+
//
|
|
581
|
+
// 底色走**主题变量**(`theme.uts` 里算好的 rgba),不写 `color-mix`:
|
|
582
|
+
// uvue App 端不支持 `color-mix`,整条声明会被丢掉;而 App 端也不支持
|
|
583
|
+
// `backdrop-filter` —— 两层同时失效,滚动时顶栏就跟全透明一样,磨砂等于没开。
|
|
584
|
+
&__glass--frosted {
|
|
585
|
+
background-color: var(--up-navbar-glass-bg, rgba(255, 255, 255, 0.75));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// 底部分隔线单独一个类,**只在 `border` 为真时**才挂上来。
|
|
589
|
+
// 早先它写死在 `--frosted` 里,页面传 `:border="false"` 也照样出线。
|
|
590
|
+
&__glass--border {
|
|
591
|
+
border-bottom: 1px solid var(--up-navbar-glass-border, rgba(0, 0, 0, 0.08));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
&__statusbar {
|
|
595
|
+
width: 100%;
|
|
596
|
+
}
|
|
597
|
+
|
|
429
598
|
&__content {
|
|
430
599
|
@include flex(row);
|
|
431
600
|
align-items: center;
|
|
432
601
|
height: 44px;
|
|
433
|
-
|
|
602
|
+
// 底色一律由 `contentStyle` 内联给出;这里必须是透明 —— 早先这里是
|
|
603
|
+
// `#9acafc`(primaryDisabled 的调试遗留色),内联背景一旦失效顶栏就整条变蓝。
|
|
604
|
+
background-color: transparent;
|
|
434
605
|
// 磨砂层是绝对定位的,会盖住同级非定位元素,故内容区显式提升层级
|
|
435
606
|
position: relative;
|
|
436
607
|
z-index: 1;
|
|
@@ -171,9 +171,22 @@ const isIosMode = computed(() => props.mode === 'ios')
|
|
|
171
171
|
// 有效大标题行高:title 为空时不渲染大标题行,行高塌陷为 0
|
|
172
172
|
const largeTitleHeight = computed(() => (props.title ? LARGE_TITLE_HEIGHT : 0))
|
|
173
173
|
|
|
174
|
+
// web 端 `statusBarHeight` 恒 0,但 iOS Safari(viewport-fit=cover,地址栏收起)的
|
|
175
|
+
// 页面视口顶部包含状态栏区域——真实高度是 env(safe-area-inset-top)。探针读一次。
|
|
176
|
+
let envSafeAreaTop = 0
|
|
177
|
+
// #ifdef H5
|
|
178
|
+
try {
|
|
179
|
+
const probe = document.createElement('div')
|
|
180
|
+
probe.setAttribute('style', 'position:absolute;visibility:hidden;height:env(safe-area-inset-top);padding:0;border:0;')
|
|
181
|
+
document.body.appendChild(probe)
|
|
182
|
+
envSafeAreaTop = probe.getBoundingClientRect().height
|
|
183
|
+
document.body.removeChild(probe)
|
|
184
|
+
} catch (e) {}
|
|
185
|
+
// #endif
|
|
186
|
+
|
|
174
187
|
// in-flow 层顶部让位块高度,为固定层腾出空间
|
|
175
188
|
const flowSpacerHeight = computed(() => {
|
|
176
|
-
const statusBarHeight = props.safeAreaInsetTop ? sys().statusBarHeight : 0
|
|
189
|
+
const statusBarHeight = props.safeAreaInsetTop ? (sys().statusBarHeight || envSafeAreaTop) : 0
|
|
177
190
|
return addUnit(getPx(props.height) + statusBarHeight, 'px')
|
|
178
191
|
})
|
|
179
192
|
|
|
@@ -81,6 +81,16 @@ export const props = defineMixin({
|
|
|
81
81
|
pageInline: {
|
|
82
82
|
type: Boolean,
|
|
83
83
|
default: () => defProps.popup.pageInline
|
|
84
|
+
},
|
|
85
|
+
// bottom 弹层是否支持拖动下滑关闭
|
|
86
|
+
dragClose: {
|
|
87
|
+
type: Boolean,
|
|
88
|
+
default: false
|
|
89
|
+
},
|
|
90
|
+
// 拖动下滑关闭的位移阈值,单位px
|
|
91
|
+
dragCloseThreshold: {
|
|
92
|
+
type: [String, Number],
|
|
93
|
+
default: 80
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
})
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
>
|
|
21
21
|
<view
|
|
22
22
|
class="up-popup__content"
|
|
23
|
-
:style="[contentStyle]"
|
|
23
|
+
:style="[contentStyle, dragStyle]"
|
|
24
|
+
@touchstart="onDragTouchStart"
|
|
25
|
+
@touchmove="onDragTouchMove"
|
|
26
|
+
@touchend="onDragTouchEnd"
|
|
27
|
+
@touchcancel="onDragTouchEnd"
|
|
24
28
|
@tap.stop="stopContentTap"
|
|
25
29
|
>
|
|
26
30
|
<up-status-bar v-if="safeAreaInsetTop"></up-status-bar>
|
|
@@ -69,6 +73,8 @@
|
|
|
69
73
|
* @property {String} closeIconPos 自定义关闭图标位置(默认 'top-right' )
|
|
70
74
|
* @property {String | Number} round 圆角值(默认 0)
|
|
71
75
|
* @property {Boolean} zoom 当mode=center时 是否开启缩放(默认 true )
|
|
76
|
+
* @property {Boolean} dragClose bottom 弹层是否支持拖动下滑关闭(默认 false;配合弹层顶部的拖拽手柄使用)
|
|
77
|
+
* @property {String | Number} dragCloseThreshold 拖动下滑关闭的位移阈值,单位px(默认 80)
|
|
72
78
|
* @property {Object} customStyle 组件的样式,对象形式
|
|
73
79
|
* @event {Function} open 弹出层打开(进场动画结束后)
|
|
74
80
|
* @event {Function} close 弹出层收起(关闭动作发生时,离场动画开始前;外部直接将show置为false时也会触发)
|
|
@@ -161,6 +167,16 @@
|
|
|
161
167
|
type: Boolean,
|
|
162
168
|
default: defProps.getBoolean('popup.pageInline')
|
|
163
169
|
},
|
|
170
|
+
// bottom 弹层是否支持拖动下滑关闭
|
|
171
|
+
dragClose: {
|
|
172
|
+
type: Boolean,
|
|
173
|
+
default: false
|
|
174
|
+
},
|
|
175
|
+
// 拖动下滑关闭的位移阈值,单位px
|
|
176
|
+
dragCloseThreshold: {
|
|
177
|
+
type: [String, Number],
|
|
178
|
+
default: 80
|
|
179
|
+
},
|
|
164
180
|
customStyle: {
|
|
165
181
|
type: [Object, String],
|
|
166
182
|
default: () => ({})
|
|
@@ -289,6 +305,78 @@
|
|
|
289
305
|
return ''
|
|
290
306
|
})
|
|
291
307
|
|
|
308
|
+
/*
|
|
309
|
+
* 拖动下滑关闭(dragClose)。
|
|
310
|
+
*
|
|
311
|
+
* 仅 bottom 弹层生效:手指按下后向下拖,content 跟手位移;松手时超过阈值
|
|
312
|
+
* 走既有 close 流程(update:show=false + close 事件),否则带回弹动画归位。
|
|
313
|
+
* 向上拖不拦截,弹层内部滚动不受影响。
|
|
314
|
+
*/
|
|
315
|
+
const dragOffset = ref(0)
|
|
316
|
+
const dragActive = ref(false)
|
|
317
|
+
let dragStartY = 0
|
|
318
|
+
let dragTracking = false
|
|
319
|
+
let dragReleaseTimer = -1
|
|
320
|
+
|
|
321
|
+
const dragStyle = computed((): UTSJSONObject => {
|
|
322
|
+
if (!dragActive.value || dragOffset.value <= 0) {
|
|
323
|
+
return {} as UTSJSONObject
|
|
324
|
+
}
|
|
325
|
+
const style = {
|
|
326
|
+
transform: `translateY(${dragOffset.value}px)`,
|
|
327
|
+
} as UTSJSONObject
|
|
328
|
+
// 跟手阶段不做过渡;松手回弹时加过渡(dragActive 在回弹结束后复位)
|
|
329
|
+
style["transition"] = dragTracking ? "none" : "transform 0.25s ease-out"
|
|
330
|
+
return style
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const dragCloseEnabled = computed((): boolean => {
|
|
334
|
+
return props.dragClose && props.mode === "bottom" && !props.pageInline
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const onDragTouchStart = (e: TouchEvent): void => {
|
|
338
|
+
if (!dragCloseEnabled.value) return
|
|
339
|
+
if (e.touches.length == 0) return
|
|
340
|
+
dragStartY = e.touches[0].clientY
|
|
341
|
+
dragTracking = true
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const onDragTouchMove = (e: TouchEvent): void => {
|
|
345
|
+
if (!dragTracking) return
|
|
346
|
+
if (e.touches.length == 0) return
|
|
347
|
+
const delta = e.touches[0].clientY - dragStartY
|
|
348
|
+
if (delta <= 0) {
|
|
349
|
+
// 向上拖:不进入拖动态,交给内部滚动
|
|
350
|
+
if (!dragActive.value) return
|
|
351
|
+
dragOffset.value = 0
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
dragActive.value = true
|
|
355
|
+
dragOffset.value = delta
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const onDragTouchEnd = (_e: UniEvent): void => {
|
|
359
|
+
if (!dragTracking) return
|
|
360
|
+
dragTracking = false
|
|
361
|
+
const offset = dragOffset.value
|
|
362
|
+
const thresholdValue = parseInt(props.dragCloseThreshold.toString())
|
|
363
|
+
const threshold = isNaN(thresholdValue) ? 80 : thresholdValue
|
|
364
|
+
if (offset >= threshold) {
|
|
365
|
+
dragActive.value = false
|
|
366
|
+
dragOffset.value = 0
|
|
367
|
+
close(_e)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
// 回弹:dragStyle 保留到过渡结束后再卸下,否则回弹动画会被瞬时移除样式截断
|
|
371
|
+
if (dragReleaseTimer >= 0) {
|
|
372
|
+
clearTimeout(dragReleaseTimer)
|
|
373
|
+
}
|
|
374
|
+
dragReleaseTimer = setTimeout(() => {
|
|
375
|
+
dragActive.value = false
|
|
376
|
+
dragOffset.value = 0
|
|
377
|
+
}, 260)
|
|
378
|
+
}
|
|
379
|
+
|
|
292
380
|
const stopContentTap = (e: UniEvent): void => {
|
|
293
381
|
e.stopPropagation()
|
|
294
382
|
}
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
>
|
|
21
21
|
<view
|
|
22
22
|
class="up-popup__content"
|
|
23
|
-
:style="[contentStyle]"
|
|
23
|
+
:style="[contentStyle, dragStyle]"
|
|
24
|
+
@touchstart="onDragTouchStart"
|
|
25
|
+
@touchmove="onDragTouchMove"
|
|
26
|
+
@touchend="onDragTouchEnd"
|
|
27
|
+
@touchcancel="onDragTouchEnd"
|
|
24
28
|
@tap.stop="noop"
|
|
25
29
|
>
|
|
26
30
|
<up-status-bar v-if="safeAreaInsetTop"></up-status-bar>
|
|
@@ -92,6 +96,70 @@
|
|
|
92
96
|
const emit = defineEmits(['open', 'close', 'closed', 'click', 'update:show'])
|
|
93
97
|
const instance = getCurrentInstance()
|
|
94
98
|
const { noop } = useUltraUI(props)
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* 拖动下滑关闭(dragClose)。
|
|
102
|
+
*
|
|
103
|
+
* 仅 bottom 弹层生效:手指按下后向下拖,content 跟手位移;松手时超过阈值
|
|
104
|
+
* 走既有 close 流程(update:show=false + close 事件),否则带回弹动画归位。
|
|
105
|
+
* 向上拖不拦截,弹层内部滚动不受影响。
|
|
106
|
+
*/
|
|
107
|
+
const dragOffset = ref(0)
|
|
108
|
+
const dragActive = ref(false)
|
|
109
|
+
let dragStartY = 0
|
|
110
|
+
let dragTracking = false
|
|
111
|
+
let dragReleaseTimer = -1
|
|
112
|
+
|
|
113
|
+
const dragStyle = computed(() => {
|
|
114
|
+
if (!dragActive.value || dragOffset.value <= 0) return {}
|
|
115
|
+
return {
|
|
116
|
+
transform: `translateY(${dragOffset.value}px)`,
|
|
117
|
+
transition: dragTracking ? 'none' : 'transform 0.25s ease-out',
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const dragCloseEnabled = computed(() => props.dragClose && props.mode === 'bottom' && !props.pageInline)
|
|
122
|
+
|
|
123
|
+
const onDragTouchStart = (e) => {
|
|
124
|
+
if (!dragCloseEnabled.value) return
|
|
125
|
+
if (!e.touches || e.touches.length === 0) return
|
|
126
|
+
dragStartY = e.touches[0].clientY
|
|
127
|
+
dragTracking = true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const onDragTouchMove = (e) => {
|
|
131
|
+
if (!dragTracking) return
|
|
132
|
+
if (!e.touches || e.touches.length === 0) return
|
|
133
|
+
const delta = e.touches[0].clientY - dragStartY
|
|
134
|
+
if (delta <= 0) {
|
|
135
|
+
// 向上拖:不进入拖动态,交给内部滚动
|
|
136
|
+
if (!dragActive.value) return
|
|
137
|
+
dragOffset.value = 0
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
dragActive.value = true
|
|
141
|
+
dragOffset.value = delta
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const onDragTouchEnd = (_e) => {
|
|
145
|
+
if (!dragTracking) return
|
|
146
|
+
dragTracking = false
|
|
147
|
+
const offset = dragOffset.value
|
|
148
|
+
const thresholdValue = parseInt(props.dragCloseThreshold)
|
|
149
|
+
const threshold = isNaN(thresholdValue) ? 80 : thresholdValue
|
|
150
|
+
if (offset >= threshold) {
|
|
151
|
+
dragActive.value = false
|
|
152
|
+
dragOffset.value = 0
|
|
153
|
+
close(_e)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
// 回弹:dragStyle 保留到过渡结束后再卸下,否则回弹动画会被瞬时移除样式截断
|
|
157
|
+
if (dragReleaseTimer >= 0) clearTimeout(dragReleaseTimer)
|
|
158
|
+
dragReleaseTimer = setTimeout(() => {
|
|
159
|
+
dragActive.value = false
|
|
160
|
+
dragOffset.value = 0
|
|
161
|
+
}, 260)
|
|
162
|
+
}
|
|
95
163
|
const overlayDuration = ref(props.duration + 50)
|
|
96
164
|
// close事件是否已由组件内部的关闭动作发出,避免show变化时重复发出
|
|
97
165
|
const closeEmitted = ref(false)
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
|
|
40
40
|
<script setup lang="uts">
|
|
41
41
|
import { computed, getCurrentInstance, nextTick, onMounted, ref } from 'vue';
|
|
42
|
-
import defProps from './radio.uts'
|
|
42
|
+
import defProps from './radio.uts'
|
|
43
|
+
import { upThemeVar } from '../../libs/theme/theme.uts';
|
|
43
44
|
import { addUnit, addStyle, os, deepMerge, formValidate, error } from '../../libs/function/index.uts';
|
|
44
45
|
import { useUltraUI } from '../../libs/composable/useUltraUI.uts';
|
|
45
46
|
/**
|
|
@@ -213,7 +214,7 @@
|
|
|
213
214
|
const elActiveColor = computed((): string => {
|
|
214
215
|
return props.activeColor as string != ''
|
|
215
216
|
? props.activeColor as string
|
|
216
|
-
: (parentData.value['activeColor'] as string != '' ? parentData.value['activeColor'] as string : '#2979ff')
|
|
217
|
+
: (parentData.value['activeColor'] as string != '' ? parentData.value['activeColor'] as string : upThemeVar('--up-primary', '#2979ff'))
|
|
217
218
|
})
|
|
218
219
|
|
|
219
220
|
// 组件选未中激活时的颜色
|
|
@@ -485,8 +486,9 @@
|
|
|
485
486
|
|
|
486
487
|
&--checked {
|
|
487
488
|
color: $up-radio-checked-color;
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
/* 兜底色接主题变量:activeColor 未显式传时(radio-group 默认)随主色走 */
|
|
490
|
+
background-color: var(--up-primary, $up-radio-checked-background-color);
|
|
491
|
+
border-color: var(--up-primary, $up-radio-checked-border-color);
|
|
490
492
|
}
|
|
491
493
|
|
|
492
494
|
&--disabled {
|