vdesign-ui 0.2.2 → 0.2.4

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.
@@ -1,14 +1,14 @@
1
1
  <template>
2
2
  <div>
3
- <transition name="vd-activityviews-fade">
4
- <div class="vd-overlay" v-if="overlay && visible" @click="handleOverlayClick"></div>
5
- </transition>
3
+ <div class="vd-overlay" v-if="overlay && visible" @click="handleOverlayClick"></div>
6
4
  <transition name="vd-activityviews-slide-up">
7
- <div class="vd-activityviews" v-if="visible">
5
+ <div :class="['vd-activityviews',{'vd-activityviews--safe-area-inset-bottom':safeAreaInsetBottom}]" :style="transitionStyle" v-if="visible">
8
6
  <div class="vd-activityviews__header" v-if="withHeader">
9
7
  <div class="vd-activityviews__wrapper">
10
8
  <vd-icon v-if="backDefault" name="icon_nav_back"></vd-icon>
11
- <div v-if="title" class="vd-activityviews__title">{{ title }}</div>
9
+ <div v-if="title || $slots.title" class="vd-activityviews__title">
10
+ <slot name="title">{{ title }}</slot>
11
+ </div>
12
12
  <slot name="close">
13
13
  <vd-icon v-if="closeIcon" class="vd-activityviews__close" :name="currentCloseIcon" svg
14
14
  @click="onCancel"></vd-icon>
@@ -21,7 +21,9 @@
21
21
  <div v-if="withBar" class="vd-activityviews__bar"></div>
22
22
  <div :class="['vd-activityviews__body', { 'vd-activityviews__body--action': actionSheet }]">
23
23
  <div v-if="actionSheet" class="vd-activityviews__action">
24
- <p v-if="title" class="vd-activityviews__action-title vd-hairline--bottom">{{ title }}</p>
24
+ <p v-if="title || $slots.title" class="vd-activityviews__action-title vd-hairline--bottom">
25
+ <slot name="title">{{ title }}</slot>
26
+ </p>
25
27
  <div class="vd-activityviews__actions">
26
28
  <vd-button v-for="(item, index) in actions" block size="large" :type="item.type"
27
29
  :key="index" :class="{ 'vd-hairline--bottom': index !== actions.length - 1 }"
@@ -40,12 +42,13 @@
40
42
  </template>
41
43
 
42
44
  <script>
45
+ import { inBrowser } from '../utils/env';
43
46
  import { themeMixin } from '../mixins/themeMixin';
44
47
  import VdIcon from '../icon';
45
48
  import VdButton from '../button';
46
49
 
47
50
  export default {
48
- name: 'vd-activityviews',
51
+ name: 'vd-activity-views',
49
52
  mixins: [themeMixin], // 引入混入
50
53
  components: {
51
54
  VdIcon,
@@ -80,6 +83,22 @@ export default {
80
83
  type: Boolean,
81
84
  default: true
82
85
  },
86
+ safeAreaInsetBottom: {
87
+ type: Boolean,
88
+ default: true,
89
+ },
90
+ lockScroll: {
91
+ type: Boolean,
92
+ default: true
93
+ },
94
+ duration: {
95
+ type: Number,
96
+ },
97
+ // 指定挂载的容器,可以是选择器字符串或返回元素的函数
98
+ getContainer: {
99
+ type: [String, Function],
100
+ default: 'body',
101
+ },
83
102
  },
84
103
  data() {
85
104
  return {
@@ -94,9 +113,26 @@ export default {
94
113
  },
95
114
  isDarkTheme() {
96
115
  return this.theme === 'dark';
97
- }
116
+ },
117
+ // 动态设置过渡持续时间的样式
118
+ transitionStyle() {
119
+ return {
120
+ transitionDuration: `${this.duration}s`,
121
+ };
122
+ },
98
123
  },
99
124
  watch: {
125
+ visible(val) {
126
+ if (inBrowser) {
127
+ if (this.lockScroll) {
128
+ if (val) {
129
+ document.body.style.overflow = 'hidden';
130
+ } else {
131
+ document.body.style.overflow = '';
132
+ }
133
+ }
134
+ }
135
+ }
100
136
  },
101
137
  methods: {
102
138
  closeActivity() {
@@ -115,9 +151,36 @@ export default {
115
151
  if (this.closeOnOverlay) {
116
152
  this.closeActivity();
117
153
  }
118
- }
154
+ },
155
+ // 将弹出层添加到指定的容器中
156
+ appendToContainer() {
157
+ const container =
158
+ typeof this.getContainer === 'function'
159
+ ? this.getContainer()
160
+ : document.querySelector(this.getContainer);
161
+ if (container && container !== this.$el.parentNode) {
162
+ container.appendChild(this.$el);
163
+ }
164
+ },
119
165
  },
120
166
  mounted() {
167
+ if (inBrowser) {
168
+ this.appendToContainer();
169
+ if (this.lockScroll && this.visible) {
170
+ document.body.style.overflow = 'hidden';
171
+ }
172
+ }
173
+ },
174
+ beforeDestroy() {
175
+ if (inBrowser){
176
+ if(this.lockScroll) {
177
+ document.body.style.overflow = '';
178
+ }
179
+ // 从容器中移除弹出层元素
180
+ if (this.$el.parentNode) {
181
+ this.$el.parentNode.removeChild(this.$el);
182
+ }
183
+ }
121
184
  }
122
185
 
123
186
  }
@@ -126,23 +189,4 @@ export default {
126
189
  <style lang="less">
127
190
  @import './style.less';
128
191
 
129
- .vd-activityviews-fade-enter-active,
130
- .vd-activityviews-fade-leave-active {
131
- transition: opacity 0.3s;
132
- }
133
-
134
- .vd-activityviews-fade-enter,
135
- .vd-activityviews-fade-leave-to {
136
- opacity: 0;
137
- }
138
-
139
- .vd-activityviews-slide-up-enter-active,
140
- .vd-activityviews-slide-up-leave-active {
141
- transition: transform 0.3s;
142
- }
143
-
144
- .vd-activityviews-slide-up-enter,
145
- .vd-activityviews-slide-up-leave-to {
146
- transform: translateY(100%);
147
- }
148
192
  </style>
@@ -1,6 +1,11 @@
1
1
  @activityviews-prefix-cls: vd-activityviews;
2
2
 
3
3
  .@{activityviews-prefix-cls} {
4
+
5
+ &--safe-area-inset-bottom{
6
+ padding-bottom: constant(safe-area-inset-bottom);
7
+ padding-bottom: env(safe-area-inset-bottom);
8
+ }
4
9
 
5
10
  ::-webkit-scrollbar { width: 3px; }
6
11
  position: fixed;
@@ -13,6 +18,7 @@
13
18
  flex-direction: column;
14
19
  max-height: 80%;
15
20
  overflow: auto;
21
+ transition: transform .3s;
16
22
  background-color: var(--color-activityviews-bg);
17
23
  border-radius: var(--radius-activityviews-title) var(--radius-activityviews-title) 0 0;
18
24
 
@@ -132,3 +138,15 @@
132
138
  }
133
139
  }
134
140
  }
141
+
142
+
143
+ .vd-activityviews-slide-up-enter-active,
144
+ .vd-activityviews-slide-up-leave-active {
145
+ transition-property: transform;
146
+ transition-timing-function: ease;
147
+ }
148
+
149
+ .vd-activityviews-slide-up-enter,
150
+ .vd-activityviews-slide-up-leave-to {
151
+ transform: translateY(100%);
152
+ }
@@ -8,7 +8,11 @@
8
8
  <div class="vd-dialog__header--img-content" v-if="$slots.imgs">
9
9
  <slot name="imgs"></slot>
10
10
  </div>
11
- <div class="vd-dialog__title">{{ title }}</div>
11
+ <div class="vd-dialog__title" v-if="title || $slots.title">
12
+ <slot name="title">
13
+ {{ title }}
14
+ </slot>
15
+ </div>
12
16
  </header>
13
17
  <div class="vd-dialog__content">
14
18
  <div class="vd-dialog__message" :class="{ 'vd-dialog__message--head': !title }"
@@ -1,9 +1,17 @@
1
1
  <template>
2
2
  <div class="vd-empty" :class="wrapClasses">
3
- <div class="vd-empty__img"><img :src="currentImg" alt="img"></div>
4
- <p class="vd-empty__txt" :class="textClasses" v-if="description">{{ description }}</p>
5
- <div class="vd-empty__footer" :class="footerClasses" v-if="$slots.btn">
6
- <slot name="btn"></slot>
3
+ <div class="vd-empty__img" v-if="size !== 'card'">
4
+ <slot name="image">
5
+ <img :src="currentImage" alt="empty" />
6
+ </slot>
7
+ </div>
8
+ <p class="vd-empty__txt" :class="textClasses" v-if="description || $slots.description">
9
+ <slot name="description">
10
+ {{ description }}
11
+ </slot>
12
+ </p>
13
+ <div class="vd-empty__footer" :class="footerClasses" v-if="$slots.default">
14
+ <slot></slot>
7
15
  </div>
8
16
  </div>
9
17
  </template>
@@ -16,24 +24,23 @@ export default {
16
24
  name: 'vd-empty',
17
25
  mixins: [themeMixin], // 引入混入
18
26
  props: {
19
- imgs: String,
20
- description: String,
21
- size: {
22
- type:String,
23
- default: 'large', // 大场景布局,使用L尺寸 小场景内容居中使用 S 尺寸 card(空状态>1 时,不显示图片,只显示暂无数据的文字)
24
- },
25
- type: {
27
+ image: {
26
28
  type: String,
27
29
  default: 'nodata', // 默认类型
28
30
  validator(value) {
29
31
  // 只接受以下类型值
30
- return ['nodata', 'network', 'not-found','noposition','nomargin','nocoupons','nosearch','nonotice','noorders','noocomments'].includes(value);
32
+ return ['nodata', 'network', 'notfound','noposition','nomargin','nocoupons','nosearch','nonotice','noorders','noocomments'].includes(value);
31
33
  }
32
34
  },
35
+ description: String,
36
+ size: {
37
+ type:String,
38
+ default: 'large', // 大场景布局,使用L尺寸 小场景内容居中使用 S 尺寸 card(空状态>1 时,不显示图片,只显示暂无数据的文字)
39
+ },
33
40
  },
34
41
  data() {
35
42
  return {
36
- defaultImgs: {
43
+ defaultImages: {
37
44
  nodata: {
38
45
  light: require('./nodata.png'),
39
46
  dark: require('./nodata-dark.png')
@@ -42,7 +49,7 @@ export default {
42
49
  light: require('./network.png'),
43
50
  dark: require('./network-dark.png')
44
51
  },
45
- 'not-found': {
52
+ notfound: {
46
53
  light: require('./404.png'),
47
54
  dark: require('./404-dark.png')
48
55
  },
@@ -78,35 +85,41 @@ export default {
78
85
  }
79
86
  },
80
87
  computed: {
81
- currentImg() {
82
- if (this.imgs) return this.imgs;
83
- return this.defaultImgs[this.type][this.theme];
88
+ currentImage() {
89
+ const image = this.image;
90
+ if (image && (image.startsWith('http') || image.startsWith('data:image'))) {
91
+ return image;
92
+ } else if (this.defaultImages[image]) {
93
+ return this.defaultImages[image][this.theme];
94
+ } else {
95
+ return this.defaultImages['nodata'][this.theme];
96
+ }
84
97
  },
85
98
  wrapClasses() {
86
99
  return [
87
100
  {
88
- [`${prefixCls}-${this.size}`]: this.size
101
+ [`${prefixCls}--${this.size}`]: this.size
89
102
  }
90
103
  ]
91
104
  },
92
105
  textClasses() {
93
106
  return [
94
107
  {
95
- [`${prefixCls}-${this.size}__txt`]: this.size
108
+ [`${prefixCls}--${this.size}__txt`]: this.size
96
109
  }
97
110
  ]
98
111
  },
99
112
  footerClasses() {
100
113
  return [
101
114
  {
102
- [`${prefixCls}-${this.size}__footer`]: this.size
115
+ [`${prefixCls}--${this.size}__footer`]: this.size
103
116
  }
104
117
  ]
105
118
  }
106
119
  },
107
120
  methods: {
108
121
  preloadImages() {
109
- Object.values(this.defaultImgs).forEach((modes) => {
122
+ Object.values(this.defaultImages).forEach((modes) => {
110
123
  Object.values(modes).forEach((src) => {
111
124
  const img = new Image();
112
125
  img.src = src;
@@ -1,18 +1,17 @@
1
1
  @empty-prefix-cls: vd-empty;
2
2
 
3
3
  .@{empty-prefix-cls} {
4
- height: 100%;
5
- margin: 0 auto;
6
- text-align: center;
7
- padding: 0 20px;
8
4
  display: flex;
9
5
  flex-direction: column;
10
6
  justify-content: center;
7
+ align-items: center;
8
+ box-sizing: border-box;
9
+ padding: 0 20px;
11
10
 
12
11
  &__img {
13
12
  width: 164px;
14
13
  height: 118px;
15
- margin: 0 auto;
14
+
16
15
  img {
17
16
  width: 100%;
18
17
  height: 100%;
@@ -30,7 +29,7 @@
30
29
  justify-content: center;
31
30
  }
32
31
 
33
- &-small {
32
+ &--small {
34
33
  &__txt {
35
34
  margin-block-start: var(--spacing-empty-small-text-margin_top);
36
35
  }
@@ -40,7 +39,7 @@
40
39
  }
41
40
  }
42
41
 
43
- &-large {
42
+ &--large {
44
43
  &__txt {
45
44
  margin-block-start: var(--spacing-empty-large-text-margin_top);
46
45
  }
@@ -50,19 +49,9 @@
50
49
  }
51
50
  }
52
51
 
53
- &-card {
52
+ &--card {
54
53
  height: var(--height-empty-card);
55
54
 
56
- .@{empty-prefix-cls} {
57
- &__img {
58
- display: none;
59
- }
60
-
61
- &__footer {
62
- display: none;
63
- }
64
- }
65
-
66
55
  &__txt {
67
56
  margin-block-start: 0;
68
57
  font-size: var(--en-single-f-c-r-fontSize);
@@ -1,4 +1,3 @@
1
- // src/mixins/languageMixin.js
2
1
  import { inBrowser } from '../utils/env';
3
2
 
4
3
  export default {
@@ -0,0 +1,7 @@
1
+ import Popup from './index.vue';
2
+
3
+ Popup.install = function (Vue) {
4
+ Vue.component(Popup.name, Popup);
5
+ };
6
+
7
+ export default Popup;
@@ -0,0 +1,243 @@
1
+ <template>
2
+ <div>
3
+ <!-- 遮罩层 -->
4
+ <div
5
+ v-if="overlay && value"
6
+ class="popup-overlay"
7
+ :style="overlayCustomStyle"
8
+ @click="onClickOverlay"
9
+ ></div>
10
+
11
+ <!-- 弹出层 -->
12
+ <transition :name="transitionName">
13
+ <div
14
+ v-if="value"
15
+ :class="['popup', positionClass]"
16
+ :style="[popupStyle, transitionStyle]"
17
+ >
18
+ <slot></slot>
19
+ </div>
20
+ </transition>
21
+ </div>
22
+ </template>
23
+
24
+ <script>
25
+ export default {
26
+ name: 'Popup',
27
+ props: {
28
+ // 弹出位置,可选值:'top'、'bottom'、'left'、'right'、'center'
29
+ position: {
30
+ type: String,
31
+ default: 'center',
32
+ },
33
+ // 动画持续时间,单位毫秒
34
+ duration: {
35
+ type: Number,
36
+ default: 300,
37
+ },
38
+ // 指定挂载的容器,可以是选择器字符串或返回元素的函数
39
+ getContainer: {
40
+ type: [String, Function],
41
+ default: 'body',
42
+ },
43
+ // 是否锁定滚动
44
+ lockScroll: {
45
+ type: Boolean,
46
+ default: true,
47
+ },
48
+ // 是否适配底部安全区(针对全面屏手机)
49
+ safeAreaInsetBottom: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ // 控制弹层显示或隐藏,支持 v-model 双向绑定
54
+ value: {
55
+ type: Boolean,
56
+ default: false,
57
+ },
58
+ // 是否显示遮罩层
59
+ overlay: {
60
+ type: Boolean,
61
+ default: true,
62
+ },
63
+ // 遮罩层的自定义样式
64
+ overlayStyle: {
65
+ type: Object,
66
+ default: () => ({}),
67
+ },
68
+ // 是否点击遮罩层关闭弹出层
69
+ closeOnClickOverlay: {
70
+ type: Boolean,
71
+ default: true,
72
+ },
73
+ },
74
+ computed: {
75
+ // 根据 position 属性计算对应的类名
76
+ positionClass() {
77
+ return `popup-${this.position}`;
78
+ },
79
+ // 根据 position 属性计算对应的过渡名称
80
+ transitionName() {
81
+ return `popup-slide-${this.position}`;
82
+ },
83
+ // 根据 safeAreaInsetBottom 属性计算弹出层的样式
84
+ popupStyle() {
85
+ const style = {};
86
+ if (this.safeAreaInsetBottom && (this.position === 'bottom' || this.position === 'center')) {
87
+ style.paddingBottom = 'env(safe-area-inset-bottom)';
88
+ }
89
+ return style;
90
+ },
91
+ // 动态设置过渡持续时间的样式
92
+ transitionStyle() {
93
+ return {
94
+ transitionDuration: `${this.duration}ms`,
95
+ };
96
+ },
97
+ // 合并遮罩层的自定义样式
98
+ overlayCustomStyle() {
99
+ return Object.assign(
100
+ {
101
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
102
+ },
103
+ this.overlayStyle
104
+ );
105
+ },
106
+ },
107
+ watch: {
108
+ // 监听 value 属性的变化,控制弹出层的显示和隐藏
109
+ value(val) {
110
+ if (val) {
111
+ this.openPopup();
112
+ } else {
113
+ this.closePopup();
114
+ }
115
+ },
116
+ },
117
+ mounted() {
118
+ // 组件挂载时,根据初始的 value 值决定是否打开弹出层
119
+ if (this.value) {
120
+ this.openPopup();
121
+ }
122
+ },
123
+ methods: {
124
+ // 打开弹出层
125
+ openPopup() {
126
+ if (this.lockScroll) {
127
+ document.body.style.overflow = 'hidden';
128
+ }
129
+ this.appendToContainer();
130
+ },
131
+ // 关闭弹出层
132
+ closePopup() {
133
+ if (this.lockScroll) {
134
+ document.body.style.overflow = '';
135
+ }
136
+ },
137
+ // 将弹出层添加到指定的容器中
138
+ appendToContainer() {
139
+ const container =
140
+ typeof this.getContainer === 'function'
141
+ ? this.getContainer()
142
+ : document.querySelector(this.getContainer);
143
+ if (container && container !== this.$el.parentNode) {
144
+ container.appendChild(this.$el);
145
+ }
146
+ },
147
+ // 点击遮罩层关闭弹出层
148
+ onClickOverlay() {
149
+ if (this.closeOnClickOverlay) {
150
+ this.$emit('input', false);
151
+ }
152
+ },
153
+ },
154
+ beforeDestroy() {
155
+ // 组件销毁前,恢复滚动
156
+ if (this.lockScroll) {
157
+ document.body.style.overflow = '';
158
+ }
159
+ },
160
+ };
161
+ </script>
162
+
163
+ <style scoped>
164
+ .popup-overlay {
165
+ position: fixed;
166
+ top: 0;
167
+ left: 0;
168
+ width: 100%;
169
+ height: 100%;
170
+ z-index: 999;
171
+ }
172
+
173
+ .popup {
174
+ position: fixed;
175
+ z-index: 1000;
176
+ }
177
+
178
+ .popup-center {
179
+ top: 50%;
180
+ left: 50%;
181
+ transform: translate(-50%, -50%);
182
+ }
183
+
184
+ .popup-top {
185
+ top: 0;
186
+ left: 0;
187
+ width: 100%;
188
+ }
189
+
190
+ .popup-bottom {
191
+ bottom: 0;
192
+ left: 0;
193
+ width: 100%;
194
+ }
195
+
196
+ .popup-left {
197
+ top: 0;
198
+ left: 0;
199
+ height: 100%;
200
+ }
201
+
202
+ .popup-right {
203
+ top: 0;
204
+ right: 0;
205
+ height: 100%;
206
+ }
207
+
208
+ /* 过渡效果 */
209
+ .popup-slide-center-enter-active,
210
+ .popup-slide-center-leave-active,
211
+ .popup-slide-top-enter-active,
212
+ .popup-slide-top-leave-active,
213
+ .popup-slide-bottom-enter-active,
214
+ .popup-slide-bottom-leave-active,
215
+ .popup-slide-left-enter-active,
216
+ .popup-slide-left-leave-active,
217
+ .popup-slide-right-enter-active,
218
+ .popup-slide-right-leave-active {
219
+ transition-property: all;
220
+ transition-timing-function: ease;
221
+ }
222
+
223
+ .popup-slide-top-enter,
224
+ .popup-slide-top-leave-to {
225
+ transform: translateY(-100%);
226
+ }
227
+ .popup-slide-bottom-enter,
228
+ .popup-slide-bottom-leave-to {
229
+ transform: translateY(100%);
230
+ }
231
+ .popup-slide-left-enter,
232
+ .popup-slide-left-leave-to {
233
+ transform: translateX(-100%);
234
+ }
235
+ .popup-slide-right-enter,
236
+ .popup-slide-right-leave-to {
237
+ transform: translateX(100%);
238
+ }
239
+ .popup-slide-center-enter,
240
+ .popup-slide-center-leave-to {
241
+ opacity: 0;
242
+ }
243
+ </style>