v-uni-app-ui 1.0.0 → 1.0.2

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 (61) hide show
  1. package/components/config.js +123 -0
  2. package/components/layout/v-card/v-card.vue +108 -0
  3. package/components/layout/v-grid/v-grid.vue +162 -0
  4. package/components/layout/v-icon-grid/v-icon-grid.vue +195 -0
  5. package/components/layout/v-infinite-scroll/v-infinite-scroll.vue +172 -0
  6. package/components/layout/v-list/v-list.vue +43 -0
  7. package/components/layout/v-row/v-row.vue +142 -0
  8. package/components/layout/v-waterfall/v-waterfall.vue +79 -0
  9. package/components/model/compound/v-checkbox-group/v-checkbox-group.vue +96 -0
  10. package/components/model/compound/v-console/v-console.js +20 -0
  11. package/components/model/compound/v-console/v-console.vue +299 -0
  12. package/components/model/compound/v-date-time/v-date-time.vue +261 -0
  13. package/components/model/compound/v-dialog/v-dialog.vue +178 -0
  14. package/components/model/compound/v-drum-select-picker/v-drum-select-picker.vue +83 -0
  15. package/components/model/compound/v-form/v-form.vue +226 -0
  16. package/components/model/compound/v-form-item/v-form-item.vue +255 -0
  17. package/components/model/compound/v-image/v-image.vue +357 -0
  18. package/components/model/compound/v-input-desensitize/v-input-desensitize.vue +101 -0
  19. package/components/model/compound/v-page/v-page.vue +11 -0
  20. package/components/model/compound/v-pages/v-pages.vue +141 -0
  21. package/components/model/compound/v-picker-list/v-picker-list.vue +109 -0
  22. package/components/model/compound/v-popup/v-popup.vue +151 -0
  23. package/components/model/compound/v-radio-group/v-radio-group.vue +86 -0
  24. package/components/model/compound/v-select-picker/v-select-picker.vue +202 -0
  25. package/components/model/compound/v-series-picker-list/v-series-picker-list.vue +221 -0
  26. package/components/model/compound/v-series-select-picker/v-series-select-picker.vue +203 -0
  27. package/components/model/compound/v-switch/v-switch.vue +136 -0
  28. package/components/model/compound/v-tabs-page/v-tabs-page.vue +138 -0
  29. package/components/model/native/v-badge/v-badge.vue +143 -0
  30. package/components/model/native/v-button/v-button.vue +273 -0
  31. package/components/model/native/v-carousel/v-carousel.vue +138 -0
  32. package/components/model/native/v-checkbox/v-checkbox.vue +215 -0
  33. package/components/model/native/v-collapse/v-collapse.vue +190 -0
  34. package/components/model/native/v-header-navigation-bar/v-header-navigation-bar.vue +92 -0
  35. package/components/model/native/v-input/v-input.vue +352 -0
  36. package/components/model/native/v-input-code/v-input-code.vue +146 -0
  37. package/components/model/native/v-loading/v-loading.vue +206 -0
  38. package/components/model/native/v-menu/v-menu.vue +222 -0
  39. package/components/model/native/v-menu-slide/v-menu-slide.vue +364 -0
  40. package/components/model/native/v-min-loading/v-min-loading.vue +80 -0
  41. package/components/model/native/v-null/v-null.vue +97 -0
  42. package/components/model/native/v-overlay/v-overlay.vue +96 -0
  43. package/components/model/native/v-pull-up-refresh/v-pull-up-refresh.vue +157 -0
  44. package/components/model/native/v-radio/v-radio.vue +138 -0
  45. package/components/model/native/v-scroll-list/v-scroll-list.vue +169 -0
  46. package/components/model/native/v-steps/v-steps.vue +253 -0
  47. package/components/model/native/v-table/v-table.vue +203 -0
  48. package/components/model/native/v-tabs/v-tabs.vue +235 -0
  49. package/components/model/native/v-tag/v-tag.vue +206 -0
  50. package/components/model/native/v-text/v-text.vue +187 -0
  51. package/components/model/native/v-text-button/v-text-button.vue +139 -0
  52. package/components/model/native/v-textarea/v-textarea.vue +178 -0
  53. package/components/model/native/v-title/v-title.vue +91 -0
  54. package/components/model/native/v-toast/info.png +0 -0
  55. package/components/model/native/v-toast/success.png +0 -0
  56. package/components/model/native/v-toast/v-toast.vue +198 -0
  57. package/components/model/native/v-toast/warn.png +0 -0
  58. package/components/model/native/v-upload-file-button/v-upload-file-button.vue +296 -0
  59. package/components/model/native/v-video/v-video.vue +175 -0
  60. package/components/model/native/v-window/v-window.vue +158 -0
  61. package/package.json +18 -94
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <view
3
+ ref="rootRef"
4
+ class="v-menu-container"
5
+ :class="[`v-menu-container--position--${menuPosition}`, { 'v-slide-x': slideAxis === 'x' }, { 'v-slide-y': slideAxis === 'y' }]"
6
+ @touchstart="onTouchStart"
7
+ @touchmove="onTouchMove"
8
+ @touchend="onTouchEnd"
9
+ @mousedown="onMouseDown"
10
+ @mousemove="onMouseMove"
11
+ @mouseup="onMouseUp"
12
+ >
13
+ <!-- 菜单栏 -->
14
+ <view class="v-menu">
15
+ <view v-for="item in menuItems" :key="item.id" :class="['menu-item', { active: activeMenuId === item.id }]" @click="handleMenuItemClick(item.id, item)">
16
+ {{ item.title }}
17
+ </view>
18
+ </view>
19
+
20
+ <!-- 内容区 -->
21
+ <view class="menu-content">
22
+ <template v-for="(item, index) in menuItems" :key="item.id">
23
+ <view v-show="activeMenuId === item.id">
24
+ <slot :name="`content-${index}`" :item="item">
25
+ </slot>
26
+ </view>
27
+ </template>
28
+ </view>
29
+ </view>
30
+ </template>
31
+
32
+ <script lang="ts" setup>
33
+ import { ref, watch, inject, computed, getCurrentInstance, nextTick,provide } from 'vue';
34
+
35
+
36
+ interface MenuItem {
37
+ id: string;
38
+ title: string;
39
+ }
40
+
41
+ /* ========= props ========= */
42
+ const props = defineProps({
43
+ menuPosition: {
44
+ type: String,
45
+ default: 'left',
46
+ validator: (v: string) => ['left', 'right'].includes(v)
47
+ },
48
+ menuItems: {
49
+ type: Array as () => MenuItem[],
50
+ required: true
51
+ },
52
+ value: {
53
+ type: String,
54
+ default: ''
55
+ },
56
+ isSlide: {
57
+ // 是否启用滑动
58
+ type: Boolean,
59
+ default: false
60
+ },
61
+ slideAxis: {
62
+ // 滑动方向
63
+ type: String as () => 'x' | 'y',
64
+ default: 'x'
65
+ }
66
+ });
67
+
68
+ const emit = defineEmits(['update:value', 'select']);
69
+
70
+ const config = inject<any>('config');
71
+ const activeMenuId = ref(props.value || props.menuItems[0]?.id || '');
72
+
73
+ /* ========= 滑动核心 ========= */
74
+ const rootRef = ref<HTMLElement>();
75
+ const startX = ref(0);
76
+ const startY = ref(0);
77
+ const isTracking = ref(false);
78
+ const SLIDE_THRESHOLD = 50;
79
+
80
+ const currentIndex = computed(() => props.menuItems.findIndex((item) => item.id === activeMenuId.value));
81
+
82
+ const next = () => {
83
+ const len = props.menuItems.length;
84
+ const idx = (currentIndex.value + 1 + len) % len;
85
+ handleMenuItemClick(props.menuItems[idx].id, props.menuItems[idx]);
86
+ };
87
+
88
+ const prev = () => {
89
+ const len = props.menuItems.length;
90
+ const idx = (currentIndex.value - 1 + len) % len;
91
+ handleMenuItemClick(props.menuItems[idx].id, props.menuItems[idx]);
92
+ };
93
+
94
+ const handleMenuItemClick = (id: string, row: MenuItem) => {
95
+ activeMenuId.value = id;
96
+ emit('update:value', id);
97
+ emit('select', id, row);
98
+ };
99
+
100
+ /* 统一手势处理 */
101
+ const onStart = (x: number, y: number) => {
102
+ if (!props.isSlide) return;
103
+ startX.value = x;
104
+ startY.value = y;
105
+ isTracking.value = true;
106
+ };
107
+
108
+ const onMove = (x: number, y: number) => {
109
+ if (!isTracking.value) return;
110
+ const dx = x - startX.value;
111
+ const dy = y - startY.value;
112
+ const dir = props.slideAxis === 'x' ? dx : dy;
113
+ const orth = props.slideAxis === 'x' ? dy : dx;
114
+ if (Math.abs(orth) > Math.abs(dir)) return; // 优先同轴
115
+ };
116
+
117
+ const onEnd = (x: number, y: number) => {
118
+ if (!isTracking.value) return;
119
+ isTracking.value = false;
120
+ const delta = props.slideAxis === 'x' ? x - startX.value : y - startY.value;
121
+ if (delta > SLIDE_THRESHOLD) prev();
122
+ else if (delta < -SLIDE_THRESHOLD) next();
123
+ };
124
+
125
+ /* Touch 事件 */
126
+ const onTouchStart = (e: TouchEvent) => onStart(e.touches[0].clientX, e.touches[0].clientY);
127
+
128
+ const onTouchMove = (e: TouchEvent) => {
129
+ if (!props.isSlide) return;
130
+ onMove(e.touches[0].clientX, e.touches[0].clientY);
131
+ e.preventDefault?.();
132
+ };
133
+
134
+ const onTouchEnd = (e: TouchEvent) => onEnd(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
135
+
136
+ /* Mouse 事件(PC H5) */
137
+ const onMouseDown = (e: MouseEvent) => onStart(e.clientX, e.clientY);
138
+ const onMouseMove = (e: MouseEvent) => {
139
+ if (!isTracking.value) return;
140
+ onMove(e.clientX, e.clientY);
141
+ e.preventDefault?.();
142
+ };
143
+ const onMouseUp = (e: MouseEvent) => onEnd(e.clientX, e.clientY);
144
+
145
+ /* 监听 v-model 外部更新 */
146
+ watch(
147
+ () => props.value,
148
+ (newVal) => {
149
+ if (newVal && newVal !== activeMenuId.value) {
150
+ activeMenuId.value = newVal;
151
+ }
152
+ }
153
+ );
154
+
155
+ /* H5:禁止横向/纵向滚动冲突 */
156
+ const isH5 = typeof plus === 'undefined';
157
+ if (isH5) {
158
+ nextTick(() => {
159
+ const el = (getCurrentInstance()?.proxy as any)?.$el as HTMLElement;
160
+ if (el) {
161
+ el.style.overscrollBehaviorX = props.slideAxis === 'x' ? 'contain' : 'auto';
162
+ el.style.overscrollBehaviorY = props.slideAxis === 'y' ? 'contain' : 'auto';
163
+ }
164
+ });
165
+ }
166
+ </script>
167
+
168
+ <style lang="scss" scoped>
169
+ .v-menu-container {
170
+ display: flex;
171
+ width: 100%;
172
+ height: 100%;
173
+ touch-action: pan-y; /* 允许垂直滚动,水平自己接管 */
174
+
175
+
176
+
177
+ &--position--right {
178
+ flex-direction: row-reverse;
179
+ }
180
+ }
181
+
182
+ .v-menu {
183
+ display: flex;
184
+ flex-direction: column;
185
+ background-color: v-bind('config.VMenu.backgroundColor');
186
+ border-right: 1rpx solid v-bind('config.border.color');
187
+ width: 200rpx;
188
+ height: 100%;
189
+ overflow-y: auto;
190
+ transition: all 0.3s ease;
191
+ border-radius: 6rpx 0 0 6rpx;
192
+ }
193
+
194
+ .menu-item {
195
+ padding: 12rpx 16rpx;
196
+ cursor: pointer;
197
+ color: v-bind('config.fontColor.subTitle');
198
+ transition: all 0.2s ease;
199
+ border-bottom: 1rpx solid transparent;
200
+
201
+ &:hover {
202
+ background-color: v-bind('config.VMenu.highlightBackgroundColor');
203
+ }
204
+
205
+ &.active {
206
+ background-color: v-bind('config.VMenu.highlightBackgroundColor');
207
+ color: v-bind('config.fontColor.mainText');
208
+ font-weight: bold;
209
+ border-left: 6rpx solid v-bind('config.border.color');
210
+ }
211
+ }
212
+
213
+ .menu-content {
214
+ flex: 1;
215
+ padding: 16rpx;
216
+ background-color: v-bind('config.VMenu.reversal');
217
+ min-height: 600rpx;
218
+ overflow: auto;
219
+ box-shadow: -2rpx 0 5px rgba(0, 0, 0, 0.05);
220
+ }
221
+
222
+ </style>
@@ -0,0 +1,364 @@
1
+ <template>
2
+ <view class="v-menu-slide" :class="[`v-menu-slide--${direction}`]">
3
+ <!-- 菜单标题区域 -->
4
+ <view :class="['menu-header', `menu-header--${direction}`]">
5
+ <view
6
+ v-for="(titleItem, index) in processedTitleItems"
7
+ :key="index"
8
+ :class="['menu-item', { 'menu-item--active': activeIndex === index }]"
9
+ @click="selectMenuItem(index)"
10
+ >
11
+ <slot name="title" :item="titleItem" :index="index" :active="activeIndex === index" :direction="direction">
12
+ <view class="menu-item__content" :class="[`menu-item__content--${direction}`]">
13
+ <text class="menu-item__text">{{ titleItem.title }}</text>
14
+ </view>
15
+ </slot>
16
+ </view>
17
+ </view>
18
+
19
+ <!-- 内容区域 -->
20
+ <view class="menu-content">
21
+ <swiper :current="activeIndex" :vertical="direction === 'y'" :duration="transitionDuration" @change="onSwiperChange" class="menu-content-swiper">
22
+ <swiper-item v-for="(contentItem, index) in processedContentItems" :key="index">
23
+ <view class="content-item">
24
+ <slot name="content" :item="contentItem" :index="index">
25
+ <text class="content-default-text">{{ contentItem.content || `内容 ${index + 1}` }}</text>
26
+ </slot>
27
+ </view>
28
+ </swiper-item>
29
+ </swiper>
30
+ </view>
31
+ </view>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { ref, watch, onMounted, computed } from 'vue';
36
+
37
+ const props = defineProps({
38
+ // 菜单项数据(合并格式)
39
+ items: {
40
+ type: Array,
41
+ default: () => []
42
+ },
43
+ // 菜单标题项数据(分开格式)
44
+ titleItems: {
45
+ type: Array,
46
+ default: () => []
47
+ },
48
+ // 菜单内容项数据(分开格式)
49
+ contentItems: {
50
+ type: Array,
51
+ default: () => []
52
+ },
53
+ // 排列方向:x(水平)或 y(垂直)
54
+ direction: {
55
+ type: String,
56
+ default: 'x',
57
+ validator: (value) => ['x', 'y'].includes(value)
58
+ },
59
+ // 默认激活的菜单项索引
60
+ defaultActive: {
61
+ type: Number,
62
+ default: 0
63
+ },
64
+ // 过渡动画时长(毫秒)
65
+ transitionDuration: {
66
+ type: Number,
67
+ default: 300
68
+ },
69
+ // 标题数据的关联键名
70
+ titleKey: {
71
+ type: String,
72
+ default: null
73
+ },
74
+ // 内容数据的关联键名
75
+ contentKey: {
76
+ type: String,
77
+ default: null
78
+ },
79
+ // 向后兼容的关联键名(如果设置了,会同时设置 titleKey 和 contentKey)
80
+ relationKey: {
81
+ type: String,
82
+ default: null
83
+ }
84
+ });
85
+
86
+ const emit = defineEmits(['update:active', 'change']);
87
+
88
+ // 当前激活的菜单项索引
89
+ const activeIndex = ref(props.defaultActive);
90
+
91
+ // 计算实际使用的键名
92
+ const effectiveTitleKey = computed(() => {
93
+ if (props.titleKey) return props.titleKey;
94
+ if (props.relationKey) return props.relationKey;
95
+ return null; // 默认按索引匹配
96
+ });
97
+
98
+ const effectiveContentKey = computed(() => {
99
+ if (props.contentKey) return props.contentKey;
100
+ if (props.relationKey) return props.relationKey;
101
+ return null; // 默认按索引匹配
102
+ });
103
+
104
+ // 检测数据格式
105
+ const isSeparateFormat = computed(() => {
106
+ return props.titleItems.length > 0 && props.contentItems.length > 0;
107
+ });
108
+
109
+ // 处理标题项数据
110
+ const processedTitleItems = computed(() => {
111
+ if (isSeparateFormat.value) {
112
+ return props.titleItems;
113
+ }
114
+ return props.items;
115
+ });
116
+
117
+ // 处理内容项数据
118
+ const processedContentItems = computed(() => {
119
+ if (isSeparateFormat.value) {
120
+ // 分开格式:根据关联键匹配内容
121
+ if (effectiveTitleKey.value && effectiveContentKey.value && props.titleItems.length > 0 && props.contentItems.length > 0) {
122
+ return props.titleItems.map((titleItem) => {
123
+ const titleValue = titleItem[effectiveTitleKey.value];
124
+ const matchedContent = props.contentItems.find((contentItem) => contentItem[effectiveContentKey.value] === titleValue);
125
+ return matchedContent || { content: '暂无内容' };
126
+ });
127
+ }
128
+
129
+ // 分开格式:按索引匹配
130
+ return props.contentItems.length > 0 ? props.contentItems : props.titleItems.map(() => ({}));
131
+ }
132
+
133
+ // 合并格式:直接使用 items
134
+ return props.items;
135
+ });
136
+
137
+ // 选择菜单项
138
+ const selectMenuItem = (index) => {
139
+ if (activeIndex.value === index) return;
140
+
141
+ activeIndex.value = index;
142
+ emit('update:active', index);
143
+ emit('change', index);
144
+ };
145
+
146
+ // Swiper 切换回调
147
+ const onSwiperChange = (e) => {
148
+ const index = e.detail.current;
149
+ if (activeIndex.value === index) return;
150
+
151
+ activeIndex.value = index;
152
+ emit('update:active', index);
153
+ emit('change', index);
154
+ };
155
+
156
+ // 监听外部 active 变化
157
+ watch(
158
+ () => props.defaultActive,
159
+ (newVal) => {
160
+ if (activeIndex.value !== newVal) {
161
+ activeIndex.value = newVal;
162
+ }
163
+ }
164
+ );
165
+
166
+ // 监听 items 变化
167
+ watch(
168
+ () => [props.items, props.titleItems, props.contentItems],
169
+ () => {
170
+ // 数据变化时重置激活索引(可选)
171
+ const maxIndex = isSeparateFormat.value ? Math.min(props.titleItems.length, props.contentItems.length) - 1 : props.items.length - 1;
172
+
173
+ if (activeIndex.value > maxIndex) {
174
+ activeIndex.value = Math.max(0, maxIndex);
175
+ }
176
+ },
177
+ { deep: true }
178
+ );
179
+
180
+ // 初始化
181
+ onMounted(() => {
182
+ if (props.items.length === 0 && props.titleItems.length === 0) {
183
+ console.warn('v-menu-slide: 请提供菜单数据(items 或 titleItems/contentItems)');
184
+ }
185
+
186
+ // 数据格式检查
187
+ if (props.items.length > 0 && (props.titleItems.length > 0 || props.contentItems.length > 0)) {
188
+ console.warn('v-menu-slide: 检测到同时提供了 items 和 titleItems/contentItems,将优先使用 titleItems/contentItems');
189
+ }
190
+
191
+ // 键名检查
192
+ if (isSeparateFormat.value && effectiveTitleKey.value && effectiveContentKey.value) {
193
+ // 检查标题数据中是否包含指定的键
194
+ if (props.titleItems.length > 0 && !props.titleItems[0].hasOwnProperty(effectiveTitleKey.value)) {
195
+ console.warn(`v-menu-slide: 标题数据中未找到键 "${effectiveTitleKey.value}",将按索引匹配`);
196
+ }
197
+
198
+ // 检查内容数据中是否包含指定的键
199
+ if (props.contentItems.length > 0 && !props.contentItems[0].hasOwnProperty(effectiveContentKey.value)) {
200
+ console.warn(`v-menu-slide: 内容数据中未找到键 "${effectiveContentKey.value}",将按索引匹配`);
201
+ }
202
+ }
203
+ });
204
+ </script>
205
+
206
+ <style lang="scss" scoped>
207
+ /* 样式保持不变,与之前相同 */
208
+ .v-menu-slide {
209
+ width: 100%;
210
+ height: 100%;
211
+ display: flex;
212
+
213
+ // 默认X轴方向
214
+ flex-direction: column;
215
+
216
+ // Y轴方向布局
217
+ &--y {
218
+ flex-direction: row;
219
+ }
220
+ }
221
+
222
+ // 菜单标题区域
223
+ .menu-header {
224
+ display: flex;
225
+ background-color: #fff;
226
+ z-index: 10;
227
+
228
+ &--x {
229
+ flex-direction: row;
230
+ overflow-x: auto;
231
+ white-space: nowrap;
232
+ // 隐藏滚动条(H5和App兼容)
233
+ &::-webkit-scrollbar {
234
+ display: none;
235
+ width: 0;
236
+ height: 0;
237
+ color: transparent;
238
+ }
239
+ }
240
+
241
+ &--y {
242
+ flex-direction: column;
243
+ width: 200rpx; // Y轴方向时固定宽度
244
+ flex-shrink: 0; // 防止被压缩
245
+ overflow-y: auto;
246
+ // 隐藏滚动条(H5和App兼容)
247
+ &::-webkit-scrollbar {
248
+ display: none;
249
+ width: 0;
250
+ height: 0;
251
+ color: transparent;
252
+ }
253
+ }
254
+ }
255
+
256
+ // 菜单项
257
+ .menu-item {
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ padding: 20rpx 30rpx;
262
+ position: relative;
263
+ transition: all 0.3s ease;
264
+ flex-shrink: 0; // 防止在x方向排列时被压缩
265
+
266
+ // Y轴方向时,菜单项内容横向排列
267
+ .menu-header--y & {
268
+ justify-content: flex-start;
269
+ }
270
+
271
+ &--active {
272
+ color: #007aff; // 激活状态颜色
273
+
274
+ &::after {
275
+ content: '';
276
+ position: absolute;
277
+ background-color: #007aff;
278
+ transition: all 0.3s ease;
279
+ }
280
+ }
281
+
282
+ // X方向激活指示器
283
+ .menu-header--x &--active::after {
284
+ bottom: 0;
285
+ left: 20%;
286
+ right: 20%;
287
+ height: 4rpx;
288
+ }
289
+
290
+ // Y方向激活指示器
291
+ .menu-header--y &--active::after {
292
+ top: 20%;
293
+ bottom: 20%;
294
+ left: 0;
295
+ width: 4rpx;
296
+ }
297
+ }
298
+
299
+ // 菜单项内容容器
300
+ .menu-item__content {
301
+ display: flex;
302
+ align-items: center;
303
+
304
+ // Y轴方向时,内容横向排列
305
+ &--y {
306
+ flex-direction: row;
307
+ }
308
+ }
309
+
310
+ .menu-item__text {
311
+ font-size: 28rpx;
312
+ font-weight: 500;
313
+ }
314
+
315
+ // 内容区域
316
+ .menu-content {
317
+ flex: 1;
318
+ position: relative;
319
+ overflow: hidden;
320
+ }
321
+
322
+ .menu-content-swiper {
323
+ width: 100%;
324
+ height: 100%;
325
+ }
326
+
327
+ .content-item {
328
+ width: 100%;
329
+ height: 100%;
330
+ padding: 20rpx;
331
+ box-sizing: border-box;
332
+ /* #ifdef H5 */
333
+ overflow: auto;
334
+ /* #endif */
335
+ // 隐藏滚动条(H5和App兼容)
336
+ &::-webkit-scrollbar {
337
+ display: none;
338
+ width: 0;
339
+ height: 0;
340
+ color: transparent;
341
+ }
342
+ }
343
+
344
+ .content-default-text {
345
+ font-size: 28rpx;
346
+ color: #666;
347
+ }
348
+
349
+ /* App和H5兼容性处理 */
350
+ /* #ifdef APP-PLUS */
351
+ .menu-header--x {
352
+ -webkit-overflow-scrolling: touch;
353
+ }
354
+ /* #endif */
355
+
356
+ /* #ifdef H5 */
357
+ // H5特定样式
358
+ @media (hover: hover) {
359
+ .menu-item:hover {
360
+ background-color: #f5f5f5;
361
+ }
362
+ }
363
+ /* #endif */
364
+ </style>
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <view v-show="value" :class="['spinner', `spinner--type--${props.type}`]"></view>
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { inject } from 'vue';
7
+
8
+ const props = defineProps({
9
+ value: {
10
+ type: Boolean,
11
+ default: false
12
+ },
13
+ type: {
14
+ type: String,
15
+ default: 'default'
16
+ },
17
+ customizeColor: {
18
+ type: String,
19
+ default: null
20
+ },
21
+ vacancyColor: {
22
+ type: String,
23
+ default: '#fff'
24
+ },
25
+ opacity: {
26
+ type: Number,
27
+ default: 0.5
28
+ }
29
+ });
30
+
31
+ const config = inject<any>('config');
32
+ const type = props.customizeColor === null ? '1px solid transparent' : `1px solid ${props.customizeColor}`;
33
+ </script>
34
+
35
+ <style lang="scss" scoped>
36
+ .spinner {
37
+ width: 20rpx;
38
+ height: 20rpx;
39
+ border-radius: 50%;
40
+ border-top-color: v-bind('props.vacancyColor') !important;
41
+ animation: spin 1s ease-in-out infinite;
42
+ margin: auto;
43
+ opacity: v-bind('props.opacity');
44
+
45
+ &--type--default {
46
+ border: 1rpx solid ;
47
+ border-color: v-bind("config.border.default");
48
+ }
49
+
50
+ &--type--warn {
51
+ border: 1rpx solid ;
52
+ border-color: v-bind("config.border.warn");
53
+ }
54
+
55
+ &--type--info {
56
+ border: 1rpx solid ;
57
+ border-color: v-bind("config.border.info");
58
+ }
59
+
60
+ &--type--delete {
61
+ border: 1rpx solid ;
62
+ border-color: v-bind("config.border.delete");
63
+ }
64
+
65
+ &--type--succeed {
66
+ border: 1rpx solid ;
67
+ border-color: v-bind("config.border.succeed");
68
+ }
69
+
70
+ &--type--customize {
71
+ border: v-bind('type');
72
+ }
73
+ }
74
+
75
+ @keyframes spin {
76
+ to {
77
+ transform: rotate(360deg);
78
+ }
79
+ }
80
+ </style>