@zfqh/uview-x 0.0.0-stage → 0.0.2

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -3
  4. package/components/uv-badge/props.ts +83 -0
  5. package/components/uv-badge/uv-badge.vue +186 -0
  6. package/components/uv-button/props.ts +148 -0
  7. package/components/uv-button/uv-button.vue +565 -0
  8. package/components/uv-checkbox/props.ts +83 -0
  9. package/components/uv-checkbox/uv-checkbox.vue +403 -0
  10. package/components/uv-checkbox-group/props.ts +97 -0
  11. package/components/uv-checkbox-group/uv-checkbox-group.vue +163 -0
  12. package/components/uv-code/props.ts +48 -0
  13. package/components/uv-code/uv-code.vue +156 -0
  14. package/components/uv-dialog/props.ts +56 -0
  15. package/components/uv-dialog/uv-dialog.vue +492 -0
  16. package/components/uv-form/props.ts +59 -0
  17. package/components/uv-form/uv-form.vue +256 -0
  18. package/components/uv-form/valid.ts +1344 -0
  19. package/components/uv-form-item/props.ts +62 -0
  20. package/components/uv-form-item/uv-form-item.vue +335 -0
  21. package/components/uv-icon/icons.ts +161 -0
  22. package/components/uv-icon/props.ts +91 -0
  23. package/components/uv-icon/uv-icon.vue +208 -0
  24. package/components/uv-icon/uvicons.ttf +0 -0
  25. package/components/uv-input/props.ts +185 -0
  26. package/components/uv-input/uv-input.vue +431 -0
  27. package/components/uv-line/props.ts +42 -0
  28. package/components/uv-line/uv-line.vue +75 -0
  29. package/components/uv-line-progress/props.ts +43 -0
  30. package/components/uv-line-progress/uv-line-progress.vue +166 -0
  31. package/components/uv-loading-icon/props.ts +78 -0
  32. package/components/uv-loading-icon/uv-loading-icon.vue +226 -0
  33. package/components/uv-overlay/props.ts +38 -0
  34. package/components/uv-overlay/uv-overlay.vue +120 -0
  35. package/components/uv-popup/props.ts +101 -0
  36. package/components/uv-popup/uv-popup.vue +371 -0
  37. package/components/uv-safe-bottom/props.ts +18 -0
  38. package/components/uv-safe-bottom/uv-safe-bottom.vue +120 -0
  39. package/components/uv-status-bar/props.ts +22 -0
  40. package/components/uv-status-bar/uv-status-bar.vue +121 -0
  41. package/components/uv-tabs/props.ts +84 -0
  42. package/components/uv-tabs/uv-tabs.vue +432 -0
  43. package/components/uv-transition/props.ts +38 -0
  44. package/components/uv-transition/uv-transition.vue +170 -0
  45. package/hooks/index.ts +5 -0
  46. package/hooks/useDebounce.ts +35 -0
  47. package/hooks/useI18n.ts +95 -0
  48. package/hooks/useThrottle.ts +38 -0
  49. package/index.ts +23 -0
  50. package/libs/dayjs/index.ts +217 -0
  51. package/libs/index.ts +4 -0
  52. package/libs/luch-request/adapters/index.ts +133 -0
  53. package/libs/luch-request/core/InterceptorManager.ts +52 -0
  54. package/libs/luch-request/core/Request.ts +202 -0
  55. package/libs/luch-request/core/buildFullPath.ts +21 -0
  56. package/libs/luch-request/core/defaults.ts +34 -0
  57. package/libs/luch-request/core/dispatchRequest.ts +7 -0
  58. package/libs/luch-request/core/mergeConfig.ts +127 -0
  59. package/libs/luch-request/core/settle.ts +17 -0
  60. package/libs/luch-request/helpers/buildURL.ts +65 -0
  61. package/libs/luch-request/helpers/combineURLs.ts +15 -0
  62. package/libs/luch-request/helpers/isAbsoluteURL.ts +15 -0
  63. package/libs/luch-request/index.d.ts +197 -0
  64. package/libs/luch-request/index.ts +3 -0
  65. package/libs/luch-request/utils/clone.ts +265 -0
  66. package/libs/luch-request/utils.ts +136 -0
  67. package/locales/en.ts +78 -0
  68. package/locales/index.ts +169 -0
  69. package/locales/zh-Hans.ts +78 -0
  70. package/package.json +42 -4
  71. package/styles/color.scss +33 -0
  72. package/styles/common.scss +134 -0
  73. package/styles/components.scss +19 -0
  74. package/styles/index.scss +7 -0
  75. package/styles/theme.scss +44 -0
  76. package/styles/var.module.scss +32 -0
  77. package/styles/vue.scss +39 -0
  78. package/theme.scss +2 -0
  79. package/types/index.d.ts +66 -0
  80. package/utils/colorGradient.ts +139 -0
  81. package/utils/compat.ts +165 -0
  82. package/utils/compress.ts +34 -0
  83. package/utils/config.ts +95 -0
  84. package/utils/debounce.ts +7 -0
  85. package/utils/digit.ts +157 -0
  86. package/utils/function.ts +771 -0
  87. package/utils/http.ts +5 -0
  88. package/utils/index.ts +57 -0
  89. package/utils/locale.ts +296 -0
  90. package/utils/platform.ts +71 -0
  91. package/utils/route.ts +142 -0
  92. package/utils/test.ts +256 -0
  93. package/utils/throttle.ts +7 -0
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <view
3
+ class="uv-status-bar"
4
+ :class="[p.customClass]"
5
+ :style="[statusBarStyle]"
6
+ >
7
+ <slot />
8
+ </view>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed, onMounted, ref } from 'vue'
13
+ import { $uv } from '../../utils'
14
+ import props from './props'
15
+
16
+ defineOptions({
17
+ name: 'uv-status-bar',
18
+ // #ifdef UNI-APP-X
19
+ styleIsolation: 'app-and-page',
20
+ // #endif
21
+ virtualHost: true,
22
+ })
23
+
24
+ /**
25
+ * StatusBar 状态栏占位
26
+ * @description 本组件主要用于状态栏填充,比如在自定义导航栏的时候,它会自动适配一个恰当的状态栏高度。
27
+ * App 蒸汽模式使用 uni.getWindowInfo() 的 statusBarHeight 计算高度。
28
+ * @tutorial https://www.uvui.cn/components/statusBar.html
29
+ * @property {String} bgColor 背景色 (默认 'transparent' )
30
+ * @property {Object} customStyle 自定义样式
31
+ * @property {String} customClass 自定义类名
32
+ * @example <uv-status-bar></uv-status-bar>
33
+ */
34
+ const p = defineProps(props)
35
+
36
+ // uni-app x:窗口信息用 getWindowInfo()、设备信息用 getDeviceInfo();
37
+ // 旧基座缺 statusBarHeight 时回退 getSystemInfoSync() / safeArea 字段。
38
+ const systemInfo = ref<any>({})
39
+ const readSystemInfo = () => {
40
+ const info: any = {}
41
+ try {
42
+ const windowInfo: any = uni.getWindowInfo()
43
+ info.statusBarHeight = windowInfo.statusBarHeight
44
+ info.safeAreaInsets = windowInfo.safeAreaInsets
45
+ info.safeArea = windowInfo.safeArea
46
+ } catch (e) {}
47
+ try {
48
+ const deviceInfo: any = uni.getDeviceInfo()
49
+ info.osName = deviceInfo.osName
50
+ info.deviceType = deviceInfo.deviceType
51
+ } catch (e) {}
52
+ if (info.statusBarHeight === undefined) {
53
+ try {
54
+ const legacyInfo: any = uni.getSystemInfoSync()
55
+ if (info.statusBarHeight === undefined) info.statusBarHeight = legacyInfo.statusBarHeight
56
+ if (info.safeAreaInsets === undefined) info.safeAreaInsets = legacyInfo.safeAreaInsets
57
+ if (info.safeArea === undefined) info.safeArea = legacyInfo.safeArea
58
+ } catch (e) {}
59
+ }
60
+ systemInfo.value = info
61
+ }
62
+ readSystemInfo()
63
+
64
+ onMounted(() => {
65
+ readSystemInfo()
66
+ })
67
+
68
+ /** 状态栏高度:getWindowInfo().statusBarHeight,缺失时依次用 safeAreaInsets.top / safeArea.top 兜底 */
69
+ const statusBarHeight = computed(() => {
70
+ const info = systemInfo.value || {}
71
+ const height = Number(info.statusBarHeight)
72
+ if (height > 0) return height
73
+ const insets = info.safeAreaInsets
74
+ if (insets && typeof insets.top === 'number' && insets.top > 0) return insets.top
75
+ // #ifdef APP || MP
76
+ const safeArea = info.safeArea
77
+ if (safeArea && safeArea.top > 0) return safeArea.top
78
+ // #endif
79
+ return 0
80
+ })
81
+
82
+ const statusBarStyle = computed(() => {
83
+ const style: any = {}
84
+ const height = statusBarHeight.value
85
+ if (height > 0) {
86
+ style.height = $uv.addUnit(height, 'px')
87
+ }
88
+ if (p.bgColor) {
89
+ if (p.bgColor.indexOf('gradient') > -1) {
90
+ // #ifdef APP-NVUE
91
+ style.backgroundColor = '#ffffff'
92
+ // #endif
93
+ // #ifndef APP-NVUE
94
+ style.backgroundImage = p.bgColor
95
+ // #endif
96
+ } else {
97
+ style.background = p.bgColor
98
+ }
99
+ }
100
+ return $uv.deepMerge(style, $uv.addStyle(p.customStyle))
101
+ })
102
+ </script>
103
+
104
+ <style lang="scss">
105
+ @import '../../styles/components.scss';
106
+
107
+ .uv-status-bar {
108
+ /* #ifndef APP-NVUE */
109
+ width: 100%;
110
+ flex-shrink: 0;
111
+ /* #endif */
112
+ }
113
+
114
+ /* #ifndef APP-NVUE */
115
+ /* #ifdef APP || MP */
116
+ .uv-status-bar {
117
+ height: var(--status-bar-height);
118
+ }
119
+ /* #endif */
120
+ /* #endif */
121
+ </style>
@@ -0,0 +1,84 @@
1
+ // @ts-nocheck
2
+
3
+ import config from '../../utils/config';
4
+
5
+ const props = {
6
+ // 滑块的移动过渡时间,单位ms
7
+ duration: {
8
+ type: Number,
9
+ default: 300,
10
+ },
11
+ // tabs标签数组
12
+ list: {
13
+ type: Array,
14
+ default: () => [],
15
+ },
16
+ // 滑块颜色
17
+ lineColor: {
18
+ type: String,
19
+ default: '#3c9cff',
20
+ },
21
+ // 菜单选择中时的样式
22
+ activeStyle: {
23
+ type: [String, Object],
24
+ default: () => ({
25
+ color: '#303133',
26
+ }),
27
+ },
28
+ // 菜单非选中时的样式
29
+ inactiveStyle: {
30
+ type: [String, Object],
31
+ default: () => ({
32
+ color: '#606266',
33
+ }),
34
+ },
35
+ // 滑块长度
36
+ lineWidth: {
37
+ type: [String, Number],
38
+ default: 20,
39
+ },
40
+ // 滑块高度
41
+ lineHeight: {
42
+ type: [String, Number],
43
+ default: 3,
44
+ },
45
+ // 滑块背景显示大小,当滑块背景设置为图片时使用
46
+ lineBgSize: {
47
+ type: String,
48
+ default: 'cover',
49
+ },
50
+ // 菜单item的样式
51
+ itemStyle: {
52
+ type: [String, Object],
53
+ default: () => ({
54
+ height: '44px',
55
+ }),
56
+ },
57
+ // 菜单是否可滚动
58
+ scrollable: {
59
+ type: Boolean,
60
+ default: true,
61
+ },
62
+ // 当前选中标签的索引
63
+ current: {
64
+ type: [Number, String],
65
+ default: 0,
66
+ },
67
+ // 默认读取的键名
68
+ keyName: {
69
+ type: String,
70
+ default: 'name',
71
+ },
72
+ customStyle: {
73
+ type: [Object, String],
74
+ default: () => ({}),
75
+ },
76
+ customClass: {
77
+ type: String,
78
+ default: '',
79
+ },
80
+
81
+ ...config.props.tabs,
82
+ };
83
+
84
+ export default props;
@@ -0,0 +1,432 @@
1
+ <template>
2
+ <view
3
+ class="uv-tabs"
4
+ :class="[p.customClass]"
5
+ :style="[$uv.addStyle(p.customStyle)]"
6
+ >
7
+ <view class="uv-tabs__wrapper">
8
+ <slot name="left" />
9
+ <view class="uv-tabs__wrapper__scroll-view-wrapper">
10
+ <scroll-view
11
+ <!-- #ifdef UNI-APP-X -->
12
+ :direction="p.scrollable ? 'horizontal' : 'none'"
13
+ <!-- #endif -->
14
+ <!-- #ifndef UNI-APP-X -->
15
+ :scroll-x="p.scrollable"
16
+ <!-- #endif -->
17
+ <!-- #ifdef APP-NVUE -->
18
+ ref="scrollRef"
19
+ <!-- #endif -->
20
+ <!-- #ifdef WEB || H5 -->
21
+ ref="scrollFrameRef"
22
+ <!-- #endif -->
23
+ class="uv-tabs__wrapper__scroll-view"
24
+ :scroll-left="scrollLeft"
25
+ :scroll-with-animation="true"
26
+ :show-scrollbar="false"
27
+ >
28
+ <view
29
+ class="uv-tabs__wrapper__nav"
30
+ :style="[navStyle]"
31
+ >
32
+ <view
33
+ v-for="(item, index) in p.list"
34
+ :key="index"
35
+ <!-- #ifdef APP-NVUE -->
36
+ ref="itemRefs"
37
+ <!-- #endif -->
38
+ class="uv-tabs__wrapper__nav__item"
39
+ :class="[
40
+ `uv-tabs__wrapper__nav__item-${index}`,
41
+ item.disabled ? 'uv-tabs__wrapper__nav__item--disabled' : ''
42
+ ]"
43
+ :style="[itemFlexStyle, $uv.addStyle(p.itemStyle)]"
44
+ @tap="clickHandler(item, index)"
45
+ >
46
+ <text
47
+ class="uv-tabs__wrapper__nav__item__text"
48
+ :class="[item.disabled ? 'uv-tabs__wrapper__nav__item__text--disabled' : '']"
49
+ :style="[textStyle(index)]"
50
+ >{{ item[p.keyName] }}</text>
51
+ <uv-badge
52
+ :show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
53
+ :is-dot="item.badge && item.badge.isDot"
54
+ :value="item.badge && item.badge.value"
55
+ :max="item.badge && item.badge.max"
56
+ :type="item.badge && item.badge.type"
57
+ :show-zero="item.badge && item.badge.showZero"
58
+ :bg-color="item.badge && item.badge.bgColor"
59
+ :color="item.badge && item.badge.color"
60
+ :shape="item.badge && item.badge.shape"
61
+ :number-type="item.badge && item.badge.numberType"
62
+ :inverted="item.badge && item.badge.inverted"
63
+ custom-style="margin-left: 4px;"
64
+ ></uv-badge>
65
+ </view>
66
+ <view
67
+ v-if="Number(p.lineHeight) > 0 && Number(p.lineWidth) > 0"
68
+ class="uv-tabs__wrapper__nav__line"
69
+ :style="[lineStyle]"
70
+ ></view>
71
+ </view>
72
+ </scroll-view>
73
+ </view>
74
+ <slot name="right" />
75
+ </view>
76
+ </view>
77
+ </template>
78
+
79
+ <script setup lang="ts">
80
+ import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
81
+ import { $uv } from '../../utils'
82
+ import props from './props'
83
+ import { getRect, getRectNvue } from '../../utils/compat'
84
+
85
+ defineOptions({
86
+ name: 'uv-tabs',
87
+ // #ifdef UNI-APP-X
88
+ styleIsolation: 'app-and-page',
89
+ // #endif
90
+ virtualHost: true,
91
+ })
92
+
93
+ /**
94
+ * Tabs 标签
95
+ * @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。
96
+ * @tutorial https://www.uvui.cn/components/tabs.html
97
+ * @property {Array} list 标签数组,元素为对象,如[{name: '推荐'}]
98
+ * @property {String | Number} duration 滑块移动一次所需的时间,单位ms(默认 300 )
99
+ * @property {String | Object} activeStyle 菜单选中时的样式(默认{ color: '#303133' })
100
+ * @property {String | Object} inactiveStyle 菜单非选中时的样式(默认{ color: '#606266' })
101
+ * @property {String | Number} lineWidth 滑块长度(默认 20)
102
+ * @property {String | Number} lineHeight 滑块高度(默认 3)
103
+ * @property {String} lineColor 滑块颜色(默认:'#3c9cff')
104
+ * @property {String} lineBgSize 滑块背景显示大小,当滑块背景设置为图片时使用(默认 cover)
105
+ * @property {String | Object} itemStyle 菜单item的样式(默认 { height: '44px' })
106
+ * @property {Boolean} scrollable 菜单是否可滚动,选项很少的时候设置为false整个tabs自动居中显示(默认:true)
107
+ * @property {String | Number} current 当前选中标签的索引(默认 0 )
108
+ * @property {String} keyName 从list元素对象中读取的键名(默认 'name' )
109
+ * @property {Object} customStyle 自定义外部样式
110
+ * @event {Function} change 标签改变时触发 index: 点击了第几个tab,索引从0开始
111
+ * @event {Function} click 点击标签时触发 index: 点击了第几个tab,索引从0开始
112
+ * @example <uv-tabs :list="list" :current="current" @change="change"></uv-tabs>
113
+ */
114
+ const p = defineProps(props)
115
+
116
+ const emit = defineEmits(['click', 'change'])
117
+
118
+ const instance = getCurrentInstance()
119
+
120
+ // 第一次移动滑块时不需要过渡动画
121
+ const firstTime = ref(true)
122
+ const scrollLeft = ref(0)
123
+ const lineOffsetLeft = ref(0)
124
+ const innerCurrent = ref(0)
125
+ // 导航内容总宽度(可滚动模式由测量值决定,保证内容宽度大于容器宽度才可滚动)
126
+ const navWidth = ref(0)
127
+
128
+ // #ifdef APP-NVUE
129
+ // nvue 不支持 uni.createSelectorQuery,使用 dom 模块对 ref 节点测量
130
+ const scrollRef = ref<any>(null)
131
+ const itemRefs = ref<any[]>([])
132
+ // #endif
133
+
134
+ // #ifdef WEB || H5
135
+ // uni-app x 的 H5 运行时对 scroll-left 响应式更新不稳定(x App 用 direction),
136
+ // H5 下直接同步内部滚动容器的 scrollLeft,保证各端滚动定位一致。
137
+ const scrollFrameRef = ref<any>(null)
138
+ const applyH5ScrollLeft = (value: number) => {
139
+ nextTick(() => {
140
+ const frame: any = scrollFrameRef.value
141
+ const root: any = frame && frame.$el ? frame.$el : frame
142
+ if (!root || typeof root.querySelectorAll !== 'function') return
143
+ const nodes: any[] = root.querySelectorAll('.uni-scroll-view')
144
+ const scroller: any = nodes && nodes.length > 1 ? nodes[1] : nodes && nodes[0]
145
+ if (scroller) scroller.scrollLeft = value
146
+ })
147
+ }
148
+ // #endif
149
+
150
+ // 非响应式的测量结果,仅用于计算滑块位置与滚动位置
151
+ let itemRects: any[] = []
152
+ let tabsRect: any = {
153
+ left: 0,
154
+ right: 0,
155
+ width: 0
156
+ }
157
+ let scrollViewWidth = 0
158
+ let isMounted = false
159
+
160
+ const navStyle = computed(() => {
161
+ if (!p.scrollable) {
162
+ return { flex: 1 }
163
+ }
164
+ // 可滚动模式下显式设置内容总宽度,避免各端 scroll-view 对横向内容布局差异导致无法滚动
165
+ return navWidth.value > 0 ? { width: `${navWidth.value}px` } : {}
166
+ })
167
+ const itemFlexStyle = computed(() => (p.scrollable ? {} : { flex: 1 }))
168
+
169
+ const lineStyle = computed(() => {
170
+ const style: any = {
171
+ width: $uv.addUnit(p.lineWidth),
172
+ transform: `translate(${lineOffsetLeft.value}px)`,
173
+ transitionDuration: `${firstTime.value ? 0 : Number(p.duration) || 0}ms`,
174
+ height: firstTime.value ? 0 : $uv.addUnit(p.lineHeight),
175
+ // 使用 background 简写而不是 backgroundColor:lineColor 支持传入背景图(url(...))
176
+ background: p.lineColor,
177
+ backgroundSize: p.lineBgSize
178
+ }
179
+ return style
180
+ })
181
+
182
+ const textStyle = (index: number) => {
183
+ const item = p.list[index] as any
184
+ // 取当前是否激活的样式
185
+ const style: any =
186
+ (index === innerCurrent.value ? $uv.addStyle(p.activeStyle) : $uv.addStyle(p.inactiveStyle)) || {}
187
+ const extra: any = {}
188
+ // 如果当前菜单被禁用,则加上对应颜色
189
+ if (item && item.disabled) {
190
+ extra.color = '#c8c9cc'
191
+ }
192
+ return $uv.deepMerge(style, extra)
193
+ }
194
+
195
+ // 获取单个节点的尺寸(返回 unbounded 的 rect 对象)
196
+ const queryRect = (selector: string) => {
197
+ return getRect(instance ? instance.proxy : null, selector)
198
+ }
199
+
200
+ // 获取导航菜单 scroll-view 的尺寸
201
+ const getTabsRect = () => {
202
+ // #ifdef APP-NVUE
203
+ return getRectNvue(scrollRef.value)
204
+ // #endif
205
+ // #ifndef APP-NVUE
206
+ return queryRect('.uv-tabs__wrapper__scroll-view')
207
+ // #endif
208
+ }
209
+
210
+ // 获取所有标签的尺寸(selectAll,返回顺序与 list 一致)
211
+ const getAllItemRect = () => {
212
+ // #ifdef APP-NVUE
213
+ const refs = Array.isArray(itemRefs.value) ? itemRefs.value : []
214
+ if (!refs.length) return Promise.resolve([])
215
+ return Promise.all(refs.map((item: any) => getRectNvue(item)))
216
+ // #endif
217
+ // #ifndef APP-NVUE
218
+ return getRect(instance ? instance.proxy : null, '.uv-tabs__wrapper__nav__item', true)
219
+ // #endif
220
+ }
221
+
222
+ // 设置滑块的位置
223
+ const setLineLeft = () => {
224
+ const tabItem = itemRects[innerCurrent.value]
225
+ if (!tabItem || !tabItem.width) return
226
+ // 获取滑块该移动的位置
227
+ let offsetLeft = 0
228
+ for (let i = 0; i < innerCurrent.value; i++) {
229
+ const rect = itemRects[i]
230
+ if (rect) offsetLeft += rect.width
231
+ }
232
+ // 获取下划线的数值px表示法
233
+ let lineWidth = $uv.getPx(p.lineWidth)
234
+ if ($uv.test.number(p.lineWidth) && $uv.config.unit) {
235
+ lineWidth = $uv.getPx(`${p.lineWidth}${$uv.config.unit}`)
236
+ }
237
+ lineOffsetLeft.value = offsetLeft + (tabItem.width - lineWidth) / 2
238
+ // 第一次移动滑块,需要等过渡结束后再开启动画
239
+ if (firstTime.value) {
240
+ setTimeout(() => {
241
+ firstTime.value = false
242
+ }, 20)
243
+ }
244
+ }
245
+
246
+ // 将激活的 tab 移动到 scroll-view 的中间
247
+ const setScrollLeft = () => {
248
+ const tabRect = itemRects[innerCurrent.value]
249
+ if (!tabRect || !tabsRect.width) return
250
+ // 累加得到当前 item 到左边的距离
251
+ let offsetLeft = 0
252
+ for (let i = 0; i < innerCurrent.value; i++) {
253
+ const rect = itemRects[i]
254
+ if (rect) offsetLeft += rect.width
255
+ }
256
+ const windowWidth = $uv.sys().windowWidth || 0
257
+ // nvue 的 dom.getComponentRect 在部分基座下只返回 width/height,缺失 left/right 时兜底:
258
+ // right 用 left + width 补齐,left 缺失按 0 处理,避免 NaN 导致 scrollLeft 失效。
259
+ const tabsWidth = Number(tabsRect.width) || 0
260
+ const tabsLeft = typeof tabsRect.left === 'number' && isFinite(tabsRect.left) ? tabsRect.left : 0
261
+ const tabsRight =
262
+ typeof tabsRect.right === 'number' && isFinite(tabsRect.right) ? tabsRect.right : tabsLeft + tabsWidth
263
+ let left =
264
+ offsetLeft -
265
+ (tabsWidth - tabRect.width) / 2 -
266
+ (windowWidth - tabsRight) / 2 +
267
+ tabsLeft / 2
268
+ if (!isFinite(left)) left = 0
269
+ // 限制 scrollLeft 的最大值为整个内容宽度减去 scroll-view 的宽度
270
+ left = Math.min(left, scrollViewWidth - tabsWidth)
271
+ scrollLeft.value = Math.max(0, left)
272
+ // #ifdef WEB || H5
273
+ applyH5ScrollLeft(scrollLeft.value)
274
+ // #endif
275
+ }
276
+
277
+ // 获取所有标签的尺寸并重新定位滑块
278
+ const resize = () => {
279
+ // 组件未挂载时 DOM 还不存在,跳过(由 onMounted 中的 init 负责首次测量)
280
+ if (!isMounted) return
281
+ if (!p.list || p.list.length === 0) return
282
+ return Promise.all([getTabsRect(), getAllItemRect()]).then((results: any[]) => {
283
+ const tabs = results[0]
284
+ const rects = Array.isArray(results[1]) ? results[1] : []
285
+ if (tabs) tabsRect = tabs
286
+ itemRects = rects
287
+ scrollViewWidth = 0
288
+ rects.forEach((rect: any) => {
289
+ if (rect) scrollViewWidth += rect.width
290
+ })
291
+ navWidth.value = scrollViewWidth
292
+ setLineLeft()
293
+ setScrollLeft()
294
+ })
295
+ }
296
+
297
+ const init = () => {
298
+ $uv.sleep().then(() => {
299
+ resize()
300
+ })
301
+ }
302
+
303
+ // 点击某一个标签
304
+ const clickHandler = (item: any, index: number) => {
305
+ const payload = {
306
+ ...item,
307
+ index
308
+ }
309
+ // 因为标签可能为 disabled 状态,所以 click 是一定会发出的,但是 change 事件是需要可用的状态才发出
310
+ emit('click', payload)
311
+ // 如果 disabled 状态,返回
312
+ if (item && item.disabled) return
313
+ if (innerCurrent.value !== index) {
314
+ emit('change', payload)
315
+ }
316
+ innerCurrent.value = index
317
+ nextTick(() => {
318
+ resize()
319
+ })
320
+ }
321
+
322
+ watch(
323
+ () => p.current,
324
+ (newValue: any) => {
325
+ const value = Number(newValue) || 0
326
+ // 内外部值不相等时,才尝试移动滑块
327
+ if (value !== innerCurrent.value) {
328
+ innerCurrent.value = value
329
+ nextTick(() => {
330
+ resize()
331
+ })
332
+ }
333
+ },
334
+ { immediate: true }
335
+ )
336
+
337
+ // list 变化时,重新渲染 list 各项信息
338
+ watch(
339
+ () => p.list,
340
+ () => {
341
+ nextTick(() => {
342
+ resize()
343
+ })
344
+ }
345
+ )
346
+
347
+ onMounted(() => {
348
+ isMounted = true
349
+ init()
350
+ })
351
+
352
+ defineExpose({
353
+ init,
354
+ resize,
355
+ setLineLeft,
356
+ setScrollLeft
357
+ })
358
+ </script>
359
+
360
+ <style lang="scss">
361
+ @import '../../styles/components.scss';
362
+ @import '../../styles/color.scss';
363
+
364
+ .uv-tabs {
365
+ display: flex;
366
+ flex-direction: column;
367
+ }
368
+
369
+ .uv-tabs__wrapper {
370
+ display: flex;
371
+ flex-direction: row;
372
+ align-items: center;
373
+ }
374
+
375
+ .uv-tabs__wrapper__scroll-view-wrapper {
376
+ flex: 1;
377
+ /* #ifdef WEB || H5 || MP */
378
+ overflow: auto hidden;
379
+ /* #endif */
380
+ }
381
+
382
+ .uv-tabs__wrapper__scroll-view {
383
+ flex: 1;
384
+ }
385
+
386
+ .uv-tabs__wrapper__nav {
387
+ display: flex;
388
+ flex-direction: row;
389
+ position: relative;
390
+ /* #ifndef APP-NVUE */
391
+ flex-shrink: 0;
392
+ /* #endif */
393
+ }
394
+
395
+ .uv-tabs__wrapper__nav__item {
396
+ padding: 0 11px;
397
+ display: flex;
398
+ flex-direction: row;
399
+ align-items: center;
400
+ justify-content: center;
401
+ /* #ifndef APP-NVUE */
402
+ flex-shrink: 0;
403
+ /* #endif */
404
+ }
405
+
406
+ .uv-tabs__wrapper__nav__item--disabled {
407
+ /* #ifdef WEB || H5 || MP */
408
+ cursor: not-allowed;
409
+ /* #endif */
410
+ }
411
+
412
+ .uv-tabs__wrapper__nav__item__text {
413
+ font-size: 15px;
414
+ color: $uv-content-color;
415
+ }
416
+
417
+ .uv-tabs__wrapper__nav__item__text--disabled {
418
+ color: $uv-disabled-color !important;
419
+ }
420
+
421
+ .uv-tabs__wrapper__nav__line {
422
+ height: 3px;
423
+ background-color: $uv-primary;
424
+ width: 30px;
425
+ position: absolute;
426
+ left: 0;
427
+ bottom: 2px;
428
+ border-radius: 100px;
429
+ transition-property: transform;
430
+ transition-duration: 300ms;
431
+ }
432
+ </style>
@@ -0,0 +1,38 @@
1
+ // @ts-nocheck
2
+
3
+ import config from '../../utils/config';
4
+
5
+ const props = {
6
+ show: {
7
+ type: Boolean,
8
+ default: false,
9
+ },
10
+ mode: {
11
+ type: [Array, String, null],
12
+ default: () => 'fade',
13
+ },
14
+ duration: {
15
+ type: [String, Number],
16
+ default: 300,
17
+ },
18
+ timingFunction: {
19
+ type: String,
20
+ default: 'ease-out',
21
+ },
22
+ customStyle: {
23
+ type: [Object, String],
24
+ default: () => ({}),
25
+ },
26
+ customClass: {
27
+ type: [String, Array],
28
+ default: '',
29
+ },
30
+ cellChild: {
31
+ type: Boolean,
32
+ default: false,
33
+ },
34
+
35
+ ...config.props.transition,
36
+ };
37
+
38
+ export default props;