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,215 @@
1
+ <template>
2
+ <view class="mu-dropdown">
3
+ <!-- 触发器槽位 Trigger -->
4
+ <view class="mu-dropdown__trigger" @tap.stop="toggle">
5
+ <slot></slot>
6
+ </view>
7
+
8
+ <!-- 悬浮下拉面板 Panel -->
9
+ <view v-if="visible" class="mu-dropdown__mask" @tap="close"></view>
10
+ <view
11
+ v-if="visible"
12
+ class="mu-dropdown__panel"
13
+ :class="[`mu-dropdown--${placement}`]"
14
+ :style="panelStyle"
15
+ @tap.stop
16
+ >
17
+ <view
18
+ v-for="(item, index) in items"
19
+ :key="index"
20
+ class="mu-dropdown__item"
21
+ :class="[
22
+ item.disabled && 'mu-dropdown__item--disabled',
23
+ item.danger && 'mu-dropdown__item--danger',
24
+ item.divider && 'mu-dropdown__item--divider'
25
+ ]"
26
+ @tap="handleClick(index, item)"
27
+ >
28
+ <mu-icon v-if="item.icon" :name="item.icon" :size="15" :color="getItemIconColor(item)" style="margin-right: 12rpx;" />
29
+ <text class="mu-dropdown__text" :style="{ color: item.color || '' }">{{ item.text || item.label || item }}</text>
30
+ </view>
31
+ </view>
32
+ </view>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { ref, computed } from 'vue'
37
+
38
+ const props = defineProps({
39
+ items: { type: Array, default: () => [] },
40
+ placement: { type: String, default: 'bottom-left' }, // bottom-left | bottom-right | top-left | top-right
41
+ width: { type: String, default: '280rpx' }
42
+ })
43
+
44
+ const emit = defineEmits(['click', 'select', 'open', 'close'])
45
+
46
+ const visible = ref(false)
47
+
48
+ const panelStyle = computed(() => ({
49
+ width: props.width
50
+ }))
51
+
52
+ function getItemIconColor(item) {
53
+ if (item.disabled) return '#CBD5E1'
54
+ if (item.danger) return '#F43F5E'
55
+ if (item.iconColor) return item.iconColor
56
+ return '#64748B'
57
+ }
58
+
59
+ function toggle() {
60
+ visible.value = !visible.value
61
+ if (visible.value) emit('open')
62
+ else emit('close')
63
+ }
64
+
65
+ function handleClick(index, item) {
66
+ if (item.disabled) return
67
+ visible.value = false
68
+ emit('click', { index, item })
69
+ emit('select', item)
70
+ emit('close')
71
+ }
72
+
73
+ function close() {
74
+ visible.value = false
75
+ emit('close')
76
+ }
77
+
78
+ defineExpose({ toggle, close, visible })
79
+ </script>
80
+
81
+ <style lang="scss" scoped>
82
+ @import "@/uni.scss";
83
+
84
+ .mu-dropdown {
85
+ position: relative;
86
+ display: inline-flex;
87
+
88
+ &__trigger {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ cursor: pointer;
92
+ }
93
+
94
+ &__mask {
95
+ position: fixed;
96
+ inset: 0;
97
+ z-index: $mu-z-popup;
98
+ background: transparent;
99
+ }
100
+
101
+ &__panel {
102
+ position: absolute;
103
+ z-index: calc(#{$mu-z-popup} + 1);
104
+ background: #FFFFFF !important;
105
+ border-radius: 20rpx;
106
+ box-shadow: 0 12rpx 36rpx rgba(15, 23, 42, 0.14);
107
+ border: 1rpx solid rgba(226, 232, 240, 0.9);
108
+ overflow: hidden;
109
+ padding: 8rpx 0;
110
+ animation: mu-dropdown-fade 0.2s cubic-bezier(0.16, 1, 0.3, 1);
111
+ transform-origin: top left;
112
+ }
113
+
114
+ /* 位置扩展 */
115
+ &--bottom-left {
116
+ top: 100%;
117
+ left: 0;
118
+ margin-top: 12rpx;
119
+ }
120
+
121
+ &--bottom-right {
122
+ top: 100%;
123
+ right: 0;
124
+ margin-top: 12rpx;
125
+ transform-origin: top right;
126
+ }
127
+
128
+ &--top-left {
129
+ bottom: 100%;
130
+ left: 0;
131
+ margin-bottom: 12rpx;
132
+ transform-origin: bottom left;
133
+ }
134
+
135
+ &--top-right {
136
+ bottom: 100%;
137
+ right: 0;
138
+ margin-bottom: 12rpx;
139
+ transform-origin: bottom right;
140
+ }
141
+
142
+ &__item {
143
+ display: flex;
144
+ align-items: center;
145
+ padding: 20rpx 24rpx;
146
+ cursor: pointer;
147
+ transition: background 0.15s ease;
148
+
149
+ &:active {
150
+ background: rgba(99, 102, 241, 0.06);
151
+ }
152
+
153
+ &--disabled {
154
+ opacity: 0.45;
155
+ pointer-events: none;
156
+ }
157
+
158
+ &--danger .mu-dropdown__text {
159
+ color: #F43F5E !important;
160
+ }
161
+
162
+ &--divider {
163
+ border-bottom: 1rpx solid rgba(226, 232, 240, 0.7);
164
+ margin-bottom: 6rpx;
165
+ padding-bottom: 22rpx;
166
+ }
167
+ }
168
+
169
+ &__text {
170
+ font-size: 26rpx;
171
+ color: $mu-text-primary;
172
+ font-weight: 500;
173
+ white-space: nowrap;
174
+ }
175
+ }
176
+
177
+ @keyframes mu-dropdown-fade {
178
+ from {
179
+ opacity: 0;
180
+ transform: scale(0.92);
181
+ }
182
+ to {
183
+ opacity: 1;
184
+ transform: scale(1);
185
+ }
186
+ }
187
+
188
+ .mu-dark, page.mu-dark {
189
+
190
+ .mu-dropdown {
191
+ &__panel {
192
+ background: #161622 !important;
193
+ border-color: rgba(255, 255, 255, 0.12);
194
+ box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.65);
195
+ }
196
+ &__text { color: #F8FAFC !important; }
197
+ &__item--divider { border-color: rgba(255, 255, 255, 0.08); }
198
+ &__item:active { background: rgba(255, 255, 255, 0.08); }
199
+ }
200
+ }
201
+
202
+ @media (prefers-color-scheme: dark) {
203
+
204
+ .mu-dropdown {
205
+ &__panel {
206
+ background: #161622 !important;
207
+ border-color: rgba(255, 255, 255, 0.12);
208
+ box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.65);
209
+ }
210
+ &__text { color: #F8FAFC !important; }
211
+ &__item--divider { border-color: rgba(255, 255, 255, 0.08); }
212
+ &__item:active { background: rgba(255, 255, 255, 0.08); }
213
+ }
214
+ }
215
+ </style>
@@ -0,0 +1,243 @@
1
+ <template>
2
+ <view v-if="visible" class="mu-dropdown-top" :class="[panelShow && 'mu-dropdown-top--open']" :style="containerStyle">
3
+ <!-- 遮罩层 -->
4
+ <view class="mu-dropdown-top__mask" @tap="handleClose"></view>
5
+
6
+ <!-- 向下滑出的面板 Panel -->
7
+ <view class="mu-dropdown-top__panel" :class="[panelShow && 'mu-dropdown-top__panel--show']" @tap.stop>
8
+ <slot>
9
+ <!-- 默认选项列表 -->
10
+ <view
11
+ v-for="(item, index) in items"
12
+ :key="index"
13
+ class="mu-dropdown-top__item"
14
+ :class="[isSelected(item, index) && 'mu-dropdown-top__item--active']"
15
+ @tap="handleClick(index, item)"
16
+ >
17
+ <text class="mu-dropdown-top__text" :style="getTextStyle(item, index)">{{ getItemText(item) }}</text>
18
+ <mu-icon v-if="isSelected(item, index)" name="check" :size="16" :color="activeColor || '#171717'" />
19
+ </view>
20
+ </slot>
21
+ </view>
22
+ </view>
23
+ </template>
24
+
25
+ <script setup>
26
+ /**
27
+ * mu-dropdown-top 顶端下拉菜单
28
+ * @description 从页面顶端/导航栏下方平滑滑出向下的全宽下拉筛选/菜单面板
29
+ * @property {boolean} show - 是否显示面板
30
+ * @property {Array} items - 菜单列表 [{label, value}]
31
+ * @property {string|number} value - 当前选中的值
32
+ * @property {number|string} top - 偏离顶部的高度 rpx/px
33
+ * @property {string} activeColor - 选中态高亮色彩
34
+ * @event {Function} select - 选中筛选项时触发
35
+ */
36
+ import { ref, computed, watch } from "vue"
37
+
38
+ const props = defineProps({
39
+ show: { type: Boolean, default: false },
40
+ items: { type: Array, default: () => [] },
41
+ value: { type: [String, Number], default: "" },
42
+ top: { type: [Number, String], default: 0 },
43
+ activeColor: { type: String, default: "#171717" }
44
+ })
45
+
46
+ const emit = defineEmits(["click", "select", "close", "update:show", "update:value"])
47
+
48
+ const visible = ref(false)
49
+ const panelShow = ref(false)
50
+
51
+ const containerStyle = computed(() => {
52
+ if (!props.top || props.top === 0 || props.top === "0") return { top: 0 }
53
+ const topVal = typeof props.top === "number" ? `${props.top}rpx` : props.top.includes("px") || props.top.includes("rpx") ? props.top : `${props.top}rpx`
54
+ return { top: topVal }
55
+ })
56
+
57
+ function getItemText(item) {
58
+ if (typeof item === "object" && item !== null) {
59
+ return item.text || item.label || String(item)
60
+ }
61
+ return String(item)
62
+ }
63
+
64
+ function getItemValue(item, index) {
65
+ if (typeof item === "object" && item !== null) {
66
+ return (item.value !== undefined ? item.value : (item.text !== undefined ? item.text : index))
67
+ }
68
+ return item
69
+ }
70
+
71
+ function isSelected(item, index) {
72
+ const val = getItemValue(item, index)
73
+ return props.value === val
74
+ }
75
+
76
+ function getTextStyle(item, index) {
77
+ if (isSelected(item, index) && props.activeColor && props.activeColor !== "#171717") {
78
+ return { color: props.activeColor, fontWeight: "800" }
79
+ }
80
+ return {}
81
+ }
82
+
83
+ function handleClick(index, item) {
84
+ const val = getItemValue(item, index)
85
+ emit("click", { index, item, value: val })
86
+ emit("select", item)
87
+ emit("update:value", val)
88
+ handleClose()
89
+ }
90
+
91
+ function handleClose() {
92
+ panelShow.value = false
93
+ emit("update:show", false)
94
+ setTimeout(() => {
95
+ visible.value = false
96
+ emit("close")
97
+ }, 250)
98
+ }
99
+
100
+ watch(
101
+ () => props.show,
102
+ (val) => {
103
+ if (val) {
104
+ visible.value = true
105
+ setTimeout(() => {
106
+ panelShow.value = true
107
+ }, 30)
108
+ } else {
109
+ panelShow.value = false
110
+ setTimeout(() => {
111
+ visible.value = false
112
+ }, 250)
113
+ }
114
+ },
115
+ { immediate: true }
116
+ )
117
+ </script>
118
+
119
+ <style lang="scss" scoped>
120
+ @import "@/uni.scss";
121
+
122
+ .mu-dropdown-top {
123
+ position: fixed;
124
+ left: 0;
125
+ right: 0;
126
+ bottom: 0;
127
+ z-index: $mu-z-popup;
128
+
129
+ &__mask {
130
+ position: fixed;
131
+ inset: 0;
132
+ background: rgba(15, 23, 42, 0.45);
133
+ backdrop-filter: none !important;
134
+ opacity: 0;
135
+ transition: opacity 0.25s ease;
136
+ }
137
+
138
+ &--open &__mask {
139
+ opacity: 1;
140
+ }
141
+
142
+ &__panel {
143
+ position: fixed;
144
+ top: 0;
145
+ left: 0;
146
+ right: 0;
147
+ background: #FFFFFF !important;
148
+ border-radius: 0 0 32rpx 32rpx;
149
+ box-shadow: 0 16rpx 40rpx rgba(15, 23, 42, 0.12);
150
+ transform: translateY(-100%);
151
+ transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
152
+ overflow: hidden;
153
+ z-index: 2;
154
+
155
+ &--show {
156
+ transform: translateY(0);
157
+ }
158
+ }
159
+
160
+ &__item {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: space-between;
164
+ padding: 28rpx 36rpx;
165
+ border-bottom: 1rpx solid rgba(226, 232, 240, 0.7);
166
+ cursor: pointer;
167
+ transition: background 0.15s ease;
168
+
169
+ &:last-child {
170
+ border-bottom: none;
171
+ }
172
+
173
+ &:active {
174
+ background: rgba(99, 102, 241, 0.06);
175
+ }
176
+
177
+ &--active {
178
+ background: rgba(99, 102, 241, 0.06);
179
+
180
+ .mu-dropdown-top__text {
181
+ color: #171717;
182
+ font-weight: 800;
183
+ }
184
+ }
185
+ }
186
+
187
+ &__text {
188
+ font-size: 28rpx;
189
+ color: $mu-text-primary;
190
+ font-weight: 500;
191
+ }
192
+ }
193
+
194
+ .mu-dark, page.mu-dark {
195
+
196
+ .mu-dropdown-top {
197
+ &__mask {
198
+ background: rgba(0, 0, 0, 0.65);
199
+ }
200
+ &__panel {
201
+ background: #161622 !important;
202
+ box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.65);
203
+ }
204
+ &__item {
205
+ border-color: rgba(255, 255, 255, 0.08);
206
+ }
207
+ &__text {
208
+ color: #F8FAFC !important;
209
+ }
210
+ &__item--active {
211
+ background: rgba(255, 255, 255, 0.08);
212
+ .mu-dropdown-top__text {
213
+ color: #FFFFFF !important;
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ @media (prefers-color-scheme: dark) {
220
+
221
+ .mu-dropdown-top {
222
+ &__mask {
223
+ background: rgba(0, 0, 0, 0.65);
224
+ }
225
+ &__panel {
226
+ background: #161622 !important;
227
+ box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.65);
228
+ }
229
+ &__item {
230
+ border-color: rgba(255, 255, 255, 0.08);
231
+ }
232
+ &__text {
233
+ color: #F8FAFC !important;
234
+ }
235
+ &__item--active {
236
+ background: rgba(255, 255, 255, 0.08);
237
+ .mu-dropdown-top__text {
238
+ color: #FFFFFF !important;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ </style>
@@ -0,0 +1,217 @@
1
+ <template>
2
+ <view class="mu-empty" :style="{ padding }">
3
+ <!-- 1. 自定义媒体/动图插槽或图片模式 -->
4
+ <view v-if="$slots.image || realImage" class="mu-empty__image-wrap">
5
+ <slot name="image">
6
+ <image
7
+ class="mu-empty__image"
8
+ :src="realImage"
9
+ :style="imageStyle"
10
+ mode="aspectFit"
11
+ />
12
+ </slot>
13
+ </view>
14
+
15
+ <!-- 2. 极奢 3D 浮雕矢量 Icon 微章模式 -->
16
+ <view v-else class="mu-empty__badge-container">
17
+ <view class="mu-empty__badge" :style="badgeStyle">
18
+ <view class="mu-empty__halo" :style="{ background: iconColorComputed }"></view>
19
+ <mu-icon :name="iconName" :size="iconSize" :color="iconColorComputed" />
20
+ </view>
21
+ </view>
22
+
23
+ <!-- 3. 标题文案 -->
24
+ <text class="mu-empty__title">{{ displayTitle }}</text>
25
+
26
+ <!-- 4. 副标题/详细说明 -->
27
+ <text v-if="subText || description" class="mu-empty__desc">{{ subText || description }}</text>
28
+
29
+ <!-- 5. 底部行动按钮或按钮插槽 -->
30
+ <view v-if="$slots.default || buttonText" class="mu-empty__action">
31
+ <slot>
32
+ <mu-button
33
+ v-if="buttonText"
34
+ type="primary"
35
+ size="small"
36
+ round
37
+ icon="refresh"
38
+ @click="onButtonClick"
39
+ >
40
+ {{ buttonText }}
41
+ </mu-button>
42
+ </slot>
43
+ </view>
44
+ </view>
45
+ </template>
46
+
47
+ <script setup>
48
+ import { computed } from 'vue'
49
+
50
+ const props = defineProps({
51
+ // 场景预设: data(数据空) | search(搜索空) | cart(购物车空) | message(消息空) | network(无网络) | permission(无权限)
52
+ mode: { type: String, default: 'data' },
53
+ title: { type: String, default: '' },
54
+ description: { type: String, default: '' },
55
+ text: { type: String, default: '' }, // 兼容旧属性 text
56
+ subText: { type: String, default: '' }, // 兼容旧属性 subText
57
+ icon: { type: String, default: '' },
58
+ iconSize: { type: [Number, String], default: 44 },
59
+ iconColor: { type: String, default: '' },
60
+ image: { type: String, default: '' }, // 支持静态图 / GIF 动图 / 外部 URL
61
+ src: { type: String, default: '' }, // 兼容 src 别名
62
+ imageSize: { type: [Number, String], default: 160 },
63
+ buttonText: { type: String, default: '' },
64
+ padding: { type: String, default: '60rpx 0' }
65
+ })
66
+
67
+ const emit = defineEmits(['click-button'])
68
+
69
+ // 场景预设映射
70
+ const modeConfigs = {
71
+ data: { icon: 'inbox', title: '暂无相关数据', color: '#171717' },
72
+ search: { icon: 'search', title: '未搜索到相关结果', color: '#0EA5E9' },
73
+ cart: { icon: 'cart', title: '购物车是空的', color: '#F59E0B' },
74
+ message: { icon: 'message', title: '暂无新的消息通知', color: '#10B981' },
75
+ network: { icon: 'wifi', title: '网络连接已断开', color: '#F43F5E' },
76
+ permission: { icon: 'lock', title: '暂无访问或查看权限', color: '#8B5CF6' }
77
+ }
78
+
79
+ const currentConfig = computed(() => modeConfigs[props.mode] || modeConfigs.data)
80
+
81
+ const iconName = computed(() => props.icon || currentConfig.value.icon)
82
+
83
+ const iconColorComputed = computed(() => props.iconColor || currentConfig.value.color)
84
+
85
+ const realImage = computed(() => props.image || props.src)
86
+
87
+ const displayTitle = computed(() => {
88
+ if (props.title) return props.title
89
+ if (props.text) return props.text
90
+ return currentConfig.value.title
91
+ })
92
+
93
+ const imageStyle = computed(() => {
94
+ const sz = typeof props.imageSize === 'number' ? props.imageSize * 2 : props.imageSize
95
+ return {
96
+ width: sz + 'rpx',
97
+ height: sz + 'rpx'
98
+ }
99
+ })
100
+
101
+ const badgeStyle = computed(() => {
102
+ const c = iconColorComputed.value
103
+ return {
104
+ background: `linear-gradient(145deg, rgba(255,255,255,0.95), ${c}0D)`,
105
+ borderColor: `${c}30`,
106
+ boxShadow: `0 16rpx 36rpx -8rpx ${c}25`
107
+ }
108
+ })
109
+
110
+ function onButtonClick(e) {
111
+ emit('click-button', e)
112
+ }
113
+ </script>
114
+
115
+ <style lang="scss" scoped>
116
+ @import "@/uni.scss";
117
+
118
+ .mu-empty {
119
+ display: flex;
120
+ flex-direction: column;
121
+ align-items: center;
122
+ justify-content: center;
123
+ box-sizing: border-box;
124
+
125
+ &__image-wrap {
126
+ margin-bottom: 24rpx;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ }
131
+
132
+ &__image {
133
+ display: block;
134
+ max-width: 100%;
135
+ }
136
+
137
+ &__badge-container {
138
+ margin-bottom: 28rpx;
139
+ }
140
+
141
+ &__badge {
142
+ width: 140rpx;
143
+ height: 140rpx;
144
+ border-radius: 50%;
145
+ border: 1rpx solid rgba(226, 232, 240, 0.8);
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ position: relative;
150
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
151
+
152
+ &:hover {
153
+ transform: scale(1.05) translateY(-4rpx);
154
+ }
155
+ }
156
+
157
+ &__halo {
158
+ position: absolute;
159
+ width: 100%;
160
+ height: 100%;
161
+ border-radius: 50%;
162
+ filter: blur(16px);
163
+ opacity: 0.2;
164
+ z-index: -1;
165
+ }
166
+
167
+ &__title {
168
+ font-size: 28rpx;
169
+ font-weight: 700;
170
+ color: $mu-text-primary;
171
+ text-align: center;
172
+ line-height: 1.4;
173
+ }
174
+
175
+ &__desc {
176
+ font-size: 24rpx;
177
+ color: #94A3B8;
178
+ margin-top: 8rpx;
179
+ text-align: center;
180
+ line-height: 1.4;
181
+ max-width: 480rpx;
182
+ }
183
+
184
+ &__action {
185
+ margin-top: 32rpx;
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ }
190
+ }
191
+
192
+ .mu-dark, page.mu-dark {
193
+
194
+ .mu-empty {
195
+ &__badge {
196
+ background: rgba(255, 255, 255, 0.05) !important;
197
+ border-color: rgba(255, 255, 255, 0.12) !important;
198
+ box-shadow: 0 16rpx 36rpx rgba(0, 0, 0, 0.3) !important;
199
+ }
200
+ &__title { color: $mu-dark-text-primary; }
201
+ &__desc { color: $mu-dark-text-secondary; }
202
+ }
203
+ }
204
+
205
+ @media (prefers-color-scheme: dark) {
206
+
207
+ .mu-empty {
208
+ &__badge {
209
+ background: rgba(255, 255, 255, 0.05) !important;
210
+ border-color: rgba(255, 255, 255, 0.12) !important;
211
+ box-shadow: 0 16rpx 36rpx rgba(0, 0, 0, 0.3) !important;
212
+ }
213
+ &__title { color: $mu-dark-text-primary; }
214
+ &__desc { color: $mu-dark-text-secondary; }
215
+ }
216
+ }
217
+ </style>