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,284 @@
1
+ <template>
2
+ <view
3
+ v-if="visible"
4
+ class="mu-notice-bar"
5
+ :class="[
6
+ `mu-notice-bar--${type}`,
7
+ round && 'mu-notice-bar--round'
8
+ ]"
9
+ :style="customStyle"
10
+ >
11
+ <!-- 前缀图标 -->
12
+ <view v-if="icon !== 'none'" class="mu-notice-bar__icon">
13
+ <slot name="icon">
14
+ <mu-icon :name="icon || defaultIcon" :size="iconSize" :color="iconColorComputed" />
15
+ </slot>
16
+ </view>
17
+
18
+ <!-- 消息内容 -->
19
+ <view class="mu-notice-bar__content" :class="{ 'mu-notice-bar__content--scroll': scrollable }">
20
+ <text
21
+ class="mu-notice-bar__text"
22
+ :class="{ 'mu-notice-bar__text--scroll': scrollable }"
23
+ :style="textStyle"
24
+ >
25
+ {{ text }}
26
+ </text>
27
+ </view>
28
+
29
+ <!-- 右侧关闭按钮或 Slot -->
30
+ <view v-if="closable || $slots.action" class="mu-notice-bar__action">
31
+ <slot name="action">
32
+ <view v-if="closable" class="mu-notice-bar__close mu-touchable-flat" @tap="close">
33
+ <mu-icon name="close" :size="14" :color="iconColorComputed" />
34
+ </view>
35
+ </slot>
36
+ </view>
37
+ </view>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { ref, computed } from 'vue'
42
+
43
+ const props = defineProps({
44
+ text: { type: String, default: '' },
45
+ icon: { type: String, default: '' },
46
+ iconSize: { type: [Number, String], default: 16 },
47
+ type: { type: String, default: 'warning' }, // warning | primary | success | danger | info
48
+ closable: { type: Boolean, default: false },
49
+ scrollable: { type: Boolean, default: true },
50
+ round: { type: Boolean, default: false },
51
+ speed: { type: Number, default: 50 }, // 滚动速度 (px/s)
52
+ color: { type: String, default: '' }, // 自定义文字颜色
53
+ background: { type: String, default: '' } // 自定义背景色
54
+ })
55
+
56
+ const emit = defineEmits(['close'])
57
+
58
+ const visible = ref(true)
59
+
60
+ // 根据 type 计算默认图标
61
+ const defaultIcon = computed(() => {
62
+ const map = {
63
+ warning: 'notification',
64
+ primary: 'sparkles',
65
+ success: 'check',
66
+ danger: 'info',
67
+ info: 'info'
68
+ }
69
+ return map[props.type] || 'notification'
70
+ })
71
+
72
+ // 图标及文字颜色控制
73
+ const iconColorComputed = computed(() => {
74
+ if (props.color) return props.color
75
+ const colorMap = {
76
+ warning: '#D97706',
77
+ primary: '#4F46E5',
78
+ success: '#059669',
79
+ danger: '#DC2626',
80
+ info: '#475569'
81
+ }
82
+ return colorMap[props.type] || '#D97706'
83
+ })
84
+
85
+ const duration = computed(() => {
86
+ if (!props.text) return 10
87
+ return Math.max(5, Math.round((props.text.length * 14) / Math.max(props.speed, 1) * 10) / 10)
88
+ })
89
+
90
+ const customStyle = computed(() => {
91
+ const s = {}
92
+ if (props.background) s.background = props.background
93
+ if (props.color) s.color = props.color
94
+ return s
95
+ })
96
+
97
+ const textStyle = computed(() => {
98
+ const s = {}
99
+ if (props.color) s.color = props.color
100
+ if (props.scrollable) s.animationDuration = duration.value + 's'
101
+ return s
102
+ })
103
+
104
+ function close() {
105
+ visible.value = false
106
+ emit('close')
107
+ }
108
+ </script>
109
+
110
+ <style lang="scss" scoped>
111
+ @import "@/uni.scss";
112
+
113
+ .mu-notice-bar {
114
+ display: flex;
115
+ align-items: center;
116
+ padding: 14rpx 20rpx;
117
+ border-radius: 12rpx;
118
+ box-sizing: border-box;
119
+ min-height: 64rpx;
120
+ line-height: 1.3;
121
+ position: relative;
122
+ overflow: hidden;
123
+
124
+ &--round {
125
+ border-radius: 999rpx;
126
+ }
127
+
128
+ /* 主题变体 */
129
+ &--warning {
130
+ background: #FEF3C7;
131
+ border: 1rpx solid rgba(245, 158, 11, 0.3);
132
+ .mu-notice-bar__text { color: #D97706; }
133
+ }
134
+
135
+ &--primary {
136
+ background: #EEF2FF;
137
+ border: 1rpx solid rgba(99, 102, 241, 0.3);
138
+ .mu-notice-bar__text { color: #4F46E5; }
139
+ }
140
+
141
+ &--success {
142
+ background: #ECFDF5;
143
+ border: 1rpx solid rgba(16, 185, 129, 0.3);
144
+ .mu-notice-bar__text { color: #059669; }
145
+ }
146
+
147
+ &--danger {
148
+ background: #FEF2F2;
149
+ border: 1rpx solid rgba(244, 63, 94, 0.3);
150
+ .mu-notice-bar__text { color: #DC2626; }
151
+ }
152
+
153
+ &--info {
154
+ background: #F1F5F9;
155
+ border: 1rpx solid rgba(148, 163, 184, 0.3);
156
+ .mu-notice-bar__text { color: #475569; }
157
+ }
158
+
159
+ &__icon {
160
+ margin-right: 14rpx;
161
+ flex-shrink: 0;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ }
166
+
167
+ &__content {
168
+ flex: 1;
169
+ overflow: hidden;
170
+ display: flex;
171
+ align-items: center;
172
+
173
+ &--scroll {
174
+ mask-image: linear-gradient(to right, transparent 0, #000 4%, #000 96%, transparent 100%);
175
+ }
176
+ }
177
+
178
+ &__text {
179
+ font-size: 24rpx;
180
+ font-weight: 600;
181
+
182
+ &--scroll {
183
+ display: inline-block;
184
+ white-space: nowrap;
185
+ padding-left: 100%;
186
+ animation: mu-notice-scroll linear infinite;
187
+ }
188
+ }
189
+
190
+ &__action {
191
+ margin-left: 14rpx;
192
+ flex-shrink: 0;
193
+ display: flex;
194
+ align-items: center;
195
+ }
196
+
197
+ &__close {
198
+ width: 36rpx;
199
+ height: 36rpx;
200
+ border-radius: 50%;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ background: rgba(0, 0, 0, 0.04);
205
+
206
+ &:active {
207
+ opacity: 0.7;
208
+ }
209
+ }
210
+ }
211
+
212
+ @keyframes mu-notice-scroll {
213
+ 0% { transform: translateX(0); }
214
+ 100% { transform: translateX(-100%); }
215
+ }
216
+
217
+ .mu-dark, page.mu-dark {
218
+
219
+ .mu-notice-bar {
220
+ &--warning {
221
+ background: rgba(245, 158, 11, 0.15);
222
+ border-color: rgba(245, 158, 11, 0.3);
223
+ .mu-notice-bar__text { color: #FBBF24; }
224
+ }
225
+ &--primary {
226
+ background: rgba(99, 102, 241, 0.15);
227
+ border-color: rgba(99, 102, 241, 0.3);
228
+ .mu-notice-bar__text { color: #FFFFFF; }
229
+ }
230
+ &--success {
231
+ background: rgba(16, 185, 129, 0.15);
232
+ border-color: rgba(16, 185, 129, 0.3);
233
+ .mu-notice-bar__text { color: #34D399; }
234
+ }
235
+ &--danger {
236
+ background: rgba(244, 63, 94, 0.15);
237
+ border-color: rgba(244, 63, 94, 0.3);
238
+ .mu-notice-bar__text { color: #F87171; }
239
+ }
240
+ &--info {
241
+ background: rgba(148, 163, 184, 0.15);
242
+ border-color: rgba(148, 163, 184, 0.3);
243
+ .mu-notice-bar__text { color: #94A3B8; }
244
+ }
245
+ &__close {
246
+ background: rgba(255, 255, 255, 0.1);
247
+ }
248
+ }
249
+ }
250
+
251
+ @media (prefers-color-scheme: dark) {
252
+
253
+ .mu-notice-bar {
254
+ &--warning {
255
+ background: rgba(245, 158, 11, 0.15);
256
+ border-color: rgba(245, 158, 11, 0.3);
257
+ .mu-notice-bar__text { color: #FBBF24; }
258
+ }
259
+ &--primary {
260
+ background: rgba(99, 102, 241, 0.15);
261
+ border-color: rgba(99, 102, 241, 0.3);
262
+ .mu-notice-bar__text { color: #FFFFFF; }
263
+ }
264
+ &--success {
265
+ background: rgba(16, 185, 129, 0.15);
266
+ border-color: rgba(16, 185, 129, 0.3);
267
+ .mu-notice-bar__text { color: #34D399; }
268
+ }
269
+ &--danger {
270
+ background: rgba(244, 63, 94, 0.15);
271
+ border-color: rgba(244, 63, 94, 0.3);
272
+ .mu-notice-bar__text { color: #F87171; }
273
+ }
274
+ &--info {
275
+ background: rgba(148, 163, 184, 0.15);
276
+ border-color: rgba(148, 163, 184, 0.3);
277
+ .mu-notice-bar__text { color: #94A3B8; }
278
+ }
279
+ &__close {
280
+ background: rgba(255, 255, 255, 0.1);
281
+ }
282
+ }
283
+ }
284
+ </style>
@@ -0,0 +1,202 @@
1
+ <template>
2
+ <view
3
+ v-if="visible"
4
+ class="mu-notify"
5
+ :class="['mu-notify--' + currentType, 'mu-notify--' + currentPosition]"
6
+ :style="containerStyle"
7
+ >
8
+ <view class="mu-notify__content">
9
+ <view class="mu-notify__icon" v-if="hasIcon">
10
+ <mu-icon v-if="currentType === 'success'" name="check" :size="18" color="#ffffff" />
11
+ <mu-icon v-else-if="currentType === 'error'" name="close" :size="18" color="#ffffff" />
12
+ <mu-icon v-else-if="currentType === 'warning'" name="warning" :size="18" color="#ffffff" />
13
+ <mu-icon v-else-if="currentType === 'primary'" name="info" :size="18" color="#ffffff" />
14
+ </view>
15
+ <text class="mu-notify__text">{{ currentMessage }}</text>
16
+ </view>
17
+ </view>
18
+ </template>
19
+
20
+ <script setup>
21
+ /**
22
+ * mu-notify 消息通知组件
23
+ * @description 顶部或底部的全局消息通知,支持组件声明式使用与纯 JS 命令式 (mu.notify) 调用
24
+ */
25
+ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
26
+
27
+ const props = defineProps({
28
+ show: { type: Boolean, default: false },
29
+ type: { type: String, default: 'primary' }, // primary | success | warning | error
30
+ message: { type: String, default: '' },
31
+ position: { type: String, default: 'top' }, // top | bottom
32
+ duration: { type: Number, default: 3000 },
33
+ color: { type: String, default: '#ffffff' },
34
+ background: { type: String, default: '' }
35
+ })
36
+
37
+ const emit = defineEmits(['update:show', 'close', 'open'])
38
+
39
+ const visible = ref(false)
40
+ const currentMessage = ref('')
41
+ const currentType = ref('primary')
42
+ const currentPosition = ref('top')
43
+ const currentDuration = ref(3000)
44
+ const currentBackground = ref('')
45
+ let timer = null
46
+
47
+ const hasIcon = computed(() => ['primary', 'success', 'warning', 'error'].includes(currentType.value))
48
+
49
+ const containerStyle = computed(() => {
50
+ const style = {}
51
+ if (currentBackground.value) style.background = currentBackground.value
52
+ return style
53
+ })
54
+
55
+ watch(() => props.show, (v) => {
56
+ if (v) {
57
+ show({
58
+ message: props.message,
59
+ type: props.type,
60
+ position: props.position,
61
+ duration: props.duration,
62
+ background: props.background
63
+ })
64
+ } else {
65
+ close()
66
+ }
67
+ })
68
+
69
+
70
+
71
+ function show(options) {
72
+ if (timer) {
73
+ clearTimeout(timer)
74
+ timer = null
75
+ }
76
+
77
+ // 快速连续触发时,先重置 visible 让 CSS 弹出动画重新播放,带来明显的连续响应体验
78
+ if (visible.value) {
79
+ visible.value = false
80
+ nextTick(() => {
81
+ applyAndShow(options)
82
+ })
83
+ } else {
84
+ applyAndShow(options)
85
+ }
86
+ }
87
+
88
+ function applyAndShow(options) {
89
+ if (typeof options === 'string') {
90
+ currentMessage.value = options
91
+ currentType.value = props.type || 'primary'
92
+ currentPosition.value = props.position || 'top'
93
+ currentDuration.value = props.duration || 3000
94
+ currentBackground.value = props.background || ''
95
+ } else if (options && typeof options === 'object') {
96
+ currentMessage.value = options.message || options.title || props.message || ''
97
+ currentType.value = options.type || props.type || 'primary'
98
+ currentPosition.value = options.position || props.position || 'top'
99
+ currentDuration.value = (options.duration !== undefined ? options.duration : (props.duration !== undefined ? props.duration : 3000))
100
+ currentBackground.value = options.background || props.background || ''
101
+ }
102
+
103
+ visible.value = true
104
+ emit('update:show', true)
105
+ emit('open')
106
+
107
+ if (currentDuration.value > 0) {
108
+ timer = setTimeout(() => { close() }, currentDuration.value)
109
+ }
110
+ }
111
+
112
+ function close() {
113
+ visible.value = false
114
+ emit('update:show', false)
115
+ emit('close')
116
+ }
117
+
118
+ // 注册全局 JS 事件总线
119
+ onMounted(() => {
120
+ uni.$on('mu-notify-show', handleGlobalNotifyShow)
121
+ uni.$on('mu-notify-close', close)
122
+ })
123
+
124
+ onUnmounted(() => {
125
+ uni.$off('mu-notify-show', handleGlobalNotifyShow)
126
+ uni.$off('mu-notify-close', close)
127
+ })
128
+
129
+ function handleGlobalNotifyShow(options) {
130
+ show(options)
131
+ }
132
+
133
+ defineExpose({ show, open: show, close })
134
+ </script>
135
+
136
+ <style lang="scss" scoped>
137
+ @import "@/uni.scss";
138
+
139
+ .mu-notify {
140
+ position: fixed;
141
+ left: 0;
142
+ right: 0;
143
+ z-index: $mu-z-toast;
144
+ padding: 24rpx 32rpx;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ box-shadow: 0 8rpx 32rpx rgba(15, 23, 42, 0.15);
149
+ animation: mu-notify-in 0.28s $mu-ease-spring;
150
+
151
+ &--top {
152
+ top: 0;
153
+ bottom: auto;
154
+ }
155
+
156
+ &--bottom {
157
+ top: auto;
158
+ bottom: 0;
159
+ padding-top: 20rpx;
160
+ padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
161
+ animation-name: mu-notify-in-bottom;
162
+ }
163
+
164
+ &--primary { background: $mu-primary; }
165
+ &--success { background: $mu-success; }
166
+ &--warning { background: $mu-warning; }
167
+ &--error { background: $mu-error; }
168
+
169
+ &__content {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ }
174
+
175
+ &__icon {
176
+ margin-right: 12rpx;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ flex-shrink: 0;
181
+ }
182
+
183
+ &__text {
184
+ color: #ffffff;
185
+ font-size: 28rpx;
186
+ font-weight: 600;
187
+ line-height: 1.4;
188
+ text-align: center;
189
+ word-break: break-all;
190
+ }
191
+ }
192
+
193
+ @keyframes mu-notify-in {
194
+ from { transform: translateY(-100%); opacity: 0; }
195
+ to { transform: translateY(0); opacity: 1; }
196
+ }
197
+
198
+ @keyframes mu-notify-in-bottom {
199
+ from { transform: translateY(100%); opacity: 0; }
200
+ to { transform: translateY(0); opacity: 1; }
201
+ }
202
+ </style>
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <view
3
+ v-if="visible"
4
+ class="mu-overlay"
5
+ :class="[animating && 'mu-overlay--show']"
6
+ :style="overlayStyle"
7
+ @tap="handleClick"
8
+ >
9
+ <slot></slot>
10
+ </view>
11
+ </template>
12
+
13
+ <script setup>
14
+ import { ref, computed, watch } from 'vue'
15
+
16
+ const props = defineProps({
17
+ show: { type: Boolean, default: false },
18
+ zIndex: { type: [Number, String], default: 999 },
19
+ color: { type: String, default: 'rgba(15, 23, 42, 0.55)' },
20
+ blur: { type: [Number, String], default: 12 }, // 毛玻璃模糊度 (px)
21
+ transparent: { type: Boolean, default: false }, // 完全透明遮罩
22
+ duration: { type: Number, default: 300 },
23
+ closeable: { type: Boolean, default: true }
24
+ })
25
+
26
+ const emit = defineEmits(['update:show', 'click', 'close'])
27
+
28
+ const visible = ref(false)
29
+ const animating = ref(false)
30
+
31
+ const overlayStyle = computed(() => {
32
+ const style = {
33
+ zIndex: props.zIndex,
34
+ transitionDuration: `${props.duration}ms`
35
+ }
36
+
37
+ if (props.transparent) {
38
+ style.background = 'transparent'
39
+ style.backdropFilter = 'none'
40
+ } else {
41
+ style.background = props.color
42
+ if (props.blur) {
43
+ const blurPx = typeof props.blur === 'number' ? `${props.blur}px` : props.blur
44
+ style.backdropFilter = `blur(${blurPx})`
45
+ }
46
+ }
47
+
48
+ return style
49
+ })
50
+
51
+ function handleClick(e) {
52
+ emit('click', e)
53
+ if (props.closeable) {
54
+ animating.value = false
55
+ emit('update:show', false)
56
+ setTimeout(() => {
57
+ visible.value = false
58
+ emit('close')
59
+ }, props.duration)
60
+ }
61
+ }
62
+
63
+ watch(() => props.show, (val) => {
64
+ if (val) {
65
+ visible.value = true
66
+ setTimeout(() => {
67
+ animating.value = true
68
+ }, 30)
69
+ } else {
70
+ animating.value = false
71
+ setTimeout(() => {
72
+ visible.value = false
73
+ }, props.duration)
74
+ }
75
+ }, { immediate: true })
76
+ </script>
77
+
78
+ <style lang="scss" scoped>
79
+ @import "@/uni.scss";
80
+
81
+ .mu-overlay {
82
+ position: fixed;
83
+ left: 0;
84
+ right: 0;
85
+ top: 0;
86
+ bottom: 0;
87
+ opacity: 0;
88
+ transition: opacity 0.3s ease;
89
+
90
+ &--show {
91
+ opacity: 1;
92
+ }
93
+ }
94
+ </style>