vdesign-ui 0.2.8 → 0.2.9
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/dist/components/tab/index.vue +79 -53
- package/dist/components/tabs/index.vue +161 -70
- package/dist/components/tabs/style.less +293 -267
- package/dist/components/toast/index.js +14 -4
- package/dist/vdesign-ui.common.js +256 -154
- package/dist/vdesign-ui.css +1 -1
- package/dist/vdesign-ui.umd.js +256 -154
- package/dist/vdesign-ui.umd.min.js +3 -3
- package/package.json +1 -1
|
@@ -1,68 +1,94 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="
|
|
3
|
-
|
|
2
|
+
<div v-show="shouldShow" :class="paneClasses">
|
|
3
|
+
<slot v-if="shouldRender" />
|
|
4
|
+
<!-- 标题插槽占位符 -->
|
|
5
|
+
<div v-if="$slots.title" ref="title">
|
|
6
|
+
<slot name="title" />
|
|
7
|
+
</div>
|
|
4
8
|
</div>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script>
|
|
8
|
-
export default {
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
export default {
|
|
9
13
|
name: 'vd-tab',
|
|
10
14
|
props: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type: String,
|
|
14
|
-
},
|
|
15
|
-
arrow: {
|
|
16
|
-
type: Boolean,
|
|
17
|
-
default: false,
|
|
18
|
-
},
|
|
15
|
+
name: [String, Number],
|
|
16
|
+
title: String,
|
|
19
17
|
},
|
|
20
18
|
data() {
|
|
19
|
+
return {
|
|
20
|
+
parent: null,
|
|
21
|
+
inited: false,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
computed: {
|
|
25
|
+
paneClasses() {
|
|
21
26
|
return {
|
|
22
|
-
|
|
27
|
+
'vd-tab__pane': true,
|
|
23
28
|
};
|
|
29
|
+
},
|
|
30
|
+
isActive() {
|
|
31
|
+
if (!this.parent) return false;
|
|
32
|
+
if (this.name !== undefined) {
|
|
33
|
+
return this.name === this.parent.currentName;
|
|
34
|
+
} else {
|
|
35
|
+
const index = this.parent.tabs.indexOf(this);
|
|
36
|
+
return index === Number(this.parent.currentName);
|
|
37
|
+
}
|
|
24
38
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
shouldShow() {
|
|
40
|
+
// 当 animated 为 true 时,所有标签页都需要渲染(用于动画效果)
|
|
41
|
+
// 当 animated 为 false 时,只渲染当前激活的标签页
|
|
42
|
+
return this.isActive || (this.parent && this.parent.animated);
|
|
43
|
+
},
|
|
44
|
+
shouldRender() {
|
|
45
|
+
return this.inited || !(this.parent && this.parent.lazyRender);
|
|
46
|
+
},
|
|
31
47
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'vd-tab': true,
|
|
39
|
-
'vd-tab-active': this.isActive,
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
computedName() {
|
|
43
|
-
return this.name != null
|
|
44
|
-
? this.name
|
|
45
|
-
: this.$parent.tabs.indexOf(this);
|
|
46
|
-
},
|
|
47
|
-
shouldRender() {
|
|
48
|
-
if (this.$parent.lazyRender) {
|
|
49
|
-
return this.isRendered || this.isActive;
|
|
50
|
-
} else {
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
computedTitle() {
|
|
55
|
-
// Since the title slot is now handled by vd-tabs, we return the title prop
|
|
56
|
-
return this.title;
|
|
57
|
-
},
|
|
48
|
+
watch: {
|
|
49
|
+
isActive(val) {
|
|
50
|
+
if (val) {
|
|
51
|
+
this.inited = true;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
58
54
|
},
|
|
59
55
|
created() {
|
|
60
|
-
|
|
56
|
+
this.parent = this.findParent('vd-tabs');
|
|
57
|
+
if (this.parent) {
|
|
58
|
+
this.parent.addTab(this);
|
|
59
|
+
} else {
|
|
60
|
+
console.error('vd-tab must be used within vd-tabs.');
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
mounted() {
|
|
64
|
+
if (this.isActive) {
|
|
65
|
+
this.inited = true;
|
|
66
|
+
}
|
|
67
|
+
// 如果存在 title 插槽,调用父组件的 renderTitle 方法
|
|
68
|
+
if (this.$slots.title && this.$refs.title) {
|
|
69
|
+
this.parent.renderTitle(this.$refs.title, this);
|
|
70
|
+
this.$refs.title.parentNode.removeChild(this.$refs.title);
|
|
71
|
+
|
|
72
|
+
}
|
|
61
73
|
},
|
|
62
74
|
beforeDestroy() {
|
|
63
|
-
|
|
75
|
+
if (this.parent) {
|
|
76
|
+
this.parent.removeTab(this);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
methods: {
|
|
80
|
+
findParent(name) {
|
|
81
|
+
let parent = this.$parent;
|
|
82
|
+
while (parent) {
|
|
83
|
+
if (parent.$options && parent.$options.name === name) {
|
|
84
|
+
return parent;
|
|
85
|
+
}
|
|
86
|
+
parent = parent.$parent;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
},
|
|
64
90
|
},
|
|
65
|
-
};
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
};
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
|
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="vd-tabs" :class="stickyClasses">
|
|
3
|
-
<div class="vd-tabs__wrap" :class="menuClasses" ref="
|
|
4
|
-
<div class="vd-tabs__nav" :class="[barType,scrollspy]" ref="
|
|
5
|
-
<div
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
<div class="vd-tabs__wrap" :class="menuClasses" ref="wrap">
|
|
4
|
+
<div class="vd-tabs__nav" :class="[barType, scrollspy]" ref="nav">
|
|
5
|
+
<div
|
|
6
|
+
v-for="(tab, index) in tabs"
|
|
7
|
+
:key="tab.name !== undefined ? tab.name : index"
|
|
8
|
+
:class="[tabClasses(tab, index), lineClasses]"
|
|
9
|
+
@click="onClick(tab, index)"
|
|
10
|
+
ref="tabItems"
|
|
11
|
+
>
|
|
12
|
+
<span class="vd-tab__text" :class="ellipsisClasses" ref="title"
|
|
13
|
+
>
|
|
14
|
+
{{ tab.title }}
|
|
15
|
+
<!-- <vd-icon
|
|
16
|
+
class="vd-tab__arrow"
|
|
17
|
+
v-if="tab.arrow"
|
|
18
|
+
name="icon_btn_moredown"
|
|
19
|
+
></vd-icon> -->
|
|
9
20
|
</span>
|
|
10
21
|
</div>
|
|
11
22
|
</div>
|
|
12
|
-
<div class="vd-
|
|
13
|
-
<vd-icon
|
|
23
|
+
<div class="vd-tabs__menu--right" v-if="menu">
|
|
24
|
+
<vd-icon
|
|
25
|
+
:name="menuIconComputed"
|
|
26
|
+
class="vd-tabs__menu--btn"
|
|
27
|
+
@click="emitMenuClick"
|
|
28
|
+
></vd-icon>
|
|
14
29
|
</div>
|
|
15
30
|
</div>
|
|
16
|
-
<div class="vd-tabs__content">
|
|
17
|
-
<
|
|
31
|
+
<div class="vd-tabs__content" ref="content" :class="{'vd-tabs__content--animated':animated}">
|
|
32
|
+
<div v-if="animated" :class="trackClasses" :style="trackStyle">
|
|
33
|
+
<slot />
|
|
34
|
+
</div>
|
|
35
|
+
<slot v-else />
|
|
18
36
|
</div>
|
|
19
37
|
</div>
|
|
20
38
|
</template>
|
|
@@ -33,7 +51,6 @@ export default {
|
|
|
33
51
|
type: String,
|
|
34
52
|
default: "primary",
|
|
35
53
|
},
|
|
36
|
-
// 将 menu 作为一个字段,同时控制显示和图标名称
|
|
37
54
|
menu: {
|
|
38
55
|
type: [Boolean, String],
|
|
39
56
|
default: false,
|
|
@@ -47,43 +64,59 @@ export default {
|
|
|
47
64
|
divider: Boolean,
|
|
48
65
|
sticky: Boolean,
|
|
49
66
|
lazyRender: Boolean,
|
|
67
|
+
animated: Boolean,
|
|
50
68
|
},
|
|
51
69
|
data() {
|
|
52
70
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
tabs: [], // 已注册的标签
|
|
71
|
+
tabs: [],
|
|
72
|
+
currentName: this.value !== undefined ? this.value : 0, // 默认为 0
|
|
56
73
|
};
|
|
57
74
|
},
|
|
58
75
|
watch: {
|
|
59
76
|
// 监听 value 变化,更新 currentValue
|
|
60
77
|
value(val) {
|
|
61
|
-
this.
|
|
78
|
+
this.currentName = val;
|
|
79
|
+
this.setCurrentName(val);
|
|
62
80
|
},
|
|
63
81
|
},
|
|
64
82
|
computed: {
|
|
83
|
+
trackClasses() {
|
|
84
|
+
return {
|
|
85
|
+
'vd-tabs__track': true,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
trackStyle() {
|
|
89
|
+
const activeIndex = this.tabs.findIndex((tab, index) => {
|
|
90
|
+
return this.isTabActive(tab, index);
|
|
91
|
+
});
|
|
92
|
+
const translateX = -activeIndex * 100;
|
|
93
|
+
return {
|
|
94
|
+
transform: `translateX(${translateX}%)`,
|
|
95
|
+
transitionDuration: '0.3s',
|
|
96
|
+
};
|
|
97
|
+
},
|
|
65
98
|
// 计算菜单按钮的图标,根据 menu 属性的类型决定图标
|
|
66
99
|
menuIconComputed() {
|
|
67
|
-
if (typeof this.menu ===
|
|
100
|
+
if (typeof this.menu === "string") {
|
|
68
101
|
return this.menu;
|
|
69
102
|
} else {
|
|
70
|
-
return
|
|
103
|
+
return "icon_tab_morelist"; // 默认的菜单图标名称
|
|
71
104
|
}
|
|
72
105
|
},
|
|
73
106
|
// 计算滚动监视的类名
|
|
74
107
|
scrollspy() {
|
|
75
|
-
return
|
|
108
|
+
return `${prefixCls}--complete`;
|
|
76
109
|
},
|
|
77
110
|
// 计算标签文字是否需要省略号的类名
|
|
78
111
|
ellipsisClasses() {
|
|
79
112
|
return {
|
|
80
|
-
[
|
|
113
|
+
[`vd-tab__text--ellipsis`]: this.ellipsis,
|
|
81
114
|
};
|
|
82
115
|
},
|
|
83
116
|
// 计算标签包裹器的类名,根据 menu、backgroundColor 和 divider 属性来确定
|
|
84
117
|
menuClasses() {
|
|
85
118
|
return {
|
|
86
|
-
[`${prefixCls}
|
|
119
|
+
[`${prefixCls}__menu`]: this.menu,
|
|
87
120
|
[`${prefixCls}__wrap--bg`]: this.backgroundColor,
|
|
88
121
|
"vd-hairline--bottom": this.divider,
|
|
89
122
|
};
|
|
@@ -91,13 +124,13 @@ export default {
|
|
|
91
124
|
// 计算标签下方线条的类名
|
|
92
125
|
lineClasses() {
|
|
93
126
|
return {
|
|
94
|
-
[
|
|
127
|
+
[`vd-tab__none--line`]: !this.actBorder,
|
|
95
128
|
};
|
|
96
129
|
},
|
|
97
130
|
// 根据 tabsType 属性计算标签类型的类名
|
|
98
131
|
barType() {
|
|
99
132
|
return {
|
|
100
|
-
[`${prefixCls}
|
|
133
|
+
[`${prefixCls}__nav--${this.tabsType}`]: this.tabsType,
|
|
101
134
|
};
|
|
102
135
|
},
|
|
103
136
|
stickyClasses() {
|
|
@@ -109,71 +142,129 @@ export default {
|
|
|
109
142
|
methods: {
|
|
110
143
|
// 当菜单按钮被点击时触发事件
|
|
111
144
|
emitMenuClick() {
|
|
112
|
-
this.$emit(
|
|
145
|
+
this.$emit("menu-click");
|
|
113
146
|
},
|
|
114
147
|
// 计算每个标签项的类名
|
|
115
|
-
tabClasses(
|
|
148
|
+
tabClasses(tab, index) {
|
|
149
|
+
const isActive = this.isTabActive(tab, index);
|
|
116
150
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
'vd-tab': true,
|
|
152
|
+
'vd-tab--active': isActive,
|
|
119
153
|
};
|
|
120
154
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
setCurrentName(name) {
|
|
156
|
+
// 如果没有指定 name 属性,则使用索引值,确保索引值为数字类型
|
|
157
|
+
const nameIsNumber = typeof name === 'number' || /^\d+$/.test(name);
|
|
158
|
+
const parsedName = nameIsNumber ? Number(name) : name;
|
|
159
|
+
|
|
160
|
+
const matchedTab = this.tabs.find((tab, index) => {
|
|
161
|
+
if (tab.name !== undefined) {
|
|
162
|
+
return tab.name === parsedName;
|
|
163
|
+
} else {
|
|
164
|
+
return index === parsedName;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (matchedTab) {
|
|
169
|
+
this.currentName = matchedTab.name !== undefined ? matchedTab.name : this.tabs.indexOf(matchedTab);
|
|
170
|
+
} else if (this.tabs.length > 0) {
|
|
171
|
+
const firstTab = this.tabs[0];
|
|
172
|
+
this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
|
|
173
|
+
} else {
|
|
174
|
+
this.currentName = null;
|
|
175
|
+
}
|
|
176
|
+
this.$emit('input', this.currentName);
|
|
177
|
+
},
|
|
178
|
+
isTabActive(tab, index) {
|
|
179
|
+
if (tab.name !== undefined) {
|
|
180
|
+
return tab.name === this.currentName;
|
|
181
|
+
} else {
|
|
182
|
+
return index === Number(this.currentName);
|
|
126
183
|
}
|
|
127
184
|
},
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
addTab(tab) {
|
|
186
|
+
this.tabs.push(tab);
|
|
187
|
+
if (this.currentName === undefined) {
|
|
188
|
+
this.setCurrentName(tab.name !== undefined ? tab.name : 0);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
removeTab(tab) {
|
|
130
192
|
const index = this.tabs.indexOf(tab);
|
|
131
193
|
if (index !== -1) {
|
|
132
194
|
this.tabs.splice(index, 1);
|
|
133
|
-
this.updateNav();
|
|
134
195
|
}
|
|
135
196
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this
|
|
146
|
-
this.$emit(
|
|
197
|
+
onClick(tab, index) {
|
|
198
|
+
if (tab.disabled) {
|
|
199
|
+
this.$emit('disabled', tab.name !== undefined ? tab.name : index, index);
|
|
200
|
+
} else {
|
|
201
|
+
this.currentName = tab.name !== undefined ? tab.name : index;
|
|
202
|
+
// 如果 currentName 是索引值,确保为数字类型
|
|
203
|
+
if (tab.name === undefined) {
|
|
204
|
+
this.currentName = Number(this.currentName);
|
|
205
|
+
}
|
|
206
|
+
this.$emit('input', this.currentName);
|
|
207
|
+
this.$emit('change', this.currentName, index);
|
|
208
|
+
// 切换标签后,滚动到当前标签
|
|
209
|
+
this.$nextTick(() => {
|
|
210
|
+
this.scrollToActiveTab();
|
|
211
|
+
});
|
|
147
212
|
}
|
|
148
213
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
214
|
+
scrollToActiveTab() {
|
|
215
|
+
const scrollWrapper = this.$refs.nav; // 修改这里
|
|
216
|
+
const tabItems = this.$refs.tabItems;
|
|
217
|
+
|
|
218
|
+
if (!scrollWrapper || !tabItems || tabItems.length === 0) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const activeIndex = this.tabs.findIndex((tab, index) => {
|
|
223
|
+
return this.isTabActive(tab, index);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const activeTab = tabItems[activeIndex];
|
|
227
|
+
|
|
228
|
+
if (activeTab) {
|
|
229
|
+
const tabOffsetLeft = activeTab.offsetLeft;
|
|
230
|
+
const tabWidth = activeTab.offsetWidth;
|
|
231
|
+
const wrapperWidth = scrollWrapper.offsetWidth;
|
|
232
|
+
const scrollLeft = tabOffsetLeft - (wrapperWidth - tabWidth) / 2;
|
|
233
|
+
|
|
234
|
+
scrollWrapper.scrollTo({
|
|
235
|
+
left: scrollLeft,
|
|
236
|
+
behavior: 'smooth',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
renderTitle(titleEl, tab) {
|
|
241
|
+
this.$nextTick(() => {
|
|
242
|
+
const index = this.tabs.indexOf(tab);
|
|
243
|
+
if (index !== -1 && this.$refs.title && this.$refs.title[index]) {
|
|
244
|
+
const navTitleEl = this.$refs.title[index];
|
|
245
|
+
// 清空导航标题元素的内容
|
|
246
|
+
navTitleEl.innerHTML = '';
|
|
247
|
+
// 将子组件的 title 插槽内容移动到导航标题元素中
|
|
248
|
+
while (titleEl.firstChild) {
|
|
249
|
+
navTitleEl.appendChild(titleEl.firstChild);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
170
253
|
},
|
|
171
254
|
},
|
|
172
255
|
mounted() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
256
|
+
if (this.currentName !== undefined) {
|
|
257
|
+
this.setCurrentName(this.currentName);
|
|
258
|
+
} else if (this.tabs.length > 0) {
|
|
259
|
+
const firstTab = this.tabs[0];
|
|
260
|
+
this.currentName = firstTab.name !== undefined ? firstTab.name : 0;
|
|
261
|
+
}
|
|
262
|
+
// 初始加载时,滚动到当前激活的标签
|
|
263
|
+
this.$nextTick(() => {
|
|
264
|
+
this.scrollToActiveTab();
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
};
|
|
177
268
|
</script>
|
|
178
269
|
<style lang="less">
|
|
179
270
|
@import "./style.less";
|