vdesign-ui 0.2.7 → 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.
@@ -27,10 +27,10 @@ export default {
27
27
  image: {
28
28
  type: String,
29
29
  default: 'nodata', // 默认类型
30
- validator(value) {
31
- // 只接受以下类型值
32
- return ['nodata', 'network', 'notfound','noposition','nomargin','nocoupons','nosearch','nonotice','noorders','noocomments'].includes(value);
33
- }
30
+ // validator(value) {
31
+ // // 只接受以下类型值
32
+ // return ['nodata', 'network', 'notfound','noposition','nomargin','nocoupons','nosearch','nonotice','noorders','noocomments'].includes(value);
33
+ // }
34
34
  },
35
35
  description: String,
36
36
  size: {
@@ -1,45 +1,45 @@
1
- // outlineConfigPlugin.js
2
- import { inBrowser } from '../utils/env'
3
- // __debug=1&__debug_vdesign_token=1
4
- const OutlineConfig = {
5
- install(Vue, options = {}) {
6
- const outlineConfig = Vue.observable({
7
- outlineEnabled: options.outlineEnabled ||false,
8
- });
9
-
10
- Vue.prototype.$outlineConfig = outlineConfig;
11
-
12
- Vue.mixin({
13
- created() {
14
- if (inBrowser) {
15
- this.$outlineConfig.outlineEnabled = this.shouldEnableOutline()
16
- this.$watch('$outlineConfig.outlineEnabled', (newValue) => {
17
- document.documentElement.style.setProperty('--outline-visible', newValue ? '1px' : '0');
18
- }, { immediate: true });
19
- }
20
- },
21
- methods:{
22
- shouldEnableOutline() {
23
- if (!inBrowser) {
24
- // 如果是在服务端渲染,返回默认值 false
25
- return false;
26
- }
27
- // 获取URL的查询参数部分
28
- const searchParams = new URLSearchParams(window.location.search);
29
- // 对于哈希模式,也解析哈希中的查询字符串
30
- const hashParams = window.location.hash.split('?')[1] ? new URLSearchParams(window.location.hash.split('?')[1]) : null;
31
-
32
- // 检查查询参数或哈希中的参数
33
- const debug = searchParams.has('__debug') || (hashParams && hashParams.has('__debug')) ? searchParams.get('__debug') === '1' || (hashParams && hashParams.get('__debug') === '1') : false;
34
- const vdesignToken = searchParams.has('__debug_vdesign_token') || (hashParams && hashParams.has('__debug_vdesign_token')) ? searchParams.get('__debug_vdesign_token') === '1' || (hashParams && hashParams.get('__debug_vdesign_token') === '1') : false;
35
-
36
- return debug && vdesignToken;
37
- }
38
- }
39
- });
40
- }
41
- };
42
-
43
-
44
-
45
- export default OutlineConfig;
1
+ // outlineConfigPlugin.js
2
+ import { inBrowser } from '../utils/env'
3
+ // __debug=1&__debug_vdesign_token=1
4
+ const OutlineConfig = {
5
+ install(Vue, options = {}) {
6
+ const outlineConfig = Vue.observable({
7
+ outlineEnabled: options.outlineEnabled ||false,
8
+ });
9
+
10
+ Vue.prototype.$outlineConfig = outlineConfig;
11
+
12
+ Vue.mixin({
13
+ created() {
14
+ if (inBrowser) {
15
+ this.$outlineConfig.outlineEnabled = this.shouldEnableOutline()
16
+ this.$watch('$outlineConfig.outlineEnabled', (newValue) => {
17
+ document.documentElement.style.setProperty('--outline-visible', newValue ? '1px' : '0');
18
+ }, { immediate: true });
19
+ }
20
+ },
21
+ methods:{
22
+ shouldEnableOutline() {
23
+ if (!inBrowser) {
24
+ // 如果是在服务端渲染,返回默认值 false
25
+ return false;
26
+ }
27
+ // 获取URL的查询参数部分
28
+ const searchParams = new URLSearchParams(window.location.search);
29
+ // 对于哈希模式,也解析哈希中的查询字符串
30
+ const hashParams = window.location.hash.split('?')[1] ? new URLSearchParams(window.location.hash.split('?')[1]) : null;
31
+
32
+ // 检查查询参数或哈希中的参数
33
+ const debug = searchParams.has('__debug') || (hashParams && hashParams.has('__debug')) ? searchParams.get('__debug') === '1' || (hashParams && hashParams.get('__debug') === '1') : false;
34
+ const vdesignToken = searchParams.has('__debug_vdesign_token') || (hashParams && hashParams.has('__debug_vdesign_token')) ? searchParams.get('__debug_vdesign_token') === '1' || (hashParams && hashParams.get('__debug_vdesign_token') === '1') : false;
35
+
36
+ return debug && vdesignToken;
37
+ }
38
+ }
39
+ });
40
+ }
41
+ };
42
+
43
+
44
+
45
+ export default OutlineConfig;
@@ -1,53 +1,94 @@
1
1
  <template>
2
- <div :class="tabClasses">
3
- <slot />
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
- name: [String, Number],
12
- title: {
13
- type: String,
14
- required: true,
15
- },
16
- arrow: {
17
- type: Boolean,
18
- default: false,
19
- },
15
+ name: [String, Number],
16
+ title: String,
20
17
  },
21
18
  data() {
19
+ return {
20
+ parent: null,
21
+ inited: false,
22
+ };
23
+ },
24
+ computed: {
25
+ paneClasses() {
22
26
  return {
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
- watch: {
26
-
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
+ },
27
47
  },
28
- computed: {
29
- isActive() {
30
- return this.computedName === this.$parent.currentValue;
31
- },
32
- tabClasses() {
33
- return {
34
- 'vd-tab': true,
35
- 'vd-tab-active': this.isActive,
36
- };
37
- },
38
- computedName() {
39
- return this.name != null
40
- ? this.name
41
- : this.$parent.tabs.indexOf(this);
42
- },
48
+ watch: {
49
+ isActive(val) {
50
+ if (val) {
51
+ this.inited = true;
52
+ }
53
+ },
43
54
  },
44
55
  created() {
45
- this.$parent.registerTab(this);
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
+ }
46
73
  },
47
74
  beforeDestroy() {
48
- this.$parent.unregisterTab(this);
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
+ },
49
90
  },
50
- };
51
- </script>
52
-
53
- <style lang="less"></style>
91
+ };
92
+ </script>
93
+
94
+
@@ -1,20 +1,38 @@
1
1
  <template>
2
- <div class="vd-tabs">
3
- <div class="vd-tabs__wrap" :class="menuClasses" ref="tabsWrap">
4
- <div :class="[barType,scrollspy]" ref="tabsContainer">
5
- <div v-for="(item, index) in navList" :key="index" :class="[tabClasses(item), lineClasses]" @click="handleChange(index)">
6
- <span class="vd-tabs-text-cell" :class="ellipsisClasses">
7
- {{ item.title }}
8
- <vd-icon class="vd-tabs-arrow" v-if="item.arrow" name="icon_btn_moredown"></vd-icon>
2
+ <div class="vd-tabs" :class="stickyClasses">
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-tabs-menu--right" v-if="menu">
13
- <vd-icon :name="menuIconComputed" class="vd-tabs-menu-btn" @click="emitMenuClick"></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
- <slot />
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,
@@ -45,43 +62,61 @@ export default {
45
62
  actBorder: Boolean,
46
63
  backgroundColor: Boolean,
47
64
  divider: Boolean,
65
+ sticky: Boolean,
66
+ lazyRender: Boolean,
67
+ animated: Boolean,
48
68
  },
49
69
  data() {
50
70
  return {
51
- currentValue: this.value, // 当前选中的标签名
52
- navList: [], // 标签导航列表
53
- tabs: [], // 已注册的标签
71
+ tabs: [],
72
+ currentName: this.value !== undefined ? this.value : 0, // 默认为 0
54
73
  };
55
74
  },
56
75
  watch: {
57
76
  // 监听 value 变化,更新 currentValue
58
77
  value(val) {
59
- this.currentValue = val;
78
+ this.currentName = val;
79
+ this.setCurrentName(val);
60
80
  },
61
81
  },
62
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
+ },
63
98
  // 计算菜单按钮的图标,根据 menu 属性的类型决定图标
64
99
  menuIconComputed() {
65
- if (typeof this.menu === 'string') {
100
+ if (typeof this.menu === "string") {
66
101
  return this.menu;
67
102
  } else {
68
- return 'icon_tab_morelist'; // 默认的菜单图标名称
103
+ return "icon_tab_morelist"; // 默认的菜单图标名称
69
104
  }
70
105
  },
71
106
  // 计算滚动监视的类名
72
107
  scrollspy() {
73
- return `${prefixCls}--complete`
108
+ return `${prefixCls}--complete`;
74
109
  },
75
110
  // 计算标签文字是否需要省略号的类名
76
111
  ellipsisClasses() {
77
112
  return {
78
- [`${prefixCls}-text--ellipsis`]: this.ellipsis,
113
+ [`vd-tab__text--ellipsis`]: this.ellipsis,
79
114
  };
80
115
  },
81
116
  // 计算标签包裹器的类名,根据 menu、backgroundColor 和 divider 属性来确定
82
117
  menuClasses() {
83
118
  return {
84
- [`${prefixCls}-menu`]: this.menu,
119
+ [`${prefixCls}__menu`]: this.menu,
85
120
  [`${prefixCls}__wrap--bg`]: this.backgroundColor,
86
121
  "vd-hairline--bottom": this.divider,
87
122
  };
@@ -89,83 +124,147 @@ export default {
89
124
  // 计算标签下方线条的类名
90
125
  lineClasses() {
91
126
  return {
92
- [`${prefixCls}__line`]: !this.actBorder,
127
+ [`vd-tab__none--line`]: !this.actBorder,
93
128
  };
94
129
  },
95
130
  // 根据 tabsType 属性计算标签类型的类名
96
131
  barType() {
97
132
  return {
98
- [`${prefixCls}-${this.tabsType}-bar`]: this.tabsType,
133
+ [`${prefixCls}__nav--${this.tabsType}`]: this.tabsType,
134
+ };
135
+ },
136
+ stickyClasses() {
137
+ return {
138
+ [`${prefixCls}--sticky`]: this.sticky,
99
139
  };
100
140
  },
101
141
  },
102
142
  methods: {
103
143
  // 当菜单按钮被点击时触发事件
104
144
  emitMenuClick() {
105
- this.$emit('menu-click');
145
+ this.$emit("menu-click");
106
146
  },
107
147
  // 计算每个标签项的类名
108
- tabClasses(item) {
148
+ tabClasses(tab, index) {
149
+ const isActive = this.isTabActive(tab, index);
109
150
  return {
110
- [`${prefixCls}-${this.tabsType}`]: true,
111
- [`${prefixCls}-${this.tabsType}-active`]: item.name === this.currentValue,
151
+ 'vd-tab': true,
152
+ 'vd-tab--active': isActive,
112
153
  };
113
154
  },
114
- // 注册新的标签到 tabs 数组中
115
- registerTab(tab) {
116
- if (!this.tabs.includes(tab)) {
117
- this.tabs.push(tab);
118
- this.updateNav();
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);
183
+ }
184
+ },
185
+ addTab(tab) {
186
+ this.tabs.push(tab);
187
+ if (this.currentName === undefined) {
188
+ this.setCurrentName(tab.name !== undefined ? tab.name : 0);
119
189
  }
120
190
  },
121
- // 从 tabs 数组中注销一个标签
122
- unregisterTab(tab) {
191
+ removeTab(tab) {
123
192
  const index = this.tabs.indexOf(tab);
124
193
  if (index !== -1) {
125
194
  this.tabs.splice(index, 1);
126
- this.updateNav();
127
195
  }
128
196
  },
129
- // 根据已注册的标签更新导航列表
130
- updateNav() {
131
- this.navList = this.tabs.map((tab, index) => ({
132
- title: tab.title,
133
- name: tab.name != null ? tab.name : index,
134
- arrow: tab.arrow,
135
- }));
136
- // 如果当前没有选中的标签且导航列表不为空,则将第一个标签设为当前值
137
- if (!this.currentValue && this.navList.length) {
138
- this.currentValue = this.navList[0].name;
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
+ });
139
212
  }
140
213
  },
141
- handleChange(index) {
142
- const nav = this.navList[index];
143
- const name = nav.name;
144
- if (this.currentValue !== name) {
145
- this.currentValue = name;
146
- this.$emit("input", name); // 触发 input 事件,更新父组件中的 v-model 绑定值
147
- this.$emit("change", { index, title: nav.title, name }); // 触发 change 事件,传递标签的详细信息
148
- this.adjustTabPosition(index); // 调整选中标签的位置
149
- }
150
- this.$emit("click", { index, title: nav.title, name }); // 触发 click 事件,传递标签的详细信息
151
- },
152
- adjustTabPosition(index) {
153
- const container = this.$refs.tabsContainer;
154
- const selectedTab = container.children[index];
155
- if (selectedTab) {
156
- const containerWidth = container.offsetWidth;
157
- const tabWidth = selectedTab.offsetWidth;
158
- const tabOffsetLeft = selectedTab.offsetLeft;
159
- const scrollLeft = tabOffsetLeft - (containerWidth - tabWidth) / 2;
160
- container.scrollTo({ left: scrollLeft, behavior: "smooth" });
161
- }
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
+ });
162
253
  },
163
254
  },
164
255
  mounted() {
165
- // 组件挂载后更新导航列表
166
- // this.updateNav();
167
- },
168
- };
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
+ };
169
268
  </script>
170
269
  <style lang="less">
171
270
  @import "./style.less";