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,339 @@
1
+ <template>
2
+ <view
3
+ class="mu-tabs"
4
+ :class="[
5
+ scrollable && 'mu-tabs--scroll',
6
+ variant && `mu-tabs--${variant}`,
7
+ `mu-tabs--align-${computedAlign}`,
8
+ jelly && 'mu-tabs--jelly'
9
+ ]"
10
+ :style="tabsStyle"
11
+ >
12
+ <scroll-view scroll-x class="mu-tabs__scroll" :scroll-left="scrollLeft" scroll-with-animation :show-scrollbar="false">
13
+ <view
14
+ class="mu-tabs__wrap"
15
+ :class="[
16
+ scrollable && 'mu-tabs__wrap--scrollable',
17
+ `mu-tabs__wrap--align-${computedAlign}`
18
+ ]"
19
+ >
20
+ <view
21
+ v-for="(item, index) in computedTabs"
22
+ :key="index"
23
+ class="mu-tabs__item mu-touchable-flat"
24
+ :class="[
25
+ index === currentIndex && 'mu-tabs__item--active',
26
+ variant === 'pill' && index === currentIndex && 'mu-tabs__item--pill-active'
27
+ ]"
28
+ :style="getItemStyle(index)"
29
+ :id="'mu-tab-' + index"
30
+ @tap="handleClick(index, item)"
31
+ >
32
+ <text class="mu-tabs__text" :style="getTextStyle(index)">
33
+ {{ getItemText(item) }}
34
+ </text>
35
+ <!-- 底部移动滑块指示线条 (仅在 line 变体且显示指示线时) -->
36
+ <view
37
+ v-if="variant !== 'pill' && showLine && index === currentIndex"
38
+ class="mu-tabs__line"
39
+ :class="[jelly && 'mu-tabs__line--jelly']"
40
+ :style="lineStyle"
41
+ ></view>
42
+ </view>
43
+ </view>
44
+ </scroll-view>
45
+ </view>
46
+ </template>
47
+
48
+ <script>
49
+ export default {
50
+ options: {
51
+ virtualHost: true
52
+ }
53
+ }
54
+ </script>
55
+
56
+ <script setup>
57
+ import { ref, computed, watch, nextTick } from 'vue'
58
+
59
+ const props = defineProps({
60
+ tabs: { type: Array, default: () => [] },
61
+ items: { type: Array, default: () => [] }, // 兼容 items 与 tabs
62
+ current: { type: Number, default: 0 },
63
+ value: { type: [Number, String], default: null }, // 兼容 v-model
64
+ modelValue: { type: [Number, String], default: null }, // Vue3 v-model 语法糖
65
+ showLine: { type: Boolean, default: true },
66
+ lineColor: { type: String, default: '' },
67
+ lineWidth: { type: String, default: '36rpx' },
68
+ lineHeight: { type: String, default: '6rpx' },
69
+ scrollable: { type: Boolean, default: false },
70
+ center: { type: Boolean, default: false },
71
+ align: { type: String, default: 'center' }, // left | center | right | space-around
72
+ gutter: { type: [Number, String], default: 24 },
73
+ activeColor: { type: String, default: '' },
74
+ color: { type: String, default: '' },
75
+ bold: { type: Boolean, default: true },
76
+ variant: { type: String, default: 'line' }, // line (下划线) | pill (胶囊)
77
+ jelly: { type: Boolean, default: true }, // 是否开启“吸吸冻”Q弹果冻拉伸切换动效
78
+ sticky: { type: Boolean, default: false },
79
+ offsetTop: { type: [Number, String], default: 0 },
80
+ zIndex: { type: [Number, String], default: 99 }
81
+ })
82
+
83
+ const emit = defineEmits(['change', 'update:current', 'update:modelValue', 'update:value'])
84
+
85
+ const scrollLeft = ref(0)
86
+
87
+ const computedTabs = computed(() => {
88
+ if (props.items && props.items.length > 0) return props.items
89
+ if (props.tabs && props.tabs.length > 0) return props.tabs
90
+ return []
91
+ })
92
+
93
+ const currentIndex = computed(() => {
94
+ if (props.modelValue !== null && props.modelValue !== undefined) return Number(props.modelValue)
95
+ if (props.value !== null && props.value !== undefined) return Number(props.value)
96
+ return Number(props.current) || 0
97
+ })
98
+
99
+ function getItemText(item) {
100
+ if (typeof item === 'object' && item !== null) {
101
+ return item.name || item.title || item.label || String(item)
102
+ }
103
+ return String(item)
104
+ }
105
+
106
+ const tabsStyle = computed(() => {
107
+ if (!props.sticky) return {}
108
+ let topVal = '0px'
109
+ if (typeof props.offsetTop === 'number') {
110
+ topVal = props.offsetTop === 0 ? '0px' : `${props.offsetTop}rpx`
111
+ } else if (props.offsetTop) {
112
+ topVal = String(props.offsetTop)
113
+ }
114
+
115
+ const style = {
116
+ position: 'sticky',
117
+ zIndex: props.zIndex
118
+ }
119
+ // #ifndef H5
120
+ style.top = topVal
121
+ // #endif
122
+
123
+ // #ifdef H5
124
+ style.top = `calc(var(--window-top, 0px) + ${topVal})`
125
+ // #endif
126
+ return style
127
+ })
128
+
129
+ const computedAlign = computed(() => {
130
+ if (props.center) return 'center'
131
+ return props.align || 'center'
132
+ })
133
+
134
+ function getItemStyle(index) {
135
+ const g = Number(props.gutter)
136
+ const style = {
137
+ paddingLeft: `${g}rpx`,
138
+ paddingRight: `${g}rpx`
139
+ }
140
+ if (props.variant === 'pill' && index === currentIndex.value) {
141
+ style.background = props.activeColor || '#171717'
142
+ style.borderRadius = '999rpx'
143
+ }
144
+ return style
145
+ }
146
+
147
+ function getTextStyle(index) {
148
+ if (index === currentIndex.value) {
149
+ if (props.variant === 'pill') {
150
+ return {
151
+ color: '#ffffff',
152
+ fontWeight: props.bold ? '800' : '600'
153
+ }
154
+ }
155
+ return {
156
+ color: props.activeColor || '#171717',
157
+ fontWeight: props.bold ? '800' : '600'
158
+ }
159
+ }
160
+ return {
161
+ color: props.color || '#64748B',
162
+ fontWeight: '400'
163
+ }
164
+ }
165
+
166
+ const lineStyle = computed(() => {
167
+ const activeColor = props.lineColor || props.activeColor || '#171717'
168
+ return {
169
+ width: props.lineWidth,
170
+ height: props.lineHeight,
171
+ backgroundColor: activeColor
172
+ }
173
+ })
174
+
175
+ function handleClick(index, item) {
176
+ emit('change', { index, item })
177
+ emit('update:current', index)
178
+ emit('update:modelValue', index)
179
+ emit('update:value', index)
180
+ }
181
+
182
+ watch(currentIndex, (val) => {
183
+ nextTick(() => {
184
+ scrollLeft.value = val * 45
185
+ })
186
+ })
187
+ </script>
188
+
189
+ <style lang="scss" scoped>
190
+ @import "@/uni.scss";
191
+
192
+ .mu-tabs {
193
+ background: #FFFFFF;
194
+ position: relative;
195
+ border-bottom: 1rpx solid rgba(241, 245, 249, 0.9);
196
+ width: 100%;
197
+ box-sizing: border-box;
198
+
199
+ &--pill {
200
+ border-bottom: none;
201
+ background: transparent;
202
+ }
203
+
204
+ &__scroll {
205
+ white-space: nowrap;
206
+ width: 100%;
207
+ }
208
+
209
+ &__wrap {
210
+ display: flex;
211
+ align-items: center;
212
+ position: relative;
213
+ width: 100%;
214
+ box-sizing: border-box;
215
+
216
+ /* 对齐方式定义 */
217
+ &--align-left {
218
+ justify-content: flex-start;
219
+ }
220
+
221
+ &--align-center {
222
+ justify-content: center;
223
+ }
224
+
225
+ &--align-right {
226
+ justify-content: flex-end;
227
+ }
228
+
229
+ &--align-space-around {
230
+ justify-content: space-around;
231
+ }
232
+
233
+ &--scrollable {
234
+ display: inline-flex;
235
+ min-width: 100%;
236
+ }
237
+ }
238
+
239
+ &__item {
240
+ display: inline-flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ height: 76rpx;
244
+ cursor: pointer;
245
+ transition: all $mu-duration $mu-ease;
246
+ position: relative;
247
+ flex-shrink: 0;
248
+
249
+ &--pill-active {
250
+ height: 64rpx;
251
+ margin: 6rpx 0;
252
+ box-shadow: 0 4rpx 14rpx rgba(15, 23, 42, 0.12);
253
+ animation: mu-pill-jelly 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
254
+ }
255
+ }
256
+
257
+ &__text {
258
+ font-size: 26rpx;
259
+ color: #64748B;
260
+ font-weight: 500;
261
+ transition: all $mu-duration $mu-ease;
262
+ }
263
+
264
+ &__line {
265
+ position: absolute;
266
+ bottom: 0;
267
+ left: 50%;
268
+ transform: translateX(-50%);
269
+ border-radius: 999rpx;
270
+ background: #171717;
271
+ box-shadow: none;
272
+ animation: mu-tabs-line-in 0.25s $mu-ease;
273
+
274
+ &--jelly {
275
+ animation: mu-tabs-jelly 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
276
+ }
277
+ }
278
+ }
279
+
280
+ /* “吸吸冻”Q弹果冻拉伸动画帧 */
281
+ @keyframes mu-tabs-jelly {
282
+ 0% {
283
+ opacity: 0.4;
284
+ transform: translateX(-50%) scale(0.3, 1.4);
285
+ }
286
+ 40% {
287
+ opacity: 1;
288
+ transform: translateX(-50%) scale(1.45, 0.6);
289
+ }
290
+ 70% {
291
+ transform: translateX(-50%) scale(0.88, 1.12);
292
+ }
293
+ 85% {
294
+ transform: translateX(-50%) scale(1.06, 0.96);
295
+ }
296
+ 100% {
297
+ opacity: 1;
298
+ transform: translateX(-50%) scale(1, 1);
299
+ }
300
+ }
301
+
302
+ @keyframes mu-pill-jelly {
303
+ 0% {
304
+ transform: scale(0.8, 1.2);
305
+ }
306
+ 45% {
307
+ transform: scale(1.15, 0.85);
308
+ }
309
+ 75% {
310
+ transform: scale(0.95, 1.05);
311
+ }
312
+ 100% {
313
+ transform: scale(1, 1);
314
+ }
315
+ }
316
+
317
+ @keyframes mu-tabs-line-in {
318
+ from { opacity: 0; transform: translateX(-50%) scaleX(0.3); }
319
+ to { opacity: 1; transform: translateX(-50%) scaleX(1); }
320
+ }
321
+
322
+ .mu-dark, page.mu-dark {
323
+
324
+ .mu-tabs {
325
+ background: #14141E !important;
326
+ border-color: rgba(255, 255, 255, 0.08);
327
+ &__text { color: #94A3B8; }
328
+ }
329
+ }
330
+
331
+ @media (prefers-color-scheme: dark) {
332
+
333
+ .mu-tabs {
334
+ background: #14141E !important;
335
+ border-color: rgba(255, 255, 255, 0.08);
336
+ &__text { color: #94A3B8; }
337
+ }
338
+ }
339
+ </style>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <view class="mu-tag" :class="tagClass" :style="tagStyle" @tap="handleClick">
3
+ <text class="mu-tag__text"><slot></slot></text>
4
+ <text v-if="closable" class="mu-tag__close" @tap.stop="handleClose">✕</text>
5
+ </view>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { computed } from 'vue'
10
+ const props = defineProps({
11
+ type: { type: String, default: 'default' },
12
+ size: { type: String, default: 'medium' },
13
+ plain: { type: Boolean, default: false },
14
+ round: { type: Boolean, default: false },
15
+ closable: { type: Boolean, default: false },
16
+ background: { type: String, default: '' },
17
+ color: { type: String, default: '' }
18
+ })
19
+ const emit = defineEmits(['click', 'close'])
20
+ const tagClass = computed(() => [
21
+ `mu-tag--${props.type}`, `mu-tag--${props.size}`,
22
+ props.plain && 'mu-tag--plain', props.round && 'mu-tag--round'
23
+ ])
24
+ const tagStyle = computed(() => ({
25
+ ...(props.background ? { background: props.background, borderColor: props.background } : {}),
26
+ ...(props.color ? { color: props.color } : {})
27
+ }))
28
+ function handleClick(e) { emit('click', e) }
29
+ function handleClose(e) { emit('close', e) }
30
+ </script>
31
+
32
+ <style lang="scss" scoped>
33
+ .mu-tag {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ border: 1rpx solid transparent;
37
+ transition: all $mu-duration $mu-ease;
38
+ white-space: nowrap;
39
+ font-weight: 600;
40
+
41
+ &--medium { padding: 6rpx 20rpx; font-size: $mu-font-sm; border-radius: $mu-radius-md; }
42
+ &--small { padding: 4rpx 14rpx; font-size: $mu-font-xs; border-radius: $mu-radius-sm; }
43
+ &--round { border-radius: $mu-radius-full; }
44
+
45
+ &--default { background: $mu-bg-gray; color: $mu-text-primary; border-color: $mu-border; }
46
+ &--primary { background: rgba(22, 93, 255, 0.08); color: $mu-primary; border-color: rgba(22, 93, 255, 0.18); }
47
+ &--success { background: rgba(0, 180, 42, 0.08); color: $mu-success; border-color: rgba(0, 180, 42, 0.18); }
48
+ &--warning { background: rgba(255, 125, 0, 0.08); color: $mu-warning; border-color: rgba(255, 125, 0, 0.18); }
49
+ &--error { background: rgba(245, 63, 63, 0.08); color: $mu-error; border-color: rgba(245, 63, 63, 0.18); }
50
+
51
+ &--plain.mu-tag--primary { background: transparent; }
52
+ &--plain.mu-tag--success { background: transparent; }
53
+ &--plain.mu-tag--warning { background: transparent; }
54
+ &--plain.mu-tag--error { background: transparent; }
55
+ &--plain.mu-tag--default { background: transparent; }
56
+
57
+ &__close {
58
+ margin-left: 10rpx;
59
+ font-size: 20rpx;
60
+ cursor: pointer;
61
+ opacity: 0.6;
62
+ &:active { opacity: 1; }
63
+ }
64
+ }
65
+ </style>
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <view class="mu-text-ellipsis">
3
+ <text class="mu-text-ellipsis__text" :class="{ 'mu-text-ellipsis__text--expanded': expanded }"
4
+ :style="{ '-webkit-line-clamp': expanded ? 'none' : rows }">
5
+ <slot>{{ text }}</slot>
6
+ </text>
7
+ <text v-if="expandable" class="mu-text-ellipsis__action" @tap="toggle">
8
+ {{ expanded ? collapseText : expandText }}
9
+ </text>
10
+ </view>
11
+ </template>
12
+
13
+ <script setup>
14
+ import { ref } from 'vue'
15
+
16
+ const props = defineProps({
17
+ text: { type: String, default: '' },
18
+ rows: { type: [Number, String], default: 3 },
19
+ expandable: { type: Boolean, default: true },
20
+ expandText: { type: String, default: '展开' },
21
+ collapseText: { type: String, default: '收起' }
22
+ })
23
+
24
+ const emit = defineEmits(['change', 'click-action'])
25
+
26
+ const expanded = ref(false)
27
+
28
+ function toggle() {
29
+ expanded.value = !expanded.value
30
+ emit('change', expanded.value)
31
+ emit('click-action', expanded.value)
32
+ }
33
+ </script>
34
+
35
+ <style lang="scss" scoped>
36
+ .mu-text-ellipsis {
37
+ &__text {
38
+ font-size: $mu-font-base; color: $mu-text-primary;
39
+ line-height: $mu-line-height;
40
+ display: -webkit-box;
41
+ -webkit-box-orient: vertical;
42
+ overflow: hidden;
43
+ word-break: break-all;
44
+ &--expanded {
45
+ display: block;
46
+ -webkit-line-clamp: none !important;
47
+ }
48
+ }
49
+ &__action {
50
+ font-size: $mu-font-sm; color: $mu-primary;
51
+ cursor: pointer; margin-top: 8rpx; display: inline-block;
52
+ }
53
+ }
54
+ </style>
@@ -0,0 +1,166 @@
1
+ <template>
2
+ <view class="mu-timeline">
3
+ <view
4
+ v-for="(item, index) in items"
5
+ :key="index"
6
+ class="mu-timeline__item"
7
+ :class="[
8
+ index === items.length - 1 && 'mu-timeline__item--last',
9
+ index === 0 && 'mu-timeline__item--first'
10
+ ]"
11
+ >
12
+ <!-- 左侧轴线区域 -->
13
+ <view class="mu-timeline__rail">
14
+ <view class="mu-timeline__dot" :style="getDotStyle(item, index)">
15
+ <slot :name="`dot-${index}`">
16
+ <mu-icon v-if="item.icon" :name="item.icon" :size="12" color="#ffffff" />
17
+ </slot>
18
+ </view>
19
+ <view v-if="index < items.length - 1" class="mu-timeline__line"></view>
20
+ </view>
21
+
22
+ <!-- 右侧内容区域 -->
23
+ <view class="mu-timeline__body">
24
+ <!-- 标题行 -->
25
+ <view v-if="item.title" class="mu-timeline__title-row">
26
+ <text class="mu-timeline__title" :class="{ 'mu-timeline__title--active': index === 0 }">{{ item.title }}</text>
27
+ </view>
28
+
29
+ <!-- 描述内容 -->
30
+ <view v-if="item.content || $slots[`content-${index}`]" class="mu-timeline__content">
31
+ <slot :name="`content-${index}`" :item="item">
32
+ <text class="mu-timeline__desc">{{ item.content }}</text>
33
+ </slot>
34
+ </view>
35
+
36
+ <!-- 时间戳 -->
37
+ <view v-if="item.time" class="mu-timeline__time">
38
+ <text class="mu-timeline__time-text">{{ item.time }}</text>
39
+ </view>
40
+ </view>
41
+ </view>
42
+ </view>
43
+ </template>
44
+
45
+ <script setup>
46
+ const props = defineProps({
47
+ items: { type: Array, default: () => [] },
48
+ dotColor: { type: String, default: '' },
49
+ lineColor: { type: String, default: '' }
50
+ })
51
+
52
+ function getDotStyle(item, index) {
53
+ const color = item.dotColor || props.dotColor || (index === 0 ? '#171717' : '#CBD5E1')
54
+ return {
55
+ background: color,
56
+ boxShadow: index === 0 ? `0 0 0 6rpx rgba(99, 102, 241, 0.15)` : 'none'
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <style lang="scss" scoped>
62
+ @import "@/uni.scss";
63
+
64
+ .mu-timeline {
65
+ padding: 8rpx 0;
66
+
67
+ &__item {
68
+ display: flex;
69
+ position: relative;
70
+
71
+ &--last .mu-timeline__line {
72
+ display: none;
73
+ }
74
+ }
75
+
76
+ &__rail {
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: center;
80
+ width: 40rpx;
81
+ flex-shrink: 0;
82
+ margin-right: 20rpx;
83
+ }
84
+
85
+ &__dot {
86
+ width: 20rpx;
87
+ height: 20rpx;
88
+ border-radius: 50%;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ flex-shrink: 0;
93
+ margin-top: 6rpx;
94
+ z-index: 2;
95
+ transition: all 0.2s ease;
96
+ }
97
+
98
+ &__line {
99
+ width: 2rpx;
100
+ flex: 1;
101
+ background: rgba(226, 232, 240, 0.8);
102
+ margin: 8rpx 0;
103
+ min-height: 32rpx;
104
+ }
105
+
106
+ &__body {
107
+ flex: 1;
108
+ min-width: 0;
109
+ padding-bottom: 32rpx;
110
+ }
111
+
112
+ &__title-row {
113
+ margin-bottom: 6rpx;
114
+ }
115
+
116
+ &__title {
117
+ font-size: 26rpx;
118
+ font-weight: 700;
119
+ color: $mu-text-primary;
120
+ line-height: 1.4;
121
+
122
+ &--active {
123
+ color: #171717;
124
+ }
125
+ }
126
+
127
+ &__content {
128
+ margin-bottom: 6rpx;
129
+ }
130
+
131
+ &__desc {
132
+ font-size: 24rpx;
133
+ color: #64748B;
134
+ line-height: 1.6;
135
+ word-break: break-all;
136
+ }
137
+
138
+ &__time {
139
+ &-text {
140
+ font-size: 22rpx;
141
+ color: #94A3B8;
142
+ font-variant-numeric: tabular-nums;
143
+ }
144
+ }
145
+ }
146
+
147
+ .mu-dark, page.mu-dark {
148
+
149
+ .mu-timeline {
150
+ &__title { color: $mu-dark-text-primary; &--active { color: #FFFFFF; } }
151
+ &__desc { color: $mu-dark-text-secondary; }
152
+ &__line { background: rgba(255, 255, 255, 0.08); }
153
+ &__dot { box-shadow: none !important; }
154
+ }
155
+ }
156
+
157
+ @media (prefers-color-scheme: dark) {
158
+
159
+ .mu-timeline {
160
+ &__title { color: $mu-dark-text-primary; &--active { color: #FFFFFF; } }
161
+ &__desc { color: $mu-dark-text-secondary; }
162
+ &__line { background: rgba(255, 255, 255, 0.08); }
163
+ &__dot { box-shadow: none !important; }
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <view v-if="visible" class="mu-tips" :class="[`mu-tips--${type}`]">
3
+ <text class="mu-tips__text"><slot>{{ text }}</slot></text>
4
+ <view v-if="closable" class="mu-tips__close" @tap="handleClose">
5
+ <text class="mu-tips__close-icon">✕</text>
6
+ </view>
7
+ </view>
8
+ </template>
9
+
10
+ <script setup>
11
+ import { ref, watch } from 'vue'
12
+ const props = defineProps({
13
+ show: { type: Boolean, default: true },
14
+ text: { type: String, default: '' },
15
+ type: { type: String, default: 'info' },
16
+ closable: { type: Boolean, default: false }
17
+ })
18
+ const emit = defineEmits(['close', 'update:show'])
19
+ const visible = ref(props.show)
20
+ watch(() => props.show, (val) => { visible.value = val })
21
+ function handleClose() { visible.value = false; emit('close'); emit('update:show', false) }
22
+ </script>
23
+
24
+ <style lang="scss" scoped>
25
+ .mu-tips {
26
+ display: flex; align-items: center; padding: $mu-space-3 $mu-space-6;
27
+ font-size: $mu-font-sm; line-height: 1.5;
28
+ &--info { background: $mu-info-bg; color: $mu-primary; }
29
+ &--success { background: $mu-success-bg; color: $mu-success; }
30
+ &--warning { background: $mu-warning-bg; color: $mu-warning; }
31
+ &--error { background: $mu-error-bg; color: $mu-error; }
32
+ &__text { flex: 1; }
33
+ &__close { margin-left: $mu-space-3; cursor: pointer; opacity: 0.6; }
34
+ &__close-icon { font-size: $mu-font-xs; }
35
+ }
36
+ </style>