tang-ui-x 1.0.0

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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/components/TActionSheet/index.uvue +170 -0
  4. package/components/TActionSheet/type.uts +29 -0
  5. package/components/TAvatar/index.uvue +156 -0
  6. package/components/TAvatar/type.uts +54 -0
  7. package/components/TBadge/index.uvue +152 -0
  8. package/components/TBadge/type.uts +48 -0
  9. package/components/TButton/README.md +111 -0
  10. package/components/TButton/index.uvue +380 -0
  11. package/components/TButton/type.uts +95 -0
  12. package/components/TCard/index.uvue +174 -0
  13. package/components/TCard/type.uts +50 -0
  14. package/components/TCell/index.uvue +49 -0
  15. package/components/TCheckbox/index.uvue +187 -0
  16. package/components/TCheckboxGroup/index.uvue +139 -0
  17. package/components/TCheckboxGroup/type.uts +26 -0
  18. package/components/TCol/index.uvue +82 -0
  19. package/components/TCol/type.uts +30 -0
  20. package/components/TCollapse/index.uvue +93 -0
  21. package/components/TCollapse/type.uts +36 -0
  22. package/components/TCollapseItem/index.uvue +194 -0
  23. package/components/TCollapseItem/type.uts +25 -0
  24. package/components/TDialog/index.uvue +386 -0
  25. package/components/TDialog/type.uts +84 -0
  26. package/components/TDivider/index.uvue +235 -0
  27. package/components/TDivider/type.uts +91 -0
  28. package/components/TEmpty/index.uvue +128 -0
  29. package/components/TErrorState/index.uvue +57 -0
  30. package/components/TGrid/index.uvue +115 -0
  31. package/components/TGrid/type.uts +77 -0
  32. package/components/TGridItem/index.uvue +243 -0
  33. package/components/TGridItem/type.uts +64 -0
  34. package/components/TIcon/index.uvue +96 -0
  35. package/components/TImage/index.uvue +255 -0
  36. package/components/TImage/type.uts +146 -0
  37. package/components/TInput/README.md +119 -0
  38. package/components/TInput/index.uvue +376 -0
  39. package/components/TInput/type.uts +138 -0
  40. package/components/TList/index.uvue +82 -0
  41. package/components/TList/type.uts +68 -0
  42. package/components/TListItem/index.uvue +161 -0
  43. package/components/TListItem/type.uts +49 -0
  44. package/components/TLoading/index.uvue +153 -0
  45. package/components/TLoading/type.uts +43 -0
  46. package/components/TNavBar/index.uvue +120 -0
  47. package/components/TNavBar/type.uts +22 -0
  48. package/components/TNoticeBar/index.uvue +106 -0
  49. package/components/TNoticeBar/type.uts +21 -0
  50. package/components/TNumberInput/index.uvue +226 -0
  51. package/components/TPicker/index.uvue +276 -0
  52. package/components/TPicker/type.uts +105 -0
  53. package/components/TPopup/index.uvue +442 -0
  54. package/components/TProgress/index.uvue +103 -0
  55. package/components/TProgress/type.uts +64 -0
  56. package/components/TRadioButton/index.uvue +232 -0
  57. package/components/TRadioGroup/index.uvue +117 -0
  58. package/components/TRadioGroup/type.uts +25 -0
  59. package/components/TRate/index.uvue +182 -0
  60. package/components/TRow/index.uvue +105 -0
  61. package/components/TRow/type.uts +52 -0
  62. package/components/TSearchBar/index.uvue +255 -0
  63. package/components/TSearchBar/type.uts +140 -0
  64. package/components/TSelect/index.uvue +655 -0
  65. package/components/TSelect/type.uts +57 -0
  66. package/components/TSlider/index.uvue +72 -0
  67. package/components/TSlider/type.uts +21 -0
  68. package/components/TSwiper/index.uvue +222 -0
  69. package/components/TSwiper/type.uts +77 -0
  70. package/components/TSwitch/index.uvue +177 -0
  71. package/components/TSwitch/type.uts +52 -0
  72. package/components/TText/README.md +124 -0
  73. package/components/TText/index.uvue +257 -0
  74. package/components/TText/type.uts +114 -0
  75. package/components/TTextarea/index.uvue +239 -0
  76. package/components/TTextarea/type.uts +106 -0
  77. package/components/TToast/type.uts +14 -0
  78. package/components/Tabs/README.md +297 -0
  79. package/components/Tabs/index.uvue +383 -0
  80. package/components/Tabs/type.uts +10 -0
  81. package/components/Tags/README.md +297 -0
  82. package/components/Tags/index.uvue +383 -0
  83. package/components/Tags/type.uts +10 -0
  84. package/components/VbenFrom/index.uvue +392 -0
  85. package/composables/useModal.uts +294 -0
  86. package/composables/useTheme.uts +235 -0
  87. package/composables/useToast.uts +322 -0
  88. package/index.js +62 -0
  89. package/package.json +48 -0
  90. package/style/colors/index.scss +157 -0
  91. package/style/index.scss +399 -0
  92. package/types/index.uts +52 -0
  93. package/uni.scss +79 -0
  94. package/utils/color.uts +92 -0
  95. package/utils/common.uts +245 -0
  96. package/utils/dom.uts +275 -0
  97. package/utils/index.uts +10 -0
  98. package/utils/validator.uts +155 -0
@@ -0,0 +1,383 @@
1
+ <script setup lang="uts">
2
+ import type {TabItem} from './type'
3
+
4
+
5
+ /**
6
+ * Tabs 组件属性
7
+ */
8
+ type TabsProps = {
9
+ /** 选项卡数据 */
10
+ items: TabItem[]
11
+ /** 当前激活的 tab key */
12
+ activeKey?: string
13
+ /** 默认激活的 tab key */
14
+ defaultActiveKey?: string
15
+ /** 选项卡类型 */
16
+ type?: "line" | "card"
17
+ /** 选项卡位置 */
18
+ tabPosition?: "top" | "bottom"
19
+ /** 是否居中显示 */
20
+ centered?: boolean
21
+ /** 是否可滑动 */
22
+ scrollable?: boolean
23
+ /** 激活标签的颜色 */
24
+ activeColor?: string
25
+ /** 未激活标签的颜色 */
26
+ inactiveColor?: string
27
+ /** 标签大小 */
28
+ size?: "small" | "medium" | "large"
29
+ /** 是否显示动画 */
30
+ animated?: boolean
31
+ }
32
+
33
+ // v-model 绑定当前激活 key
34
+ const model = defineModel()
35
+
36
+ const props = withDefaults(defineProps<TabsProps>(), {
37
+ items: () => [],
38
+ type: 'line',
39
+ tabPosition: 'top',
40
+ centered: false,
41
+ scrollable: true,
42
+ activeColor: '#00bba7',
43
+ inactiveColor: '#666666',
44
+ size: 'medium',
45
+ animated: true,
46
+ })
47
+
48
+ const emit = defineEmits<{
49
+ /** 切换标签时触发 */
50
+ change: [key: string |number]
51
+ /** 当前激活的 tab 改变时触发 */
52
+ 'update:activeKey': [key: string |number]
53
+ /** 点击标签时触发 */
54
+ tabClick: [key: string |number, item: TabItem]
55
+ }>()
56
+
57
+ /** 获取默认激活项的 key */
58
+ const getDefaultKey = (): string => {
59
+ if (props.defaultActiveKey) {
60
+ return props.defaultActiveKey
61
+ }
62
+ if (props.items.length > 0) {
63
+ const firstEnabled = props.items.find(item => !item.disabled)
64
+ return firstEnabled ? firstEnabled.key : props.items[0].key
65
+ }
66
+ return ''
67
+ }
68
+
69
+ /** 初始化默认激活项 */
70
+ onMounted(() => {
71
+ if (!model.value) {
72
+ model.value = getDefaultKey()
73
+ }
74
+ })
75
+
76
+ /** 计算当前激活项的索引 */
77
+ const activeIndex = computed(() => {
78
+ return props.items.findIndex(item => item.key === model.value)
79
+ })
80
+
81
+ /** 获取当前激活项的 DOM 元素 */
82
+ const activeItemRef = ref<UniElement | null>(null)
83
+ const navListRef = ref<UniElement | null>(null)
84
+
85
+ /** 将十六进制颜色转换为 RGB */
86
+ const hexToRgb = (hex: string): string => {
87
+ // 移除 # 号
88
+ hex = hex.replace('#', '')
89
+
90
+ // 处理缩写形式 (#fff)
91
+ if (hex.length === 3) {
92
+ hex = hex.split('').map(char => char + char).join('')
93
+ }
94
+
95
+ // 转换为 RGB
96
+ const r = parseInt(hex.substring(0, 2), 16)
97
+ const g = parseInt(hex.substring(2, 4), 16)
98
+ const b = parseInt(hex.substring(4, 6), 16)
99
+
100
+ return `${r}, ${g}, ${b}`
101
+ }
102
+
103
+ /** 计算激活颜色的 RGB 值 */
104
+ const activeColorRgb = computed(() => {
105
+ // 如果是 rgba 格式,直接提取 RGB 部分
106
+ if (props.activeColor.startsWith('rgb')) {
107
+ const match = props.activeColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/)
108
+ if (match) {
109
+ return `${match[1]}, ${match[2]}, ${match[3]}`
110
+ }
111
+ }
112
+ // 如果是十六进制颜色,转换为 RGB
113
+ return hexToRgb(props.activeColor)
114
+ })
115
+
116
+ /** 点击切换 tab */
117
+ const handleClick = (item: TabItem, index: number, e: UniPointerEvent) => {
118
+ if (item.disabled) return
119
+
120
+ model.value = item.key
121
+ emit('update:activeKey', item.key)
122
+ emit('change', item.key)
123
+ emit('tabClick', item.key, item)
124
+
125
+ // 滚动到激活项
126
+ if (props.scrollable) {
127
+ nextTick(() => {
128
+ })
129
+ }
130
+ }
131
+
132
+ </script>
133
+
134
+ <template>
135
+ <view
136
+ class="nav-tabs"
137
+ :class="[type, centered ? 'centered' : '', `size-${size}`]"
138
+ :style="{
139
+ '--active-color': activeColor,
140
+ '--active-color-rgb': activeColorRgb
141
+ }"
142
+ >
143
+ <scroll-view
144
+ v-if="tabPosition === 'top'"
145
+ ref="navListRef"
146
+ class="nav-scroll"
147
+ :scroll-x="true"
148
+ :show-scrollbar="false"
149
+ >
150
+ <view class="nav-list">
151
+ <view
152
+ v-for="(item, index) in items"
153
+ :key="item.key"
154
+ class="nav-item"
155
+ :class="{
156
+ active: item.key === model,
157
+ disabled: item.disabled
158
+ }"
159
+ :ref="'navItem' + index"
160
+ @click="handleClick(item, index, $event)"
161
+ >
162
+ <image v-if="item.icon" class="nav-icon" :src="item.icon" mode="aspectFit" />
163
+
164
+ <text
165
+ class="nav-label"
166
+ :style="{ color: item.key === model ? activeColor : inactiveColor }"
167
+ >
168
+ {{ item.label }}
169
+ </text>
170
+
171
+ <view v-if="item.badge" class="nav-badge">
172
+ {{ item.badge }}
173
+ </view>
174
+ </view>
175
+ </view>
176
+ </scroll-view>
177
+
178
+ <!-- 内容插槽 -->
179
+ <view class="tab-content">
180
+ <slot :active-key="model"></slot>
181
+ </view>
182
+
183
+ <!-- 底部标签 -->
184
+ <scroll-view
185
+ v-if="tabPosition === 'bottom'"
186
+ ref="navListRef"
187
+ class="nav-scroll"
188
+ :scroll-x="true"
189
+ :show-scrollbar="false"
190
+ >
191
+ <view class="nav-list">
192
+ <view
193
+ v-for="(item, index) in items"
194
+ :key="item.key"
195
+ class="nav-item"
196
+ :class="{
197
+ active: item.key === model,
198
+ disabled: item.disabled
199
+ }"
200
+ @click="handleClick(item, index, $event)"
201
+ >
202
+ <image v-if="item.icon" class="nav-icon" :src="item.icon" mode="aspectFit" />
203
+
204
+ <text
205
+ class="nav-label"
206
+ :style="{ color: item.key === model ? activeColor : inactiveColor }"
207
+ >
208
+ {{ item.label }}
209
+ </text>
210
+
211
+ <view v-if="item.badge" class="nav-badge">
212
+ {{ item.badge }}
213
+ </view>
214
+ </view>
215
+
216
+ <!-- 滑动指示器 -->
217
+ <view
218
+ v-if="type === 'line' && animated && activeIndex >= 0"
219
+ class="nav-line"
220
+ :style="[{ backgroundColor: activeColor }]"
221
+ ></view>
222
+ </view>
223
+ </scroll-view>
224
+ </view>
225
+ </template>
226
+
227
+ <style scoped lang="scss">
228
+ .nav-tabs {
229
+ width: 100%;
230
+ background-color: #fff;
231
+ overflow: hidden;
232
+
233
+ &.centered {
234
+ .nav-list {
235
+ justify-content: center;
236
+ }
237
+ }
238
+
239
+ .nav-scroll {
240
+ width: 100%;
241
+ white-space: nowrap;
242
+ }
243
+
244
+ .nav-list {
245
+ display: flex;
246
+ align-items: center;
247
+ flex-direction: row;
248
+ position: relative;
249
+ padding: 20rpx 0;
250
+ overflow: overlay;
251
+ scrollbar-width: none;
252
+
253
+ &::-webkit-scrollbar {
254
+ display: none;
255
+ }
256
+ }
257
+
258
+ .nav-item {
259
+ position: relative;
260
+ display: inline-flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ padding: 16rpx 28rpx;
264
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
265
+ transform-origin: center;
266
+
267
+ &.active {
268
+ transform: scale(1.05);
269
+ }
270
+
271
+ &.disabled {
272
+ opacity: 0.5;
273
+ cursor: not-allowed;
274
+ }
275
+
276
+ .nav-icon {
277
+ width: 36rpx;
278
+ height: 36rpx;
279
+ margin-right: 8rpx;
280
+ transition: all 0.3s;
281
+ }
282
+
283
+ .nav-label {
284
+ font-size: 28rpx;
285
+ transition: color 0.3s, transform 0.3s;
286
+ }
287
+
288
+ .nav-badge {
289
+ position: absolute;
290
+ top: 6rpx;
291
+ right: 12rpx;
292
+ min-width: 28rpx;
293
+ height: 28rpx;
294
+ border-radius: 14rpx;
295
+ background-color: #f56c6c;
296
+ color: #fff;
297
+ font-size: 20rpx;
298
+ line-height: 28rpx;
299
+ text-align: center;
300
+ padding: 0 6rpx;
301
+ transform: translateX(30%);
302
+ }
303
+ }
304
+
305
+ /* 滑动指示器 */
306
+ .nav-line {
307
+ position: absolute;
308
+ bottom: 0;
309
+ height: 4rpx;
310
+ border-radius: 2rpx;
311
+ background-color: var(--active-color);
312
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
313
+ opacity: 0;
314
+
315
+ &.animated {
316
+ opacity: 1;
317
+ }
318
+ }
319
+
320
+ /* 卡片样式 */
321
+ &.card {
322
+ .nav-list {
323
+ padding: 16rpx 24rpx;
324
+ background-color: #f5f5f5;
325
+ border-radius: 12rpx 12rpx 0 0;
326
+ }
327
+
328
+ .nav-item {
329
+ border: 1rpx solid #e8e8e8;
330
+ border-radius: 8rpx;
331
+ margin: 0 8rpx;
332
+ padding: 16rpx 32rpx;
333
+ background-color: #fff;
334
+ transition: all 0.3s ease;
335
+
336
+ &.active {
337
+ /* 使用 CSS 变量控制的激活颜色 */
338
+ background-color: rgba(var(--active-color-rgb, 0, 187, 167), 0.1);
339
+ border-color: var(--active-color);
340
+ color: var(--active-color);
341
+ transform: translateY(-2rpx);
342
+ box-shadow: 0 4rpx 12rpx rgba(var(--active-color-rgb, 0, 187, 167), 0.2);
343
+ }
344
+ }
345
+ }
346
+
347
+ /* 尺寸变体 */
348
+ &.size-small .nav-item {
349
+ padding: 12rpx 20rpx;
350
+
351
+ .nav-label {
352
+ font-size: 24rpx;
353
+ }
354
+ }
355
+
356
+ &.size-large .nav-item {
357
+ padding: 20rpx 40rpx;
358
+
359
+ .nav-label {
360
+ font-size: 32rpx;
361
+ }
362
+ }
363
+
364
+ /* 底部位置 */
365
+ &[tab-position="bottom"] {
366
+ flex-direction: column-reverse;
367
+
368
+ .nav-list {
369
+ border-top: 1rpx solid #e8e8e8;
370
+ border-bottom: none;
371
+ }
372
+
373
+ .nav-line {
374
+ top: 0;
375
+ bottom: auto;
376
+ }
377
+ }
378
+
379
+ .tab-content {
380
+ background-color: #fff;
381
+ }
382
+ }
383
+ </style>
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Tab 选项项
3
+ */
4
+ export type TabItem = {
5
+ key: string |number,
6
+ label: string
7
+ disabled?: boolean
8
+ badge?: number | string
9
+ icon?: string
10
+ }
@@ -0,0 +1,297 @@
1
+ # Tabs 标签页组件
2
+
3
+ 类似 Ant Design 的 Tabs 标签页组件,支持多种样式和功能。
4
+
5
+ ## 基本用法
6
+
7
+ ```vue
8
+ <script setup>
9
+ import Tabs from "@/components/Tabs";
10
+ import type { TabItem } from "@/components/Tabs";
11
+
12
+ const tabItems: TabItem[] = [
13
+ { key: "1", label: "选项卡 1" },
14
+ { key: "2", label: "选项卡 2" },
15
+ { key: "3", label: "选项卡 3" },
16
+ ];
17
+
18
+ const activeKey = ref("1");
19
+ </script>
20
+
21
+ <template>
22
+ <Tabs :items="tabItems" v-model="activeKey">
23
+ <view v-if="activeKey === '1'">内容 1</view>
24
+ <view v-if="activeKey === '2'">内容 2</view>
25
+ <view v-if="activeKey === '3'">内容 3</view>
26
+ </Tabs>
27
+ </template>
28
+ ```
29
+
30
+ ## 卡片式标签页
31
+
32
+ ```vue
33
+ <template>
34
+ <Tabs :items="tabItems" type="card" v-model="activeKey">
35
+ <view v-if="activeKey === '1'">卡片内容 1</view>
36
+ <view v-if="activeKey === '2'">卡片内容 2</view>
37
+ </Tabs>
38
+ </template>
39
+ ```
40
+
41
+ ## 带徽标和图标
42
+
43
+ ```vue
44
+ <script setup>
45
+ const tabItems: TabItem[] = [
46
+ { key: "1", label: "消息", icon: "💬", badge: 5 },
47
+ { key: "2", label: "通知", icon: "🔔", badge: 99 },
48
+ { key: "3", label: "设置", icon: "⚙️" },
49
+ ];
50
+ </script>
51
+
52
+ <template>
53
+ <Tabs :items="tabItems" />
54
+ </template>
55
+ ```
56
+
57
+ ## 禁用选项卡
58
+
59
+ ```vue
60
+ <script setup>
61
+ const tabItems: TabItem[] = [
62
+ { key: "1", label: "可用选项" },
63
+ { key: "2", label: "禁用选项", disabled: true },
64
+ { key: "3", label: "可用选项" },
65
+ ];
66
+ </script>
67
+ ```
68
+
69
+ ## 居中显示
70
+
71
+ ```vue
72
+ <template>
73
+ <Tabs :items="tabItems" centered />
74
+ </template>
75
+ ```
76
+
77
+ ## 自定义颜色
78
+
79
+ ```vue
80
+ <template>
81
+ <Tabs :items="tabItems" active-color="#52c41a" inactive-color="#999999" />
82
+ </template>
83
+ ```
84
+
85
+ ### 颜色变量机制
86
+
87
+ 组件内部使用 CSS 变量统一管理颜色,支持以下变量:
88
+
89
+ - `--active-color`:激活标签的主颜色(控制文字、边框、滑块颜色)
90
+ - `--active-color-rgb`:激活颜色的 RGB 值(用于生成透明背景)
91
+
92
+ 组件会自动将 `activeColor` prop 转换为对应的 CSS 变量,无需手动设置。
93
+
94
+ **card 类型的增强效果:**
95
+
96
+ card 类型的激活标签会自动应用:
97
+
98
+ - 透明背景色:`rgba(var(--active-color-rgb), 0.1)`
99
+ - 轻微上移效果:`translateY(-2rpx)`
100
+ - 动态阴影:`0 4rpx 12rpx rgba(var(--active-color-rgb), 0.2)`
101
+
102
+ ## 不同尺寸
103
+
104
+ ```vue
105
+ <template>
106
+ <!-- 小号 -->
107
+ <Tabs :items="tabItems" size="small" />
108
+
109
+ <!-- 中号(默认) -->
110
+ <Tabs :items="tabItems" size="medium" />
111
+
112
+ <!-- 大号 -->
113
+ <Tabs :items="tabItems" size="large" />
114
+ </template>
115
+ ```
116
+
117
+ ## 使用插槽自定义内容
118
+
119
+ ```vue
120
+ <template>
121
+ <Tabs :items="tabItems" v-model="activeKey">
122
+ <template #default="{ activeKey }">
123
+ <view class="p-4"> 当前激活: {{ activeKey }} </view>
124
+ </template>
125
+ </Tabs>
126
+ </template>
127
+ ```
128
+
129
+ ## API
130
+
131
+ ### TabItem 类型
132
+
133
+ | 属性 | 说明 | 类型 | 默认值 |
134
+ | -------- | ---------------- | ------------------ | ------- |
135
+ | key | 唯一标识(必填) | `string` | - |
136
+ | label | 标签标题(必填) | `string` | - |
137
+ | disabled | 是否禁用 | `boolean` | `false` |
138
+ | badge | 徽标数字 | `number \| string` | - |
139
+ | icon | 自定义图标 | `string` | - |
140
+
141
+ ### Props
142
+
143
+ | 属性 | 说明 | 类型 | 默认值 |
144
+ | ----------------------------- | ------------------ | -------------------------------- | ------------ |
145
+ | items | 选项卡数据 | `TabItem[]` | `[]` |
146
+ | activeKey / v-model:activeKey | 当前激活的 tab key | `string` | - |
147
+ | defaultActiveKey | 默认激活的 tab key | `string` | 第一项的 key |
148
+ | type | 选项卡类型 | `'line' \| 'card'` | `'line'` |
149
+ | tabPosition | 选项卡位置 | `'top' \| 'bottom'` | `'top'` |
150
+ | centered | 是否居中显示 | `boolean` | `false` |
151
+ | scrollable | 是否可滑动 | `boolean` | `true` |
152
+ | activeColor | 激活标签的颜色 | `string` | `'#1677ff'` |
153
+ | inactiveColor | 未激活标签的颜色 | `string` | `'#666666'` |
154
+ | size | 标签大小 | `'small' \| 'medium' \| 'large'` | `'medium'` |
155
+ | animated | 是否显示动画 | `boolean` | `true` |
156
+
157
+ ### Events
158
+
159
+ | 事件名 | 说明 | 回调参数 |
160
+ | ---------------- | ------------------------- | ------------------------------ |
161
+ | change | 切换标签时触发 | `(key: string)` |
162
+ | update:activeKey | 当前激活的 tab 改变时触发 | `(key: string)` |
163
+ | tabClick | 点击标签时触发 | `(key: string, item: TabItem)` |
164
+
165
+ ### Slots
166
+
167
+ | 插槽名 | 说明 | 参数 |
168
+ | ------- | -------- | ----------------------- |
169
+ | default | 内容区域 | `{ activeKey: string }` |
170
+
171
+ ## 完整示例
172
+
173
+ ```vue
174
+ <script setup>
175
+ import Tabs from "@/components/Tabs";
176
+ import type { TabItem } from "@/components/Tabs";
177
+
178
+ const tabItems: TabItem[] = [
179
+ { key: "tab1", label: "首页", icon: "🏠" },
180
+ { key: "tab2", label: "消息", icon: "💬", badge: 5 },
181
+ { key: "tab3", label: "禁用", disabled: true },
182
+ { key: "tab4", label: "设置", icon: "⚙️" },
183
+ ];
184
+
185
+ const activeKey = ref("tab1");
186
+
187
+ const handleChange = (key: string) => {
188
+ console.log("切换到:", key);
189
+ };
190
+
191
+ const handleTabClick = (key: string, item: TabItem) => {
192
+ console.log("点击标签:", key, item);
193
+ };
194
+ </script>
195
+
196
+ <template>
197
+ <view class="page">
198
+ <Tabs
199
+ :items="tabItems"
200
+ v-model="activeKey"
201
+ type="line"
202
+ size="medium"
203
+ active-color="#1677ff"
204
+ @change="handleChange"
205
+ @tab-click="handleTabClick"
206
+ >
207
+ <view class="tab-content">
208
+ <view v-if="activeKey === 'tab1'" class="p-4">
209
+ <text>首页内容</text>
210
+ </view>
211
+ <view v-if="activeKey === 'tab2'" class="p-4">
212
+ <text>消息内容</text>
213
+ </view>
214
+ <view v-if="activeKey === 'tab4'" class="p-4">
215
+ <text>设置内容</text>
216
+ </view>
217
+ </view>
218
+ </Tabs>
219
+ </view>
220
+ </template>
221
+
222
+ <style lang="scss" scoped>
223
+ .page {
224
+ padding: 32rpx;
225
+ }
226
+
227
+ .tab-content {
228
+ background-color: #ffffff;
229
+ min-height: 400rpx;
230
+ }
231
+ </style>
232
+ ```
233
+
234
+ ## 注意事项
235
+
236
+ 1. 每个 `TabItem` 的 `key` 必须唯一
237
+ 2. `v-model:activeKey` 支持双向绑定
238
+ 3. 滑动指示器动画仅在 `type="line"` 且 `animated=true` 时生效
239
+ 4. 使用 `scrollable` 时,标签超出屏幕宽度会自动滚动
240
+ 5. `tabPosition="bottom"` 可将标签放置在底部(适合底部导航场景)
241
+
242
+ ```ts
243
+ // 选项项
244
+
245
+ /**
246
+
247
+ * TabItem 类型定义
248
+
249
+ */
250
+
251
+ export type TabItem = {
252
+ /** 唯一标识 */
253
+
254
+ key: string;
255
+
256
+ /** 标签标题 */
257
+
258
+ label: string;
259
+
260
+ /** 是否禁用 */
261
+
262
+ disabled?: boolean;
263
+
264
+ /** 徽标数字 */
265
+
266
+ badge?: number | string;
267
+
268
+ /** 自定义图标 */
269
+
270
+ icon?: string;
271
+ };
272
+
273
+ type TabsProps = {
274
+ /** 选项卡数据 */
275
+ items: TabItem[];
276
+ /** 当前激活的 tab key */
277
+ activeKey?: string;
278
+ /** 默认激活的 tab key */
279
+ defaultActiveKey?: string;
280
+ /** 选项卡类型 */
281
+ type?: "line" | "card";
282
+ /** 选项卡位置 */
283
+ tabPosition?: "top" | "bottom";
284
+ /** 是否居中显示 */
285
+ centered?: boolean;
286
+ /** 是否可滑动 */
287
+ scrollable?: boolean;
288
+ /** 激活标签的颜色 */
289
+ activeColor?: string;
290
+ /** 未激活标签的颜色 */
291
+ inactiveColor?: string;
292
+ /** 标签大小 */
293
+ size?: "small" | "medium" | "large";
294
+ /** 是否显示动画 */
295
+ animated?: boolean;
296
+ };
297
+ ```