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,415 @@
1
+ <template>
2
+ <view class="mu-upload" :class="{ 'mu-upload--disabled': disabled, 'mu-upload--avatar': avatar }">
3
+ <!-- 头像上传专用单图模式 -->
4
+ <template v-if="avatar">
5
+ <view class="mu-upload__avatar-wrap" :style="avatarStyle" @tap="handleChoose">
6
+ <image v-if="fileList.length > 0 && getFileUrl(fileList[0])" class="mu-upload__avatar-img" :src="getFileUrl(fileList[0])" mode="aspectFill" />
7
+ <view v-else class="mu-upload__avatar-placeholder">
8
+ <mu-icon name="camera" :size="28" color="#94A3B8" />
9
+ <text class="mu-upload__avatar-text">{{ addText || '上传头像' }}</text>
10
+ </view>
11
+ <view v-if="fileList.length > 0" class="mu-upload__avatar-edit">
12
+ <mu-icon name="camera" :size="12" color="#ffffff" />
13
+ </view>
14
+ </view>
15
+ </template>
16
+
17
+ <!-- 标准多图/文件网格列表模式 -->
18
+ <template v-else>
19
+ <view class="mu-upload__list">
20
+ <!-- 图片/文件已选节点网格 -->
21
+ <view v-for="(file, index) in fileList" :key="index" class="mu-upload__item" :style="itemStyle">
22
+ <image v-if="isImage(file)" class="mu-upload__img" :src="getFileUrl(file)" mode="aspectFill" @tap="handlePreview(index)" />
23
+ <view v-else class="mu-upload__file">
24
+ <mu-icon name="attachment" :size="20" color="#64748B" />
25
+ <text class="mu-upload__file-name">{{ getFileName(file) }}</text>
26
+ </view>
27
+
28
+ <!-- 右上角精致删除图标按钮 -->
29
+ <view v-if="!disabled" class="mu-upload__del mu-touchable-flat" @tap.stop="handleDelete(index)">
30
+ <mu-icon name="close" :size="10" color="#ffffff" />
31
+ </view>
32
+
33
+ <!-- 动态上传中 (Uploading 蒙层 & 百分比数字 & Loading 转圈) -->
34
+ <view v-if="getFileStatus(file) === 'uploading' || getFileStatus(file) === 'failed'" class="mu-upload__status-layer" :class="{ 'mu-upload__status-layer--failed': getFileStatus(file) === 'failed' }">
35
+ <template v-if="getFileStatus(file) === 'uploading'">
36
+ <mu-loading :size="18" color="#ffffff" />
37
+ <text class="mu-upload__status-text">{{ getFileMessage(file) || `${getFileProgress(file)}%` }}</text>
38
+ <view class="mu-upload__progress-track">
39
+ <view class="mu-upload__progress-bar" :style="{ width: getFileProgress(file) + '%' }"></view>
40
+ </view>
41
+ </template>
42
+ <template v-else-if="getFileStatus(file) === 'failed'">
43
+ <mu-icon name="warning" :size="20" color="#F43F5E" />
44
+ <text class="mu-upload__status-text mu-upload__status-text--failed">上传失败</text>
45
+ </template>
46
+ </view>
47
+ </view>
48
+
49
+ <!-- 增加照片卡片 Add Box -->
50
+ <view v-if="showAdd" class="mu-upload__add mu-touchable-flat" :style="itemStyle" @tap="handleChoose">
51
+ <slot name="add">
52
+ <view class="mu-upload__add-inner">
53
+ <mu-icon name="camera" :size="24" color="#94A3B8" />
54
+ <text v-if="addText" class="mu-upload__add-text">{{ addText }}</text>
55
+ </view>
56
+ </slot>
57
+ </view>
58
+ </view>
59
+ </template>
60
+ </view>
61
+ </template>
62
+
63
+ <script setup>
64
+ import { ref, computed, watch } from 'vue'
65
+
66
+ const props = defineProps({
67
+ modelValue: { type: [Array, String], default: () => [] },
68
+ value: { type: [Array, String], default: null },
69
+ max: { type: Number, default: 9 },
70
+ disabled: { type: Boolean, default: false },
71
+ avatar: { type: Boolean, default: false }, // 是否开启单图头像上传模式
72
+ addText: { type: String, default: '上传图片' },
73
+ size: { type: [Number, String], default: 150 },
74
+ accept: { type: String, default: 'image' },
75
+ sourceType: { type: Array, default: () => ['album', 'camera'] },
76
+ sizeLimit: { type: Number, default: 0 }
77
+ })
78
+
79
+ const emit = defineEmits(['change', 'delete', 'oversize', 'update:modelValue', 'update:value'])
80
+
81
+ function toArray(val) {
82
+ if (!val) return []
83
+ if (Array.isArray(val)) return [...val]
84
+ if (typeof val === 'string' && val.trim()) return [val]
85
+ return []
86
+ }
87
+
88
+ const fileList = ref(toArray((props.modelValue !== undefined ? props.modelValue : props.value)))
89
+
90
+ watch(() => props.modelValue, (v) => {
91
+ fileList.value = toArray(v)
92
+ })
93
+ watch(() => props.value, (v) => {
94
+ fileList.value = toArray(v)
95
+ })
96
+
97
+ const showAdd = computed(() => fileList.value.length < props.max && !props.disabled)
98
+
99
+ const itemStyle = computed(() => {
100
+ const s = Number(props.size)
101
+ return { width: s + 'rpx', height: s + 'rpx' }
102
+ })
103
+
104
+ const avatarStyle = computed(() => {
105
+ const s = Number(props.size) > 150 ? Number(props.size) : 160
106
+ return { width: s + 'rpx', height: s + 'rpx' }
107
+ })
108
+
109
+ function getFileUrl(file) {
110
+ if (typeof file === 'string') return file
111
+ return file.url || file.path || file.tempFilePath || ''
112
+ }
113
+
114
+ function getFileStatus(file) {
115
+ if (typeof file === 'object' && file !== null) {
116
+ if (file.status) return file.status
117
+ if (typeof file.progress === 'number' && file.progress < 100) return 'uploading'
118
+ }
119
+ return 'success'
120
+ }
121
+
122
+ function getFileProgress(file) {
123
+ if (typeof file === 'object' && file !== null && typeof file.progress === 'number') {
124
+ return Math.min(100, Math.max(0, file.progress))
125
+ }
126
+ return 0
127
+ }
128
+
129
+ function getFileMessage(file) {
130
+ if (typeof file === 'object' && file !== null) return file.message || ''
131
+ return ''
132
+ }
133
+
134
+ function isImage(file) {
135
+ if (props.accept === 'image') return true
136
+ const url = getFileUrl(file)
137
+ if (!url) return true
138
+ if (typeof file === 'object' && file !== null && file.type && file.type.startsWith('image/')) return true
139
+ return /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i.test(url) ||
140
+ url.startsWith('blob:') ||
141
+ url.startsWith('data:') ||
142
+ url.startsWith('wxfile:') ||
143
+ url.startsWith('file:') ||
144
+ url.startsWith('tmp:')
145
+ }
146
+
147
+ function getFileName(file) {
148
+ if (typeof file === 'object' && file !== null) return file.name || file.url || '文件'
149
+ return String(file)
150
+ }
151
+
152
+ function handleChoose() {
153
+ if (props.disabled) return
154
+ const count = props.avatar ? 1 : (props.max - fileList.value.length)
155
+ uni.chooseImage({
156
+ count,
157
+ sourceType: props.sourceType,
158
+ sizeType: ['compressed'],
159
+ success: (res) => {
160
+ const rawFiles = res.tempFilePaths || []
161
+ const tempFiles = rawFiles.map(p => typeof p === 'string' && p.startsWith('http://tmp/') ? p.replace('http://tmp/', 'wxfile://tmp/') : p)
162
+ const newList = props.avatar ? tempFiles : [...fileList.value, ...tempFiles]
163
+ fileList.value = newList
164
+ const emitVal = props.avatar ? (newList[0] || '') : newList
165
+ emit('update:modelValue', emitVal)
166
+ emit('update:value', emitVal)
167
+ emit('change', emitVal)
168
+ }
169
+ })
170
+ }
171
+
172
+ function handleDelete(index) {
173
+ const deletedItem = fileList.value[index]
174
+ fileList.value.splice(index, 1)
175
+ const newList = [...fileList.value]
176
+ const emitVal = props.avatar ? '' : newList
177
+ emit('update:modelValue', emitVal)
178
+ emit('update:value', emitVal)
179
+ emit('delete', { index, item: deletedItem })
180
+ emit('change', emitVal)
181
+ }
182
+
183
+ function handlePreview(index) {
184
+ const urls = fileList.value.map(f => getFileUrl(f)).filter(Boolean)
185
+ uni.previewImage({
186
+ current: index,
187
+ urls
188
+ })
189
+ }
190
+ </script>
191
+
192
+ <style lang="scss" scoped>
193
+ @import "@/uni.scss";
194
+
195
+ .mu-upload {
196
+ &--disabled { opacity: $mu-opacity-disabled; pointer-events: none; }
197
+
198
+ &__list {
199
+ display: flex;
200
+ flex-wrap: wrap;
201
+ gap: 20rpx;
202
+ }
203
+
204
+ &__item {
205
+ position: relative;
206
+ border-radius: 16rpx;
207
+ overflow: hidden;
208
+ background: rgba(241, 245, 249, 0.6);
209
+ border: 1rpx solid $mu-border;
210
+ box-sizing: border-box;
211
+ }
212
+
213
+ &__img {
214
+ width: 100%;
215
+ height: 100%;
216
+ display: block;
217
+ }
218
+
219
+ &__file {
220
+ width: 100%;
221
+ height: 100%;
222
+ display: flex;
223
+ flex-direction: column;
224
+ align-items: center;
225
+ justify-content: center;
226
+ padding: 12rpx;
227
+ box-sizing: border-box;
228
+
229
+ &-name {
230
+ font-size: 20rpx;
231
+ color: $mu-text-secondary;
232
+ margin-top: 8rpx;
233
+ text-align: center;
234
+ word-break: break-all;
235
+ }
236
+ }
237
+
238
+ &__del {
239
+ position: absolute;
240
+ top: 8rpx;
241
+ right: 8rpx;
242
+ width: 32rpx;
243
+ height: 32rpx;
244
+ border-radius: 50%;
245
+ background: rgba(15, 23, 42, 0.65);
246
+ backdrop-filter: blur(8px);
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ cursor: pointer;
251
+ z-index: 20;
252
+ }
253
+
254
+ &__status-layer {
255
+ position: absolute;
256
+ inset: 0;
257
+ background: rgba(15, 23, 42, 0.65);
258
+ backdrop-filter: blur(4px);
259
+ display: flex;
260
+ flex-direction: column;
261
+ align-items: center;
262
+ justify-content: center;
263
+ padding: 12rpx;
264
+ z-index: 10;
265
+ box-sizing: border-box;
266
+
267
+ &--failed {
268
+ background: rgba(244, 63, 94, 0.15);
269
+ }
270
+ }
271
+
272
+ &__status-text {
273
+ color: #ffffff;
274
+ font-size: 20rpx;
275
+ font-weight: 700;
276
+ margin-top: 8rpx;
277
+
278
+ &--failed {
279
+ color: #F43F5E;
280
+ }
281
+ }
282
+
283
+ &__progress-track {
284
+ position: absolute;
285
+ bottom: 0;
286
+ left: 0;
287
+ right: 0;
288
+ height: 6rpx;
289
+ background: rgba(255, 255, 255, 0.2);
290
+
291
+ .mu-upload__progress-bar {
292
+ height: 100%;
293
+ background: $mu-primary;
294
+ transition: width 0.2s ease;
295
+ }
296
+ }
297
+
298
+ &__add {
299
+ border-radius: 16rpx;
300
+ border: 2rpx dashed rgba(148, 163, 184, 0.4);
301
+ background: rgba(248, 250, 252, 0.8);
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: center;
305
+ cursor: pointer;
306
+ transition: all 0.2s ease;
307
+ box-sizing: border-box;
308
+
309
+ &:active {
310
+ border-color: $mu-primary;
311
+ background: rgba(99, 102, 241, 0.04);
312
+ }
313
+
314
+ &-inner {
315
+ display: flex;
316
+ flex-direction: column;
317
+ align-items: center;
318
+ justify-content: center;
319
+ }
320
+
321
+ &-text {
322
+ font-size: 22rpx;
323
+ color: #94A3B8;
324
+ font-weight: 600;
325
+ margin-top: 8rpx;
326
+ }
327
+ }
328
+
329
+ /* 单图头像专用样式 */
330
+ &__avatar-wrap {
331
+ position: relative;
332
+ border-radius: 50%;
333
+ overflow: hidden;
334
+ background: rgba(241, 245, 249, 0.9);
335
+ border: 2rpx dashed rgba(148, 163, 184, 0.5);
336
+ cursor: pointer;
337
+ display: flex;
338
+ align-items: center;
339
+ justify-content: center;
340
+ box-sizing: border-box;
341
+ }
342
+
343
+ &__avatar-img {
344
+ width: 100%;
345
+ height: 100%;
346
+ border-radius: 50%;
347
+ }
348
+
349
+ &__avatar-placeholder {
350
+ display: flex;
351
+ flex-direction: column;
352
+ align-items: center;
353
+ justify-content: center;
354
+ }
355
+
356
+ &__avatar-text {
357
+ font-size: 20rpx;
358
+ color: #64748B;
359
+ font-weight: 600;
360
+ margin-top: 6rpx;
361
+ }
362
+
363
+ &__avatar-edit {
364
+ position: absolute;
365
+ bottom: 0;
366
+ left: 0;
367
+ right: 0;
368
+ height: 40rpx;
369
+ background: rgba(15, 23, 42, 0.65);
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ }
374
+ }
375
+
376
+ .mu-dark, page.mu-dark {
377
+
378
+ .mu-upload {
379
+ &__item {
380
+ background: rgba(255, 255, 255, 0.04);
381
+ border-color: $mu-dark-border;
382
+ }
383
+ &__add {
384
+ background: rgba(255, 255, 255, 0.02);
385
+ border-color: rgba(255, 255, 255, 0.15);
386
+ &-text { color: $mu-dark-text-secondary; }
387
+ }
388
+ &__avatar-wrap {
389
+ background: rgba(255, 255, 255, 0.04);
390
+ border-color: rgba(255, 255, 255, 0.2);
391
+ }
392
+ &__avatar-text { color: $mu-dark-text-secondary; }
393
+ }
394
+ }
395
+
396
+ @media (prefers-color-scheme: dark) {
397
+
398
+ .mu-upload {
399
+ &__item {
400
+ background: rgba(255, 255, 255, 0.04);
401
+ border-color: $mu-dark-border;
402
+ }
403
+ &__add {
404
+ background: rgba(255, 255, 255, 0.02);
405
+ border-color: rgba(255, 255, 255, 0.15);
406
+ &-text { color: $mu-dark-text-secondary; }
407
+ }
408
+ &__avatar-wrap {
409
+ background: rgba(255, 255, 255, 0.04);
410
+ border-color: rgba(255, 255, 255, 0.2);
411
+ }
412
+ &__avatar-text { color: $mu-dark-text-secondary; }
413
+ }
414
+ }
415
+ </style>