vdesign-ui 0.2.20 → 0.3.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 (166) hide show
  1. package/README.md +58 -58
  2. package/dist/components/actionbar/index.js +8 -8
  3. package/dist/components/actionbar/index.vue +39 -39
  4. package/dist/components/actionbar/style.less +44 -44
  5. package/dist/components/actionbar-cell/index.js +7 -7
  6. package/dist/components/actionbar-cell/index.vue +42 -42
  7. package/dist/components/actions/index.js +6 -6
  8. package/dist/components/actions/index.vue +77 -77
  9. package/dist/components/actions/style.less +109 -109
  10. package/dist/components/actions-cell/index.js +6 -6
  11. package/dist/components/actions-cell/index.vue +94 -94
  12. package/dist/components/actions-cell/style.less +38 -38
  13. package/dist/components/activityviews/index.js +8 -8
  14. package/dist/components/activityviews/index.vue +192 -192
  15. package/dist/components/activityviews/style.less +161 -161
  16. package/dist/components/badge/index.js +8 -8
  17. package/dist/components/badge/index.vue +49 -49
  18. package/dist/components/badge/style.less +54 -54
  19. package/dist/components/button/index.js +8 -8
  20. package/dist/components/button/index.vue +93 -93
  21. package/dist/components/button/style.less +558 -558
  22. package/dist/components/calendar/index-element.vue +84 -84
  23. package/dist/components/calendar/index.js +7 -7
  24. package/dist/components/calendar/index.vue +53 -53
  25. package/dist/components/calendar/style.less +138 -138
  26. package/dist/components/checkbox/index.js +8 -8
  27. package/dist/components/checkbox/index.vue +161 -161
  28. package/dist/components/checkbox/style.less +288 -288
  29. package/dist/components/checkbox-group/index.js +7 -7
  30. package/dist/components/checkbox-group/index.vue +73 -73
  31. package/dist/components/common/state/index.vue +33 -33
  32. package/dist/components/common/state/style.less +47 -47
  33. package/dist/components/data-list/index.js +10 -10
  34. package/dist/components/data-list/index.vue +19 -19
  35. package/dist/components/data-list/style.less +623 -623
  36. package/dist/components/datetime-picker/index.js +7 -7
  37. package/dist/components/datetime-picker/index.vue +39 -39
  38. package/dist/components/datetime-picker/style.less +23 -23
  39. package/dist/components/dialog/index.js +8 -8
  40. package/dist/components/dialog/index.vue +164 -164
  41. package/dist/components/dialog/overlay-manager.js +18 -18
  42. package/dist/components/dialog/style.less +138 -138
  43. package/dist/components/divider/index.js +8 -8
  44. package/dist/components/divider/index.vue +54 -54
  45. package/dist/components/divider/style.less +92 -92
  46. package/dist/components/dropdown/index.js +8 -8
  47. package/dist/components/dropdown/index.vue +218 -218
  48. package/dist/components/dropdown/style.less +432 -432
  49. package/dist/components/empty/index.js +8 -8
  50. package/dist/components/empty/index.vue +138 -138
  51. package/dist/components/empty/style.less +60 -60
  52. package/dist/components/footer/index.js +6 -6
  53. package/dist/components/footer/index.vue +33 -33
  54. package/dist/components/footer/style.less +20 -20
  55. package/dist/components/footnav/index.js +6 -6
  56. package/dist/components/footnav/index.vue +93 -93
  57. package/dist/components/footnav/style.less +22 -22
  58. package/dist/components/footnav-item/index.js +6 -6
  59. package/dist/components/footnav-item/index.vue +50 -50
  60. package/dist/components/footnav-item/style.less +39 -39
  61. package/dist/components/form/index.js +6 -6
  62. package/dist/components/form/index.vue +14 -14
  63. package/dist/components/headnav/index.js +6 -6
  64. package/dist/components/headnav/index.vue +185 -185
  65. package/dist/components/headnav/style.less +231 -231
  66. package/dist/components/icon/font/iconfont.css +163 -163
  67. package/dist/components/icon/font/iconfont.js +5 -5
  68. package/dist/components/icon/index.js +9 -9
  69. package/dist/components/icon/index.vue +96 -96
  70. package/dist/components/icon/style.less +44 -44
  71. package/dist/components/input/calcTextareaHeight.js +162 -162
  72. package/dist/components/input/index.js +8 -8
  73. package/dist/components/input/index.vue +345 -345
  74. package/dist/components/input/style.less +471 -471
  75. package/dist/components/list/index.js +8 -8
  76. package/dist/components/list/index.vue +152 -152
  77. package/dist/components/list/style.less +213 -213
  78. package/dist/components/loading/index.js +6 -6
  79. package/dist/components/loading/index.vue +68 -68
  80. package/dist/components/loading/style.less +53 -53
  81. package/dist/components/mixins/clickoutside.js +81 -81
  82. package/dist/components/mixins/dom.js +41 -41
  83. package/dist/components/mixins/languageMixin.js +43 -43
  84. package/dist/components/mixins/outlineConfigPlugin.js +45 -45
  85. package/dist/components/mixins/router-link.js +22 -22
  86. package/dist/components/mixins/themeMixin.js +43 -43
  87. package/dist/components/noticebar/index.js +8 -8
  88. package/dist/components/noticebar/index.vue +258 -258
  89. package/dist/components/noticebar/style.less +333 -333
  90. package/dist/components/overlay/index.js +8 -8
  91. package/dist/components/overlay/index.vue +184 -184
  92. package/dist/components/overlay/style.less +24 -24
  93. package/dist/components/pagebreak/index.js +6 -6
  94. package/dist/components/pagebreak/index.vue +67 -67
  95. package/dist/components/pagebreak/style.less +41 -41
  96. package/dist/components/password/index.js +8 -8
  97. package/dist/components/password/index.vue +64 -64
  98. package/dist/components/popover/index.js +8 -8
  99. package/dist/components/popover/index.vue +100 -100
  100. package/dist/components/popover/style.less +346 -346
  101. package/dist/components/popover/vue-popover.vue +314 -314
  102. package/dist/components/popup/index.js +7 -7
  103. package/dist/components/popup/index.vue +165 -165
  104. package/dist/components/popup/style.less +78 -78
  105. package/dist/components/radio/index.js +8 -8
  106. package/dist/components/radio/index.vue +184 -184
  107. package/dist/components/radio/style.less +300 -300
  108. package/dist/components/radio-group/index.js +7 -7
  109. package/dist/components/radio-group/index.vue +62 -62
  110. package/dist/components/result/index.js +8 -8
  111. package/dist/components/result/index.vue +73 -73
  112. package/dist/components/result/style.less +43 -43
  113. package/dist/components/search/index.js +8 -8
  114. package/dist/components/search/index.vue +70 -70
  115. package/dist/components/selector/index.js +8 -8
  116. package/dist/components/selector/index.vue +161 -161
  117. package/dist/components/selector/style.less +484 -484
  118. package/dist/components/skeleton/index.js +6 -6
  119. package/dist/components/skeleton/index.vue +207 -207
  120. package/dist/components/skeleton/style.less +196 -196
  121. package/dist/components/slider/draggable.js +49 -49
  122. package/dist/components/slider/index.js +6 -6
  123. package/dist/components/slider/index.vue +167 -167
  124. package/dist/components/slider/style.less +99 -99
  125. package/dist/components/slider/utils.js +59 -59
  126. package/dist/components/step/index.js +7 -7
  127. package/dist/components/step/index.vue +48 -48
  128. package/dist/components/step/style.less +66 -66
  129. package/dist/components/step-item/index.js +7 -7
  130. package/dist/components/step-item/index.vue +126 -126
  131. package/dist/components/step-item/style.less +399 -399
  132. package/dist/components/stepper/index.js +8 -8
  133. package/dist/components/stepper/index.vue +150 -150
  134. package/dist/components/style/index.vue +42 -42
  135. package/dist/components/switch/index.js +8 -8
  136. package/dist/components/switch/index.vue +72 -72
  137. package/dist/components/switch/style.less +56 -56
  138. package/dist/components/tab/index.js +7 -7
  139. package/dist/components/tab/index.vue +97 -97
  140. package/dist/components/tabs/index.js +8 -8
  141. package/dist/components/tabs/index.vue +356 -356
  142. package/dist/components/tabs/style.less +504 -504
  143. package/dist/components/tag/index.js +6 -6
  144. package/dist/components/tag/index.vue +64 -64
  145. package/dist/components/tag/style.less +210 -210
  146. package/dist/components/title/index.js +8 -8
  147. package/dist/components/title/index.vue +99 -99
  148. package/dist/components/title/style.less +187 -187
  149. package/dist/components/toast/index.js +139 -139
  150. package/dist/components/toast/index.vue +50 -50
  151. package/dist/components/toast/style.less +58 -58
  152. package/dist/components/transition/index.js +8 -8
  153. package/dist/components/transition/index.vue +13 -13
  154. package/dist/components/transition/style.less +208 -208
  155. package/dist/components/upload/index.js +6 -6
  156. package/dist/components/upload/index.vue +106 -106
  157. package/dist/components/upload/style.less +147 -147
  158. package/dist/components/utils/assist.js +34 -34
  159. package/dist/components/utils/env.js +21 -21
  160. package/dist/locale/ar.js +97 -97
  161. package/dist/locale/en.js +97 -97
  162. package/dist/locale/zh.js +97 -97
  163. package/dist/token.css +9 -9
  164. package/dist/vdesign-ui.common.js +83 -83
  165. package/dist/vdesign-ui.umd.js +83 -83
  166. package/package.json +113 -113
@@ -1,356 +1,356 @@
1
- <template>
2
- <div class="vd-tabs" :class="stickyClasses">
3
- <div class="vd-tabs__wrap" :class="menuClasses" ref="wrap">
4
- <div class="vd-tabs__scroll" ref="scroll">
5
- <div class="vd-tabs__scroll--left" v-show="firstTabOut"></div>
6
- <div class="vd-tabs__nav" :class="[barType, scrollspyClasses]" ref="nav">
7
- <div
8
- v-for="(tab, index) in tabs"
9
- :key="tab.name !== undefined ? tab.name : index"
10
- :class="[tabClasses(tab, index), lineClasses]"
11
- @click="onClick(tab, index)"
12
- ref="tabItems"
13
- >
14
- <span class="vd-tab__text" :class="ellipsisClasses" ref="title"
15
- >
16
- {{ tab.title }}
17
- <!-- <vd-icon
18
- class="vd-tab__arrow"
19
- v-if="tab.arrow"
20
- name="icon_btn_moredown"
21
- ></vd-icon> -->
22
- </span>
23
- </div>
24
- </div>
25
- <div class="vd-tabs__scroll--right" v-show="lastTabOut"></div>
26
- </div>
27
- <div class="vd-tabs__menu--right" :class="{'vd-tabs__menu--margin':lastTabOut}" v-if="menu">
28
- <vd-icon
29
- :name="menuIconComputed"
30
- class="vd-tabs__menu--btn"
31
- @click="emitMenuClick"
32
- ></vd-icon>
33
- </div>
34
- </div>
35
- <div class="vd-tabs__content" ref="content" :class="{'vd-tabs__content--animated':animated}">
36
- <div v-if="animated" :class="trackClasses" :style="trackStyle">
37
- <slot />
38
- </div>
39
- <slot v-else />
40
- </div>
41
- </div>
42
- </template>
43
-
44
- <script>
45
- import VdIcon from "../icon";
46
- import languageMixin from '../mixins/languageMixin.js';
47
-
48
- const prefixCls = "vd-tabs";
49
- export default {
50
- name: "vd-tabs",
51
- mixins: [languageMixin],
52
- components: {
53
- VdIcon,
54
- },
55
- props: {
56
- value: [String, Number],
57
- tabsType: {
58
- type: String,
59
- default: "primary",
60
- },
61
- menu: {
62
- type: [Boolean, String],
63
- default: false,
64
- },
65
- ellipsis: {
66
- type: Boolean,
67
- default: true,
68
- },
69
- actBorder: Boolean,
70
- backgroundColor: Boolean,
71
- divider: Boolean,
72
- sticky: Boolean,
73
- lazyRender: Boolean,
74
- animated: Boolean,
75
- scrollspy:Boolean
76
- },
77
- data() {
78
- return {
79
- tabs: [],
80
- currentName: this.value !== undefined ? this.value : 0, // 默认为 0
81
- firstTabOut: false, // 第一个标签是否超出
82
- lastTabOut: false
83
- };
84
- },
85
- watch: {
86
- // 监听 value 变化,更新 currentValue
87
- value(val) {
88
- this.currentName = val;
89
- this.setCurrentName(val);
90
- },
91
- },
92
- computed: {
93
- trackClasses() {
94
- return {
95
- 'vd-tabs__track': true,
96
- };
97
- },
98
- trackStyle() {
99
- const activeIndex = this.tabs.findIndex((tab, index) => {
100
- return this.isTabActive(tab, index);
101
- });
102
-
103
- const offset = activeIndex * 100;
104
- // 根据文本方向调整 translateX 值
105
- const translateX = this.language === 'ar' ? offset : -offset;
106
- // const translateX = -activeIndex * 100;
107
- return {
108
- transform: `translateX(${translateX}%)`,
109
- transitionDuration: '0.3s',
110
- };
111
- },
112
- // 计算菜单按钮的图标,根据 menu 属性的类型决定图标
113
- menuIconComputed() {
114
- if (typeof this.menu === "string") {
115
- return this.menu;
116
- } else {
117
- return "icon_tab_morelist"; // 默认的菜单图标名称
118
- }
119
- },
120
- // 计算滚动监视的类名
121
- scrollspyClasses() {
122
- return {
123
- [`${prefixCls}--complete`]:this.scrollspy
124
- }
125
- },
126
- // 计算标签文字是否需要省略号的类名
127
- ellipsisClasses() {
128
- return {
129
- [`vd-tab__text--ellipsis`]: this.ellipsis,
130
- };
131
- },
132
- // 计算标签包裹器的类名,根据 menu、backgroundColor 和 divider 属性来确定
133
- menuClasses() {
134
- return {
135
- [`${prefixCls}__menu`]: this.menu,
136
- [`${prefixCls}__wrap--bg`]: this.backgroundColor,
137
- "vd-hairline--bottom": this.divider,
138
- // "vd-tabs__scroll": this.firstTabOut, // 当第一个标签超出时,添加滚动类名
139
- };
140
- },
141
- // 计算标签下方线条的类名
142
- lineClasses() {
143
- return {
144
- [`vd-tab__none--line`]: !this.actBorder,
145
- };
146
- },
147
- // 根据 tabsType 属性计算标签类型的类名
148
- barType() {
149
- return {
150
- [`${prefixCls}__nav--${this.tabsType}`]: this.tabsType,
151
- };
152
- },
153
- stickyClasses() {
154
- return {
155
- [`${prefixCls}--sticky`]: this.sticky,
156
- };
157
- },
158
- },
159
- methods: {
160
- // 当菜单按钮被点击时触发事件
161
- emitMenuClick() {
162
- this.$emit("menu-click");
163
- },
164
- // 计算每个标签项的类名
165
- tabClasses(tab, index) {
166
- const isActive = this.isTabActive(tab, index);
167
- return {
168
- 'vd-tab': true,
169
- 'vd-tab--active': isActive,
170
- };
171
- },
172
- setCurrentName(name) {
173
- // 如果没有指定 name 属性,则使用索引值,确保索引值为数字类型
174
- const nameIsNumber = typeof name === 'number' || /^\d+$/.test(name);
175
- const parsedName = nameIsNumber ? Number(name) : name;
176
-
177
- const matchedTab = this.tabs.find((tab, index) => {
178
- if (tab.name !== undefined) {
179
- return tab.name === parsedName;
180
- } else {
181
- return index === parsedName;
182
- }
183
- });
184
-
185
- if (matchedTab) {
186
- this.currentName = matchedTab.name !== undefined ? matchedTab.name : this.tabs.indexOf(matchedTab);
187
- } else if (this.tabs.length > 0) {
188
- const firstTab = this.tabs[0];
189
- this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
190
- } else {
191
- this.currentName = null;
192
- }
193
- this.$emit('input', this.currentName);
194
- },
195
- isTabActive(tab, index) {
196
- if (tab.name !== undefined) {
197
- return tab.name === this.currentName;
198
- } else {
199
- return index === Number(this.currentName);
200
- }
201
- },
202
- addTab(tab) {
203
- this.tabs.push(tab);
204
- if (this.currentName === undefined) {
205
- this.setCurrentName(tab.name !== undefined ? tab.name : 0);
206
- }
207
- },
208
- removeTab(tab) {
209
- const index = this.tabs.indexOf(tab);
210
- if (index !== -1) {
211
- this.tabs.splice(index, 1);
212
- }
213
- },
214
- onClick(tab, index) {
215
- if (tab.disabled) {
216
- this.$emit('disabled', tab.name !== undefined ? tab.name : index, index);
217
- } else {
218
- const newName = tab.name !== undefined ? tab.name : index;
219
-
220
- // 始终触发 click 事件
221
- this.$emit('click', newName, index);
222
-
223
- // 检查是否需要切换标签
224
- if (newName !== this.currentName) {
225
- this.currentName = newName;
226
-
227
- // 如果 currentName 是索引值,确保为数字类型
228
- if (tab.name === undefined) {
229
- this.currentName = Number(this.currentName);
230
- }
231
-
232
- this.$emit('input', this.currentName);
233
- this.$emit('change', this.currentName, index); // 仅在切换时触发 change 事件
234
-
235
- // 切换标签后,滚动到当前标签
236
- this.$nextTick(() => {
237
- this.scrollToActiveTab();
238
- });
239
- }
240
- }
241
- },
242
- scrollToActiveTab(targetIndex = null) {
243
- const scrollWrapper = this.$refs.nav;
244
- const tabItems = this.$refs.tabItems;
245
- if (!scrollWrapper || !tabItems || tabItems.length === 0) {
246
- return;
247
- }
248
- // 如果传入 targetIndex,设置当前激活标签的 name
249
- if (targetIndex !== null) {
250
- const targetTab = this.tabs[targetIndex];
251
- if (targetTab) {
252
- this.currentName = targetTab.name !== undefined ? targetTab.name : targetIndex;
253
- } else {
254
- console.warn('Invalid target index:', targetIndex);
255
- return;
256
- }
257
- }
258
-
259
- const activeIndex = targetIndex !== null ? targetIndex : this.tabs.findIndex((tab, index) => this.isTabActive(tab, index));
260
- const activeTab = tabItems[activeIndex];
261
-
262
- setTimeout(() => {
263
- const tabOffsetLeft = activeTab.offsetLeft;
264
- const tabWidth = activeTab.offsetWidth;
265
- const wrapperWidth = scrollWrapper.offsetWidth;
266
- const scrollLeft = tabOffsetLeft - (wrapperWidth - tabWidth) / 2;
267
-
268
- scrollWrapper.scrollTo({
269
- left: scrollLeft,
270
- behavior: 'smooth'
271
- });
272
- }, 0);
273
- },
274
- checkFirstTabOverflow() {
275
- const nav = this.$refs.nav;
276
- if (nav) {
277
- if (this.language === 'ar') {
278
- // 在 RTL 模式下,scrollLeft 可能为负,判断是否滚出视口可以检测 scrollLeft 是否小于 0
279
- // this.firstTabOut = nav.scrollLeft < 0;
280
- // this.lastTabOut = nav.scrollLeft < 0;
281
-
282
- this.firstTabOut =
283
- nav.scrollWidth > nav.clientWidth &&
284
- Math.abs(nav.scrollLeft) + nav.clientWidth < nav.scrollWidth;
285
- this.lastTabOut = nav.scrollLeft < 0;
286
- } else {
287
- // LTR 模式下,滚出视口时 scrollLeft 大于 0
288
- this.firstTabOut = nav.scrollLeft > 0;
289
- this.lastTabOut =
290
- nav.scrollWidth > nav.clientWidth && nav.scrollLeft + nav.clientWidth < nav.scrollWidth;
291
- // this.firstTabOut = nav.scrollLeft > 0;
292
- // this.lastTabOut = Math.ceil(nav.scrollLeft + nav.clientWidth) < nav.scrollWidth;
293
- }
294
- }
295
- },
296
- renderTitle(titleEl, tab) {
297
- this.$nextTick(() => {
298
- const index = this.tabs.indexOf(tab);
299
- if (index !== -1 && this.$refs.title && this.$refs.title[index]) {
300
- const navTitleEl = this.$refs.title[index];
301
- // 清空导航标题元素的内容
302
- navTitleEl.innerHTML = '';
303
- // 将子组件的 title 插槽内容移动到导航标题元素中
304
- while (titleEl.firstChild) {
305
- navTitleEl.appendChild(titleEl.firstChild);
306
- }
307
- }
308
- });
309
- },
310
- /**
311
- * resize 方法
312
- * 当外层元素大小或组件显示状态变化时调用,触发重绘
313
- */
314
- resize() {
315
- // 重新计算当前激活标签的位置
316
- this.scrollToActiveTab();
317
- // 如果有其他需要重绘或重新计算的逻辑,可以在这里添加
318
- // 例如,重新计算动画轨道的位置等
319
- },
320
- /**
321
- * scrollTo 方法
322
- * 刷新页面,滚动到指定位置
323
- */
324
- scrollTo(targetIndex = null){
325
- // 重新计算当前激活标签的位置
326
- this.scrollToActiveTab(targetIndex);
327
- // 如果有其他需要重绘或重新计算的逻辑,可以在这里添加
328
- // 例如,重新计算动画轨道的位置等
329
- }
330
- },
331
- mounted() {
332
- if (this.currentName !== undefined) {
333
- this.setCurrentName(this.currentName);
334
- } else if (this.tabs.length > 0) {
335
- const firstTab = this.tabs[0];
336
- this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
337
- }
338
- // 初始加载时,滚动到当前激活的标签
339
- this.$nextTick(() => {
340
- this.scrollToActiveTab();
341
- this.checkFirstTabOverflow()
342
- if (this.$refs.nav) {
343
- this.$refs.nav.addEventListener('scroll', this.checkFirstTabOverflow);
344
- }
345
- });
346
- },
347
- beforeDestroy() {
348
- if (this.$refs.nav) {
349
- this.$refs.nav.removeEventListener('scroll', this.checkFirstTabOverflow);
350
- }
351
- },
352
- };
353
- </script>
354
- <style lang="less">
355
- @import "./style.less";
356
- </style>
1
+ <template>
2
+ <div class="vd-tabs" :class="stickyClasses">
3
+ <div class="vd-tabs__wrap" :class="menuClasses" ref="wrap">
4
+ <div class="vd-tabs__scroll" ref="scroll">
5
+ <div class="vd-tabs__scroll--left" v-show="firstTabOut"></div>
6
+ <div class="vd-tabs__nav" :class="[barType, scrollspyClasses]" ref="nav">
7
+ <div
8
+ v-for="(tab, index) in tabs"
9
+ :key="tab.name !== undefined ? tab.name : index"
10
+ :class="[tabClasses(tab, index), lineClasses]"
11
+ @click="onClick(tab, index)"
12
+ ref="tabItems"
13
+ >
14
+ <span class="vd-tab__text" :class="ellipsisClasses" ref="title"
15
+ >
16
+ {{ tab.title }}
17
+ <!-- <vd-icon
18
+ class="vd-tab__arrow"
19
+ v-if="tab.arrow"
20
+ name="icon_btn_moredown"
21
+ ></vd-icon> -->
22
+ </span>
23
+ </div>
24
+ </div>
25
+ <div class="vd-tabs__scroll--right" v-show="lastTabOut"></div>
26
+ </div>
27
+ <div class="vd-tabs__menu--right" :class="{'vd-tabs__menu--margin':lastTabOut}" v-if="menu">
28
+ <vd-icon
29
+ :name="menuIconComputed"
30
+ class="vd-tabs__menu--btn"
31
+ @click="emitMenuClick"
32
+ ></vd-icon>
33
+ </div>
34
+ </div>
35
+ <div class="vd-tabs__content" ref="content" :class="{'vd-tabs__content--animated':animated}">
36
+ <div v-if="animated" :class="trackClasses" :style="trackStyle">
37
+ <slot />
38
+ </div>
39
+ <slot v-else />
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script>
45
+ import VdIcon from "../icon";
46
+ import languageMixin from '../mixins/languageMixin.js';
47
+
48
+ const prefixCls = "vd-tabs";
49
+ export default {
50
+ name: "vd-tabs",
51
+ mixins: [languageMixin],
52
+ components: {
53
+ VdIcon,
54
+ },
55
+ props: {
56
+ value: [String, Number],
57
+ tabsType: {
58
+ type: String,
59
+ default: "primary",
60
+ },
61
+ menu: {
62
+ type: [Boolean, String],
63
+ default: false,
64
+ },
65
+ ellipsis: {
66
+ type: Boolean,
67
+ default: true,
68
+ },
69
+ actBorder: Boolean,
70
+ backgroundColor: Boolean,
71
+ divider: Boolean,
72
+ sticky: Boolean,
73
+ lazyRender: Boolean,
74
+ animated: Boolean,
75
+ scrollspy:Boolean
76
+ },
77
+ data() {
78
+ return {
79
+ tabs: [],
80
+ currentName: this.value !== undefined ? this.value : 0, // 默认为 0
81
+ firstTabOut: false, // 第一个标签是否超出
82
+ lastTabOut: false
83
+ };
84
+ },
85
+ watch: {
86
+ // 监听 value 变化,更新 currentValue
87
+ value(val) {
88
+ this.currentName = val;
89
+ this.setCurrentName(val);
90
+ },
91
+ },
92
+ computed: {
93
+ trackClasses() {
94
+ return {
95
+ 'vd-tabs__track': true,
96
+ };
97
+ },
98
+ trackStyle() {
99
+ const activeIndex = this.tabs.findIndex((tab, index) => {
100
+ return this.isTabActive(tab, index);
101
+ });
102
+
103
+ const offset = activeIndex * 100;
104
+ // 根据文本方向调整 translateX 值
105
+ const translateX = this.language === 'ar' ? offset : -offset;
106
+ // const translateX = -activeIndex * 100;
107
+ return {
108
+ transform: `translateX(${translateX}%)`,
109
+ transitionDuration: '0.3s',
110
+ };
111
+ },
112
+ // 计算菜单按钮的图标,根据 menu 属性的类型决定图标
113
+ menuIconComputed() {
114
+ if (typeof this.menu === "string") {
115
+ return this.menu;
116
+ } else {
117
+ return "icon_tab_morelist"; // 默认的菜单图标名称
118
+ }
119
+ },
120
+ // 计算滚动监视的类名
121
+ scrollspyClasses() {
122
+ return {
123
+ [`${prefixCls}--complete`]:this.scrollspy
124
+ }
125
+ },
126
+ // 计算标签文字是否需要省略号的类名
127
+ ellipsisClasses() {
128
+ return {
129
+ [`vd-tab__text--ellipsis`]: this.ellipsis,
130
+ };
131
+ },
132
+ // 计算标签包裹器的类名,根据 menu、backgroundColor 和 divider 属性来确定
133
+ menuClasses() {
134
+ return {
135
+ [`${prefixCls}__menu`]: this.menu,
136
+ [`${prefixCls}__wrap--bg`]: this.backgroundColor,
137
+ "vd-hairline--bottom": this.divider,
138
+ // "vd-tabs__scroll": this.firstTabOut, // 当第一个标签超出时,添加滚动类名
139
+ };
140
+ },
141
+ // 计算标签下方线条的类名
142
+ lineClasses() {
143
+ return {
144
+ [`vd-tab__none--line`]: !this.actBorder,
145
+ };
146
+ },
147
+ // 根据 tabsType 属性计算标签类型的类名
148
+ barType() {
149
+ return {
150
+ [`${prefixCls}__nav--${this.tabsType}`]: this.tabsType,
151
+ };
152
+ },
153
+ stickyClasses() {
154
+ return {
155
+ [`${prefixCls}--sticky`]: this.sticky,
156
+ };
157
+ },
158
+ },
159
+ methods: {
160
+ // 当菜单按钮被点击时触发事件
161
+ emitMenuClick() {
162
+ this.$emit("menu-click");
163
+ },
164
+ // 计算每个标签项的类名
165
+ tabClasses(tab, index) {
166
+ const isActive = this.isTabActive(tab, index);
167
+ return {
168
+ 'vd-tab': true,
169
+ 'vd-tab--active': isActive,
170
+ };
171
+ },
172
+ setCurrentName(name) {
173
+ // 如果没有指定 name 属性,则使用索引值,确保索引值为数字类型
174
+ const nameIsNumber = typeof name === 'number' || /^\d+$/.test(name);
175
+ const parsedName = nameIsNumber ? Number(name) : name;
176
+
177
+ const matchedTab = this.tabs.find((tab, index) => {
178
+ if (tab.name !== undefined) {
179
+ return tab.name === parsedName;
180
+ } else {
181
+ return index === parsedName;
182
+ }
183
+ });
184
+
185
+ if (matchedTab) {
186
+ this.currentName = matchedTab.name !== undefined ? matchedTab.name : this.tabs.indexOf(matchedTab);
187
+ } else if (this.tabs.length > 0) {
188
+ const firstTab = this.tabs[0];
189
+ this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
190
+ } else {
191
+ this.currentName = null;
192
+ }
193
+ this.$emit('input', this.currentName);
194
+ },
195
+ isTabActive(tab, index) {
196
+ if (tab.name !== undefined) {
197
+ return tab.name === this.currentName;
198
+ } else {
199
+ return index === Number(this.currentName);
200
+ }
201
+ },
202
+ addTab(tab) {
203
+ this.tabs.push(tab);
204
+ if (this.currentName === undefined) {
205
+ this.setCurrentName(tab.name !== undefined ? tab.name : 0);
206
+ }
207
+ },
208
+ removeTab(tab) {
209
+ const index = this.tabs.indexOf(tab);
210
+ if (index !== -1) {
211
+ this.tabs.splice(index, 1);
212
+ }
213
+ },
214
+ onClick(tab, index) {
215
+ if (tab.disabled) {
216
+ this.$emit('disabled', tab.name !== undefined ? tab.name : index, index);
217
+ } else {
218
+ const newName = tab.name !== undefined ? tab.name : index;
219
+
220
+ // 始终触发 click 事件
221
+ this.$emit('click', newName, index);
222
+
223
+ // 检查是否需要切换标签
224
+ if (newName !== this.currentName) {
225
+ this.currentName = newName;
226
+
227
+ // 如果 currentName 是索引值,确保为数字类型
228
+ if (tab.name === undefined) {
229
+ this.currentName = Number(this.currentName);
230
+ }
231
+
232
+ this.$emit('input', this.currentName);
233
+ this.$emit('change', this.currentName, index); // 仅在切换时触发 change 事件
234
+
235
+ // 切换标签后,滚动到当前标签
236
+ this.$nextTick(() => {
237
+ this.scrollToActiveTab();
238
+ });
239
+ }
240
+ }
241
+ },
242
+ scrollToActiveTab(targetIndex = null) {
243
+ const scrollWrapper = this.$refs.nav;
244
+ const tabItems = this.$refs.tabItems;
245
+ if (!scrollWrapper || !tabItems || tabItems.length === 0) {
246
+ return;
247
+ }
248
+ // 如果传入 targetIndex,设置当前激活标签的 name
249
+ if (targetIndex !== null) {
250
+ const targetTab = this.tabs[targetIndex];
251
+ if (targetTab) {
252
+ this.currentName = targetTab.name !== undefined ? targetTab.name : targetIndex;
253
+ } else {
254
+ console.warn('Invalid target index:', targetIndex);
255
+ return;
256
+ }
257
+ }
258
+
259
+ const activeIndex = targetIndex !== null ? targetIndex : this.tabs.findIndex((tab, index) => this.isTabActive(tab, index));
260
+ const activeTab = tabItems[activeIndex];
261
+
262
+ setTimeout(() => {
263
+ const tabOffsetLeft = activeTab.offsetLeft;
264
+ const tabWidth = activeTab.offsetWidth;
265
+ const wrapperWidth = scrollWrapper.offsetWidth;
266
+ const scrollLeft = tabOffsetLeft - (wrapperWidth - tabWidth) / 2;
267
+
268
+ scrollWrapper.scrollTo({
269
+ left: scrollLeft,
270
+ behavior: 'smooth'
271
+ });
272
+ }, 0);
273
+ },
274
+ checkFirstTabOverflow() {
275
+ const nav = this.$refs.nav;
276
+ if (nav) {
277
+ if (this.language === 'ar') {
278
+ // 在 RTL 模式下,scrollLeft 可能为负,判断是否滚出视口可以检测 scrollLeft 是否小于 0
279
+ // this.firstTabOut = nav.scrollLeft < 0;
280
+ // this.lastTabOut = nav.scrollLeft < 0;
281
+
282
+ this.firstTabOut =
283
+ nav.scrollWidth > nav.clientWidth &&
284
+ Math.abs(nav.scrollLeft) + nav.clientWidth < nav.scrollWidth;
285
+ this.lastTabOut = nav.scrollLeft < 0;
286
+ } else {
287
+ // LTR 模式下,滚出视口时 scrollLeft 大于 0
288
+ this.firstTabOut = nav.scrollLeft > 0;
289
+ this.lastTabOut =
290
+ nav.scrollWidth > nav.clientWidth && nav.scrollLeft + nav.clientWidth < nav.scrollWidth;
291
+ // this.firstTabOut = nav.scrollLeft > 0;
292
+ // this.lastTabOut = Math.ceil(nav.scrollLeft + nav.clientWidth) < nav.scrollWidth;
293
+ }
294
+ }
295
+ },
296
+ renderTitle(titleEl, tab) {
297
+ this.$nextTick(() => {
298
+ const index = this.tabs.indexOf(tab);
299
+ if (index !== -1 && this.$refs.title && this.$refs.title[index]) {
300
+ const navTitleEl = this.$refs.title[index];
301
+ // 清空导航标题元素的内容
302
+ navTitleEl.innerHTML = '';
303
+ // 将子组件的 title 插槽内容移动到导航标题元素中
304
+ while (titleEl.firstChild) {
305
+ navTitleEl.appendChild(titleEl.firstChild);
306
+ }
307
+ }
308
+ });
309
+ },
310
+ /**
311
+ * resize 方法
312
+ * 当外层元素大小或组件显示状态变化时调用,触发重绘
313
+ */
314
+ resize() {
315
+ // 重新计算当前激活标签的位置
316
+ this.scrollToActiveTab();
317
+ // 如果有其他需要重绘或重新计算的逻辑,可以在这里添加
318
+ // 例如,重新计算动画轨道的位置等
319
+ },
320
+ /**
321
+ * scrollTo 方法
322
+ * 刷新页面,滚动到指定位置
323
+ */
324
+ scrollTo(targetIndex = null){
325
+ // 重新计算当前激活标签的位置
326
+ this.scrollToActiveTab(targetIndex);
327
+ // 如果有其他需要重绘或重新计算的逻辑,可以在这里添加
328
+ // 例如,重新计算动画轨道的位置等
329
+ }
330
+ },
331
+ mounted() {
332
+ if (this.currentName !== undefined) {
333
+ this.setCurrentName(this.currentName);
334
+ } else if (this.tabs.length > 0) {
335
+ const firstTab = this.tabs[0];
336
+ this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
337
+ }
338
+ // 初始加载时,滚动到当前激活的标签
339
+ this.$nextTick(() => {
340
+ this.scrollToActiveTab();
341
+ this.checkFirstTabOverflow()
342
+ if (this.$refs.nav) {
343
+ this.$refs.nav.addEventListener('scroll', this.checkFirstTabOverflow);
344
+ }
345
+ });
346
+ },
347
+ beforeDestroy() {
348
+ if (this.$refs.nav) {
349
+ this.$refs.nav.removeEventListener('scroll', this.checkFirstTabOverflow);
350
+ }
351
+ },
352
+ };
353
+ </script>
354
+ <style lang="less">
355
+ @import "./style.less";
356
+ </style>