muzhiyu-ui 1.0.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 (96) hide show
  1. package/README.md +202 -0
  2. package/components/muzhiyu/mu-action-sheet/mu-action-sheet.vue +335 -0
  3. package/components/muzhiyu/mu-alert/mu-alert.vue +220 -0
  4. package/components/muzhiyu/mu-back-top/mu-back-top.vue +161 -0
  5. package/components/muzhiyu/mu-badge/mu-badge.vue +150 -0
  6. package/components/muzhiyu/mu-barcode/mu-barcode.vue +100 -0
  7. package/components/muzhiyu/mu-blank/mu-blank.vue +30 -0
  8. package/components/muzhiyu/mu-bottom-nav/mu-bottom-nav.vue +331 -0
  9. package/components/muzhiyu/mu-bubble/mu-bubble.vue +316 -0
  10. package/components/muzhiyu/mu-button/mu-button.vue +431 -0
  11. package/components/muzhiyu/mu-calendar/mu-calendar.vue +429 -0
  12. package/components/muzhiyu/mu-car-input/mu-car-input.vue +209 -0
  13. package/components/muzhiyu/mu-card/mu-card.vue +317 -0
  14. package/components/muzhiyu/mu-cascader/mu-cascader.vue +336 -0
  15. package/components/muzhiyu/mu-cell/mu-cell.vue +368 -0
  16. package/components/muzhiyu/mu-checkbox/mu-checkbox.vue +116 -0
  17. package/components/muzhiyu/mu-checkbox-group/mu-checkbox-group.vue +40 -0
  18. package/components/muzhiyu/mu-code-input/mu-code-input.vue +264 -0
  19. package/components/muzhiyu/mu-collapse/mu-collapse.vue +316 -0
  20. package/components/muzhiyu/mu-color-picker/mu-color-picker.vue +466 -0
  21. package/components/muzhiyu/mu-config/index.js +34 -0
  22. package/components/muzhiyu/mu-config/mu-config.vue +54 -0
  23. package/components/muzhiyu/mu-countdown/mu-countdown.vue +338 -0
  24. package/components/muzhiyu/mu-cropper/mu-cropper.vue +415 -0
  25. package/components/muzhiyu/mu-date-picker/mu-date-picker.vue +290 -0
  26. package/components/muzhiyu/mu-datetime/mu-datetime.vue +262 -0
  27. package/components/muzhiyu/mu-divider/mu-divider.vue +229 -0
  28. package/components/muzhiyu/mu-drawer/mu-drawer.vue +172 -0
  29. package/components/muzhiyu/mu-dropdown/mu-dropdown.vue +215 -0
  30. package/components/muzhiyu/mu-dropdown-top/mu-dropdown-top.vue +243 -0
  31. package/components/muzhiyu/mu-empty/mu-empty.vue +217 -0
  32. package/components/muzhiyu/mu-fab/mu-fab.vue +324 -0
  33. package/components/muzhiyu/mu-footer/mu-footer.vue +21 -0
  34. package/components/muzhiyu/mu-form/mu-form.vue +170 -0
  35. package/components/muzhiyu/mu-grid/mu-grid.vue +95 -0
  36. package/components/muzhiyu/mu-grid-item/mu-grid-item.vue +247 -0
  37. package/components/muzhiyu/mu-html/mu-html.vue +145 -0
  38. package/components/muzhiyu/mu-icon/mu-icon.vue +822 -0
  39. package/components/muzhiyu/mu-image-grid/mu-image-grid.vue +225 -0
  40. package/components/muzhiyu/mu-image-preview/mu-image-preview.vue +286 -0
  41. package/components/muzhiyu/mu-index-list/mu-index-list.vue +381 -0
  42. package/components/muzhiyu/mu-input/mu-input.vue +570 -0
  43. package/components/muzhiyu/mu-input-number/mu-input-number.vue +193 -0
  44. package/components/muzhiyu/mu-keyboard/mu-keyboard.vue +415 -0
  45. package/components/muzhiyu/mu-list/mu-list.vue +89 -0
  46. package/components/muzhiyu/mu-loading/mu-loading.vue +124 -0
  47. package/components/muzhiyu/mu-loading-page/mu-loading-page.vue +529 -0
  48. package/components/muzhiyu/mu-loadmore/mu-loadmore.vue +26 -0
  49. package/components/muzhiyu/mu-modal/mu-modal.vue +298 -0
  50. package/components/muzhiyu/mu-navbar/mu-navbar.vue +223 -0
  51. package/components/muzhiyu/mu-nomore/mu-nomore.vue +107 -0
  52. package/components/muzhiyu/mu-notice-bar/mu-notice-bar.vue +284 -0
  53. package/components/muzhiyu/mu-notify/mu-notify.vue +202 -0
  54. package/components/muzhiyu/mu-overlay/mu-overlay.vue +94 -0
  55. package/components/muzhiyu/mu-pagination/mu-pagination.vue +330 -0
  56. package/components/muzhiyu/mu-password-input/mu-password-input.vue +119 -0
  57. package/components/muzhiyu/mu-picker/mu-picker.vue +297 -0
  58. package/components/muzhiyu/mu-popup/mu-popup.vue +297 -0
  59. package/components/muzhiyu/mu-progress/mu-progress.vue +156 -0
  60. package/components/muzhiyu/mu-pull-refresh/mu-pull-refresh.vue +54 -0
  61. package/components/muzhiyu/mu-qrcode/mu-qrcode.vue +340 -0
  62. package/components/muzhiyu/mu-qrcode/qrcode.js +2237 -0
  63. package/components/muzhiyu/mu-radio/mu-radio.vue +125 -0
  64. package/components/muzhiyu/mu-radio-group/mu-radio-group.vue +37 -0
  65. package/components/muzhiyu/mu-rate/mu-rate.vue +103 -0
  66. package/components/muzhiyu/mu-search/mu-search.vue +320 -0
  67. package/components/muzhiyu/mu-section/mu-section.vue +206 -0
  68. package/components/muzhiyu/mu-select/mu-select.vue +440 -0
  69. package/components/muzhiyu/mu-share-sheet/mu-share-sheet.vue +269 -0
  70. package/components/muzhiyu/mu-sidebar/mu-sidebar.vue +304 -0
  71. package/components/muzhiyu/mu-signature/mu-signature.vue +449 -0
  72. package/components/muzhiyu/mu-skeleton/mu-skeleton.vue +225 -0
  73. package/components/muzhiyu/mu-slider/mu-slider.vue +228 -0
  74. package/components/muzhiyu/mu-space/mu-space.vue +84 -0
  75. package/components/muzhiyu/mu-steps/mu-steps.vue +311 -0
  76. package/components/muzhiyu/mu-sticky/mu-sticky.vue +57 -0
  77. package/components/muzhiyu/mu-swipe-cell/mu-swipe-cell.vue +281 -0
  78. package/components/muzhiyu/mu-swiper/mu-swiper.vue +107 -0
  79. package/components/muzhiyu/mu-switch/mu-switch.vue +99 -0
  80. package/components/muzhiyu/mu-tabbar/mu-tabbar.vue +382 -0
  81. package/components/muzhiyu/mu-table/mu-table.vue +420 -0
  82. package/components/muzhiyu/mu-tabs/mu-tabs.vue +339 -0
  83. package/components/muzhiyu/mu-tag/mu-tag.vue +65 -0
  84. package/components/muzhiyu/mu-text-ellipsis/mu-text-ellipsis.vue +54 -0
  85. package/components/muzhiyu/mu-timeline/mu-timeline.vue +166 -0
  86. package/components/muzhiyu/mu-tips/mu-tips.vue +36 -0
  87. package/components/muzhiyu/mu-toast/mu-toast.vue +151 -0
  88. package/components/muzhiyu/mu-transfer/mu-transfer.vue +114 -0
  89. package/components/muzhiyu/mu-tree/mu-tree.vue +263 -0
  90. package/components/muzhiyu/mu-upload/mu-upload.vue +415 -0
  91. package/components/muzhiyu/mu-waterfall/mu-waterfall.vue +321 -0
  92. package/components/muzhiyu/mu-watermark/mu-watermark.vue +60 -0
  93. package/index.js +83 -0
  94. package/package.json +38 -0
  95. package/utils/index.js +600 -0
  96. package/utils/request.js +265 -0
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <view
3
+ class="mu-loading"
4
+ :class="{
5
+ 'mu-loading--rotate': type === 'spinner',
6
+ 'mu-loading--fullscreen': fullScreen
7
+ }"
8
+ :style="wrapStyle"
9
+ >
10
+ <!-- 旋转类型 -->
11
+ <view v-if="type === 'spinner'" class="mu-loading__spinner" :style="spinnerStyle">
12
+ <view v-for="i in 12" :key="i" class="mu-loading__dot" :style="{ transform: `rotate(${i * 30}deg)`, animationDelay: `${-1.1 + i * 0.1}s` }"></view>
13
+ </view>
14
+ <!-- 圆环类型 -->
15
+ <view v-else class="mu-loading__ring" :style="ringStyle"></view>
16
+ <text v-if="text" class="mu-loading__text" :style="textStyle">{{ text }}</text>
17
+ </view>
18
+ </template>
19
+
20
+ <script setup>
21
+ import { computed } from 'vue'
22
+
23
+ const props = defineProps({
24
+ type: { type: String, default: 'ring' }, // ring | spinner
25
+ size: { type: [Number, String], default: 20 },
26
+ color: { type: String, default: '#171717' },
27
+ text: { type: String, default: '' },
28
+ textColor: { type: String, default: '' },
29
+ marginRight: { type: [Number, String], default: 0 },
30
+ fullScreen: { type: Boolean, default: false }, // 是否全屏模态遮罩居中显示
31
+ bgColor: { type: String, default: 'rgba(255, 255, 255, 0.88)' }
32
+ })
33
+
34
+ function formatUnit(val, defaultVal = '36rpx') {
35
+ if (typeof val === 'number') return `${val}rpx`
36
+ if (typeof val === 'string') {
37
+ if (val.endsWith('rpx') || val.endsWith('px')) return val
38
+ return `${val}rpx`
39
+ }
40
+ return defaultVal
41
+ }
42
+
43
+ const sizeRpx = computed(() => formatUnit(props.size, '36rpx'))
44
+
45
+ const wrapStyle = computed(() => {
46
+ if (props.fullScreen) {
47
+ return {
48
+ backgroundColor: props.bgColor,
49
+ color: props.color
50
+ }
51
+ }
52
+ return {
53
+ marginRight: props.marginRight ? props.marginRight + 'rpx' : '0',
54
+ color: props.color
55
+ }
56
+ })
57
+
58
+ const spinnerStyle = computed(() => ({ width: sizeRpx.value, height: sizeRpx.value }))
59
+
60
+ const ringStyle = computed(() => ({
61
+ width: sizeRpx.value,
62
+ height: sizeRpx.value,
63
+ borderWidth: '4rpx',
64
+ borderColor: 'rgba(0, 0, 0, 0.1)',
65
+ borderTopColor: props.color
66
+ }))
67
+
68
+ const textStyle = computed(() => ({ color: props.textColor || props.color }))
69
+ </script>
70
+
71
+ <style lang="scss" scoped>
72
+ .mu-loading {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+
77
+ &--fullscreen {
78
+ position: fixed;
79
+ top: 0;
80
+ left: 0;
81
+ right: 0;
82
+ bottom: 0;
83
+ width: 100vw;
84
+ height: 100vh;
85
+ z-index: 99999;
86
+ display: flex;
87
+ flex-direction: column;
88
+ align-items: center;
89
+ justify-content: center;
90
+ backdrop-filter: blur(16px);
91
+ -webkit-backdrop-filter: blur(16px);
92
+
93
+ .mu-loading__text {
94
+ margin-left: 0;
95
+ margin-top: 24rpx;
96
+ font-size: 28rpx;
97
+ font-weight: 700;
98
+ }
99
+ }
100
+
101
+ &__ring {
102
+ border-radius: 50%;
103
+ border-style: solid;
104
+ animation: mu-spin 0.75s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
105
+ }
106
+ &__spinner { position: relative; }
107
+ &__dot {
108
+ position: absolute; width: 4rpx; height: 26%;
109
+ left: calc(50% - 2rpx); top: 0;
110
+ transform-origin: center bottom;
111
+ background: currentColor; border-radius: $mu-radius-full;
112
+ opacity: 0.2;
113
+ animation: mu-fade 1.2s linear infinite;
114
+ }
115
+ &__text {
116
+ margin-left: $mu-space-3;
117
+ font-size: $mu-font-sm;
118
+ font-weight: 600;
119
+ }
120
+ }
121
+
122
+ @keyframes mu-spin { to { transform: rotate(360deg); } }
123
+ @keyframes mu-fade { 0% { opacity: 1; } 100% { opacity: 0.15; } }
124
+ </style>
@@ -0,0 +1,529 @@
1
+ <template>
2
+ <view
3
+ v-if="visible"
4
+ class="mu-loading-page"
5
+ :class="[
6
+ `mu-loading-page--${activeLayout}`,
7
+ {
8
+ 'mu-loading-page--blur': blur,
9
+ 'mu-loading-page--fade-out': isFadingOut
10
+ }
11
+ ]"
12
+ :style="pageStyle"
13
+ @tap="handleTap"
14
+ @touchmove.stop.prevent
15
+ >
16
+ <!-- 悬浮岛/微卡片核心容器 -->
17
+ <view class="mu-loading-page__card" :style="cardStyle">
18
+
19
+ <!-- ===== 模式 0: 自定义图片/GIF/插槽模式 ===== -->
20
+ <view v-if="activeType === 'custom' || ($slots.image || (activeImage && activeType === 'custom'))" class="mu-loading-page__custom-box">
21
+ <slot name="image">
22
+ <image
23
+ v-if="activeImage"
24
+ class="mu-loading-page__custom-img"
25
+ :class="[`mu-loading-page__custom-img--${activeImageAnimation}`]"
26
+ :src="activeImage"
27
+ :mode="imageMode"
28
+ :style="customImageStyle"
29
+ />
30
+ </slot>
31
+ </view>
32
+
33
+ <!-- ===== 模式 1: Quantum Logo 呼吸脉冲 ===== -->
34
+ <view v-else-if="activeType === 'logo'" class="mu-loading-page__anim-box">
35
+ <view class="mu-loading-page__logo-ripple" :style="{ borderColor: activeColor }"></view>
36
+ <view class="mu-loading-page__logo-inner">
37
+ <image v-if="activeImage" class="mu-loading-page__logo-img" :src="activeImage" mode="aspectFit" />
38
+ <mu-icon v-else :name="icon || 'loading'" :size="32" :color="activeColor" class="mu-spin-pulse" />
39
+ </view>
40
+ </view>
41
+
42
+ <!-- ===== 模式 2: Bento 冰晶方块连波 ===== -->
43
+ <view v-else-if="activeType === 'bento'" class="mu-loading-page__bento-box">
44
+ <view
45
+ v-for="i in 4"
46
+ :key="i"
47
+ class="mu-loading-page__bento-block"
48
+ :style="{ backgroundColor: activeColor, animationDelay: `${(i - 1) * 0.15}s` }"
49
+ ></view>
50
+ </view>
51
+
52
+ <!-- ===== 模式 3: Orbit 双轨双色流光 ===== -->
53
+ <view v-else-if="activeType === 'orbit'" class="mu-loading-page__orbit-box" :style="{ width: `${sizeNumber}px`, height: `${sizeNumber}px` }">
54
+ <view class="mu-loading-page__orbit-ring mu-loading-page__orbit-ring--1" :style="{ borderTopColor: activeColor, borderRightColor: activeColor }"></view>
55
+ <view class="mu-loading-page__orbit-ring mu-loading-page__orbit-ring--2" :style="{ borderBottomColor: activeColor }"></view>
56
+ </view>
57
+
58
+ <!-- ===== 模式 4: Wave 5柱声波律动 ===== -->
59
+ <view v-else-if="activeType === 'wave'" class="mu-loading-page__wave-box">
60
+ <view
61
+ v-for="i in 5"
62
+ :key="i"
63
+ class="mu-loading-page__wave-bar"
64
+ :style="{ backgroundColor: activeColor, animationDelay: `${(i - 1) * 0.12}s` }"
65
+ ></view>
66
+ </view>
67
+
68
+ <!-- ===== 模式 5: Cube 3D 翻转方块 ===== -->
69
+ <view v-else-if="activeType === 'cube'" class="mu-loading-page__cube-box">
70
+ <view class="mu-loading-page__cube-inner" :style="{ backgroundColor: activeColor }"></view>
71
+ </view>
72
+
73
+ <!-- ===== 模式 6: Spinner / Ring 经典轻量微旋 ===== -->
74
+ <view v-else class="mu-loading-page__spinner-box">
75
+ <view class="mu-loading-page__ring" :style="ringStyle"></view>
76
+ </view>
77
+
78
+ <!-- 主文案 -->
79
+ <text v-if="activeText" class="mu-loading-page__text" :style="{ color: textColor || activeColor }">
80
+ {{ activeText }}
81
+ </text>
82
+
83
+ <!-- 副文案 -->
84
+ <text v-if="activeSubText" class="mu-loading-page__subtext">
85
+ {{ activeSubText }}
86
+ </text>
87
+
88
+ <!-- 允许用户传入提示/取消按钮槽 -->
89
+ <slot></slot>
90
+ </view>
91
+ </view>
92
+ </template>
93
+
94
+ <script setup>
95
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
96
+
97
+ const props = defineProps({
98
+ // 控制显示/隐藏 (支持 v-model:show)
99
+ show: { type: Boolean, default: false },
100
+ loading: { type: Boolean, default: false },
101
+
102
+ // 布局风格: 'card' (悬浮冰晶岛) | 'pure' (纯净透屏)
103
+ layout: { type: String, default: 'card' },
104
+
105
+ // 加载页形态: logo | bento | orbit | wave | cube | spinner | ring | custom
106
+ type: { type: String, default: 'logo' },
107
+
108
+ // 提示文本
109
+ text: { type: String, default: '页面加载中...' },
110
+ subText: { type: String, default: '' },
111
+
112
+ // 图标 / Logo 图片
113
+ image: { type: String, default: '' },
114
+ imageSize: { type: [Number, String], default: 80 },
115
+ imageMode: { type: String, default: 'aspectFit' },
116
+ imageAnimation: { type: String, default: 'pulse' }, // 'pulse' | 'spin' | 'bounce' | 'float' | 'none'
117
+ icon: { type: String, default: '' },
118
+
119
+ // 尺寸 (px)
120
+ size: { type: [Number, String], default: 48 },
121
+
122
+ // 颜色体系
123
+ color: { type: String, default: '#171717' },
124
+ textColor: { type: String, default: '#0F172A' },
125
+ bgColor: { type: String, default: 'rgba(15, 23, 42, 0.45)' },
126
+
127
+ // 毛玻璃特效
128
+ blur: { type: Boolean, default: true },
129
+
130
+ // 层级 Z-Index
131
+ zIndex: { type: [Number, String], default: 99999 },
132
+
133
+ // 允许点击页面遮罩关闭
134
+ clickClose: { type: Boolean, default: false }
135
+ })
136
+
137
+ const emit = defineEmits(['update:show', 'click', 'close'])
138
+
139
+ const visible = ref(false)
140
+ const isFadingOut = ref(false)
141
+ let animTimer = null
142
+
143
+ // JS 动态覆盖属性
144
+ const overrideType = ref('')
145
+ const overrideLayout = ref('')
146
+ const overrideText = ref('')
147
+ const overrideSubText = ref('')
148
+ const overrideImage = ref('')
149
+ const overrideImageAnimation = ref('')
150
+ const overrideColor = ref('')
151
+
152
+ const activeType = computed(() => overrideType.value || props.type)
153
+ const activeLayout = computed(() => overrideLayout.value || props.layout)
154
+ const activeText = computed(() => overrideText.value || props.text)
155
+ const activeSubText = computed(() => overrideSubText.value || props.subText)
156
+ const activeImage = computed(() => overrideImage.value || props.image)
157
+ const activeImageAnimation = computed(() => overrideImageAnimation.value || props.imageAnimation)
158
+ const activeColor = computed(() => overrideColor.value || props.color)
159
+
160
+ const isShow = computed(() => props.show || props.loading)
161
+
162
+ const sizeNumber = computed(() => {
163
+ const n = Number(props.size)
164
+ return isNaN(n) ? 48 : n
165
+ })
166
+
167
+ const customImageStyle = computed(() => {
168
+ const sz = Number(props.imageSize) || 80
169
+ return {
170
+ width: `${sz}px`,
171
+ height: `${sz}px`
172
+ }
173
+ })
174
+
175
+ const pageStyle = computed(() => ({
176
+ backgroundColor: activeLayout.value === 'pure' ? (props.bgColor || 'rgba(255, 255, 255, 0.95)') : props.bgColor,
177
+ zIndex: props.zIndex
178
+ }))
179
+
180
+ const cardStyle = computed(() => {
181
+ if (activeLayout.value === 'pure') {
182
+ return {
183
+ background: 'transparent',
184
+ boxShadow: 'none',
185
+ border: 'none',
186
+ padding: '0'
187
+ }
188
+ }
189
+ return {
190
+ background: 'rgba(255, 255, 255, 0.92)',
191
+ backdropFilter: 'blur(20px)',
192
+ boxShadow: '0 20rpx 60rpx -10rpx rgba(15, 23, 42, 0.15)',
193
+ borderRadius: '36rpx',
194
+ border: '1rpx solid rgba(255, 255, 255, 0.9)'
195
+ }
196
+ })
197
+
198
+ const ringStyle = computed(() => ({
199
+ width: `${sizeNumber.value}px`,
200
+ height: `${sizeNumber.value}px`,
201
+ borderWidth: '5rpx',
202
+ borderColor: 'rgba(0, 0, 0, 0.08)',
203
+ borderTopColor: activeColor.value
204
+ }))
205
+
206
+ watch(isShow, (val) => {
207
+ if (animTimer) {
208
+ clearTimeout(animTimer)
209
+ animTimer = null
210
+ }
211
+ if (val) {
212
+ open()
213
+ } else {
214
+ close()
215
+ }
216
+ }, { immediate: true })
217
+
218
+ function open(opts) {
219
+ if (opts && typeof opts === 'object') {
220
+ overrideType.value = opts.type || ''
221
+ overrideLayout.value = opts.layout || ''
222
+ overrideText.value = opts.text || ''
223
+ overrideSubText.value = opts.subText || ''
224
+ overrideImage.value = opts.image || ''
225
+ overrideImageAnimation.value = opts.imageAnimation || ''
226
+ overrideColor.value = opts.color || ''
227
+ }
228
+ visible.value = true
229
+ isFadingOut.value = false
230
+ }
231
+
232
+ function close() {
233
+ isFadingOut.value = true
234
+ animTimer = setTimeout(() => {
235
+ visible.value = false
236
+ isFadingOut.value = false
237
+ overrideType.value = ''
238
+ overrideLayout.value = ''
239
+ overrideText.value = ''
240
+ overrideSubText.value = ''
241
+ overrideImage.value = ''
242
+ overrideImageAnimation.value = ''
243
+ overrideColor.value = ''
244
+ emit('update:show', false)
245
+ emit('close')
246
+ }, 240)
247
+ }
248
+
249
+ // 注册全局 JS 事件总线监听器
250
+ onMounted(() => {
251
+ uni.$on('mu-loading-page-show', handleGlobalShow)
252
+ uni.$on('mu-loading-page-hide', close)
253
+ uni.$on('mu-loading-page-close', close)
254
+ })
255
+
256
+ onUnmounted(() => {
257
+ if (animTimer) clearTimeout(animTimer)
258
+ uni.$off('mu-loading-page-show', handleGlobalShow)
259
+ uni.$off('mu-loading-page-hide', close)
260
+ uni.$off('mu-loading-page-close', close)
261
+ })
262
+
263
+ function handleGlobalShow(opts) {
264
+ open(opts)
265
+ }
266
+
267
+ function handleTap(e) {
268
+ emit('click', e)
269
+ if (props.clickClose) {
270
+ close()
271
+ }
272
+ }
273
+
274
+ defineExpose({
275
+ open,
276
+ show: open,
277
+ close,
278
+ hide: close
279
+ })
280
+ </script>
281
+
282
+ <style lang="scss" scoped>
283
+ @import "@/uni.scss";
284
+
285
+ .mu-loading-page {
286
+ position: fixed;
287
+ top: 0;
288
+ left: 0;
289
+ right: 0;
290
+ bottom: 0;
291
+ width: 100vw;
292
+ height: 100vh;
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1);
297
+ will-change: opacity;
298
+ box-sizing: border-box;
299
+
300
+ &--blur {
301
+ /* #ifndef APP-NVUE */
302
+ backdrop-filter: blur(16px);
303
+ -webkit-backdrop-filter: blur(16px);
304
+ /* #endif */
305
+ }
306
+
307
+ &--fade-out {
308
+ opacity: 0;
309
+ pointer-events: none;
310
+ }
311
+
312
+ &__card {
313
+ display: flex;
314
+ flex-direction: column;
315
+ align-items: center;
316
+ justify-content: center;
317
+ padding: 56rpx 64rpx;
318
+ min-width: 280rpx;
319
+ max-width: 80%;
320
+ box-sizing: border-box;
321
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
322
+ }
323
+
324
+ /* ===== 模式 1: Logo 呼吸脉冲 ===== */
325
+ &__anim-box {
326
+ position: relative;
327
+ width: 110rpx;
328
+ height: 110rpx;
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ }
333
+
334
+ &__logo-ripple {
335
+ position: absolute;
336
+ inset: -10rpx;
337
+ border-radius: 50%;
338
+ border: 4rpx solid currentColor;
339
+ opacity: 0.3;
340
+ animation: mu-ripple 1.8s $mu-ease infinite;
341
+ }
342
+
343
+ &__logo-inner {
344
+ width: 90rpx;
345
+ height: 90rpx;
346
+ border-radius: 26rpx;
347
+ background: #FFFFFF;
348
+ box-shadow: 0 10rpx 28rpx rgba(0, 0, 0, 0.08);
349
+ display: flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+ animation: mu-pulse 1.8s $mu-ease infinite;
353
+ }
354
+
355
+ &__logo-img {
356
+ width: 65%;
357
+ height: 65%;
358
+ }
359
+
360
+ /* ===== 模式 2: Bento 冰晶 ===== */
361
+ &__bento-box {
362
+ display: grid;
363
+ grid-template-columns: repeat(2, 1fr);
364
+ gap: 12rpx;
365
+ width: 72rpx;
366
+ height: 72rpx;
367
+ }
368
+
369
+ &__bento-block {
370
+ width: 100%;
371
+ height: 100%;
372
+ border-radius: 10rpx;
373
+ animation: mu-bento-scale 1.2s ease-in-out infinite;
374
+ }
375
+
376
+ /* ===== 模式 3: Orbit 行星轨 ===== */
377
+ &__orbit-box {
378
+ position: relative;
379
+ display: flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ }
383
+
384
+ &__orbit-ring {
385
+ position: absolute;
386
+ inset: 0;
387
+ border-radius: 50%;
388
+ border: 4rpx solid transparent;
389
+
390
+ &--1 {
391
+ animation: mu-spin 1s linear infinite;
392
+ }
393
+
394
+ &--2 {
395
+ inset: 10rpx;
396
+ animation: mu-spin-reverse 0.75s linear infinite;
397
+ }
398
+ }
399
+
400
+ /* ===== 模式 4: Wave 5柱声波 ===== */
401
+ &__wave-box {
402
+ display: flex;
403
+ align-items: center;
404
+ gap: 10rpx;
405
+ height: 52rpx;
406
+ }
407
+
408
+ &__wave-bar {
409
+ width: 8rpx;
410
+ height: 100%;
411
+ border-radius: 999rpx;
412
+ animation: mu-wave 1s ease-in-out infinite;
413
+ }
414
+
415
+ /* ===== 模式 5: 3D 方块 ===== */
416
+ &__cube-box {
417
+ width: 60rpx;
418
+ height: 60rpx;
419
+ perspective: 160rpx;
420
+ }
421
+
422
+ &__cube-inner {
423
+ width: 100%;
424
+ height: 100%;
425
+ border-radius: 12rpx;
426
+ animation: mu-cube-flip 1.4s ease-in-out infinite;
427
+ }
428
+
429
+ /* ===== 模式 0: 自定义图片 ===== */
430
+ &__custom-box {
431
+ display: flex;
432
+ align-items: center;
433
+ justify-content: center;
434
+ }
435
+
436
+ &__custom-img {
437
+ display: block;
438
+ object-fit: contain;
439
+
440
+ &--pulse {
441
+ animation: mu-pulse 1.8s $mu-ease infinite;
442
+ }
443
+
444
+ &--spin {
445
+ animation: mu-spin 1.2s linear infinite;
446
+ }
447
+
448
+ &--bounce {
449
+ animation: mu-bounce 1.2s ease-in-out infinite;
450
+ }
451
+
452
+ &--float {
453
+ animation: mu-float 2s ease-in-out infinite;
454
+ }
455
+ }
456
+
457
+ /* ===== 模式 6: Ring 环形 ===== */
458
+ &__ring {
459
+ border-radius: 50%;
460
+ border-style: solid;
461
+ animation: mu-spin 0.75s linear infinite;
462
+ }
463
+
464
+ /* ===== 文本说明 ===== */
465
+ &__text {
466
+ font-size: 28rpx;
467
+ font-weight: 700;
468
+ margin-top: 32rpx;
469
+ letter-spacing: 1rpx;
470
+ text-align: center;
471
+ }
472
+
473
+ &__subtext {
474
+ font-size: 22rpx;
475
+ color: #64748B;
476
+ margin-top: 10rpx;
477
+ text-align: center;
478
+ font-weight: 500;
479
+ }
480
+ }
481
+
482
+ .mu-spin-pulse {
483
+ animation: mu-spin 2s linear infinite;
484
+ }
485
+
486
+ @keyframes mu-spin {
487
+ to { transform: rotate(360deg); }
488
+ }
489
+
490
+ @keyframes mu-spin-reverse {
491
+ to { transform: rotate(-360deg); }
492
+ }
493
+
494
+ @keyframes mu-pulse {
495
+ 0%, 100% { transform: scale(1); }
496
+ 50% { transform: scale(1.08); }
497
+ }
498
+
499
+ @keyframes mu-bounce {
500
+ 0%, 100% { transform: translateY(0); }
501
+ 50% { transform: translateY(-16rpx); }
502
+ }
503
+
504
+ @keyframes mu-float {
505
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
506
+ 50% { transform: translateY(-12rpx) rotate(3deg); }
507
+ }
508
+
509
+ @keyframes mu-ripple {
510
+ 0% { transform: scale(0.8); opacity: 0.8; }
511
+ 100% { transform: scale(1.4); opacity: 0; }
512
+ }
513
+
514
+ @keyframes mu-bento-scale {
515
+ 0%, 100% { transform: scale(0.5); opacity: 0.3; }
516
+ 50% { transform: scale(1.1); opacity: 1; }
517
+ }
518
+
519
+ @keyframes mu-wave {
520
+ 0%, 100% { transform: scaleY(0.3); }
521
+ 50% { transform: scaleY(1); }
522
+ }
523
+
524
+ @keyframes mu-cube-flip {
525
+ 0% { transform: rotateX(0deg) rotateY(0deg); }
526
+ 50% { transform: rotateX(180deg) rotateY(0deg); }
527
+ 100% { transform: rotateX(180deg) rotateY(180deg); }
528
+ }
529
+ </style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <view class="mu-loadmore">
3
+ <mu-loading v-if="status === 'loading'" size="12" color="#8C8C8C" :margin-right="8" />
4
+ <text class="mu-loadmore__text">{{ statusText }}</text>
5
+ </view>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { computed } from 'vue'
10
+ const props = defineProps({
11
+ status: { type: String, default: 'more' }, // more | loading | nomore
12
+ textMore: { type: String, default: '加载更多' },
13
+ textLoading: { type: String, default: '正在加载...' },
14
+ textNomore: { type: String, default: '没有更多了' }
15
+ })
16
+ const statusText = computed(() => ({
17
+ more: props.textMore, loading: props.textLoading, nomore: props.textNomore
18
+ }[props.status]))
19
+ </script>
20
+
21
+ <style lang="scss" scoped>
22
+ .mu-loadmore {
23
+ display: flex; align-items: center; justify-content: center; padding: $mu-space-6 0;
24
+ &__text { font-size: $mu-font-sm; color: $mu-text-tertiary; }
25
+ }
26
+ </style>