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,228 @@
1
+ <template>
2
+ <view class="mu-slider" :class="{ 'mu-slider--disabled': disabled }">
3
+ <view
4
+ class="mu-slider__track"
5
+ id="sliderTrack"
6
+ ref="trackRef"
7
+ @touchstart="onTouchStart"
8
+ @touchmove="onTouchMove"
9
+ @touchend="onTouchEnd"
10
+ @mousedown="onMouseDown"
11
+ >
12
+ <view class="mu-slider__bar" :style="{ width: percent + '%' }"></view>
13
+ <view class="mu-slider__thumb" :style="{ left: percent + '%' }">
14
+ <view v-if="showValue" class="mu-slider__tooltip">{{ modelValue }}</view>
15
+ </view>
16
+ </view>
17
+ <text v-if="showValueText" class="mu-slider__value">{{ modelValue }}</text>
18
+ </view>
19
+ </template>
20
+
21
+ <script setup>
22
+ import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'
23
+
24
+ const props = defineProps({
25
+ modelValue: { type: Number, default: 0 },
26
+ min: { type: Number, default: 0 },
27
+ max: { type: Number, default: 100 },
28
+ step: { type: Number, default: 1 },
29
+ disabled: { type: Boolean, default: false },
30
+ showValue: { type: Boolean, default: false },
31
+ showValueText: { type: Boolean, default: true }
32
+ })
33
+
34
+ const emit = defineEmits(['update:modelValue', 'change', 'drag-start', 'drag-end'])
35
+
36
+ const instance = getCurrentInstance()
37
+ const trackLeft = ref(0)
38
+ const trackWidth = ref(0)
39
+ const isDragging = ref(false)
40
+
41
+ const percent = computed(() => {
42
+ const range = props.max - props.min
43
+ if (range <= 0) return 0
44
+ return Math.min(100, Math.max(0, ((props.modelValue - props.min) / range) * 100))
45
+ })
46
+
47
+ function updateTrackRect(callback) {
48
+ // #ifndef APP-NVUE
49
+ const query = uni.createSelectorQuery().in(instance)
50
+ query.select('#sliderTrack').boundingClientRect(data => {
51
+ if (data) {
52
+ trackLeft.value = data.left
53
+ trackWidth.value = data.width
54
+ if (callback) callback()
55
+ }
56
+ }).exec()
57
+ // #endif
58
+ }
59
+
60
+ function getClientX(e) {
61
+ if (e.touches && e.touches[0]) return e.touches[0].clientX
62
+ if (e.changedTouches && e.changedTouches[0]) return e.changedTouches[0].clientX
63
+ if (e.clientX !== undefined) return e.clientX
64
+ return 0
65
+ }
66
+
67
+ function getValueFromX(clientX) {
68
+ if (trackWidth.value <= 0) return props.modelValue
69
+ let diff = clientX - trackLeft.value
70
+ diff = Math.max(0, Math.min(diff, trackWidth.value))
71
+ const ratio = diff / trackWidth.value
72
+ let val = props.min + ratio * (props.max - props.min)
73
+ if (props.step > 0) {
74
+ val = Math.round((val - props.min) / props.step) * props.step + props.min
75
+ }
76
+ return Math.min(props.max, Math.max(props.min, val))
77
+ }
78
+
79
+ function handleStart(e) {
80
+ if (props.disabled) return
81
+ isDragging.value = true
82
+ updateTrackRect(() => {
83
+ const clientX = getClientX(e)
84
+ const val = getValueFromX(clientX)
85
+ emit('drag-start', props.modelValue)
86
+ emit('update:modelValue', val)
87
+ emit('change', val)
88
+ })
89
+ }
90
+
91
+ function handleMove(e) {
92
+ if (props.disabled || !isDragging.value) return
93
+ const clientX = getClientX(e)
94
+ const val = getValueFromX(clientX)
95
+ emit('update:modelValue', val)
96
+ emit('change', val)
97
+ }
98
+
99
+ function handleEnd() {
100
+ if (props.disabled || !isDragging.value) return
101
+ isDragging.value = false
102
+ emit('drag-end', props.modelValue)
103
+ }
104
+
105
+ function onTouchStart(e) { handleStart(e) }
106
+ function onTouchMove(e) { handleMove(e) }
107
+ function onTouchEnd() { handleEnd() }
108
+
109
+ // H5 Mouse event handlers for desktop support
110
+ function onMouseDown(e) {
111
+ handleStart(e)
112
+ // #ifdef H5
113
+ window.addEventListener('mousemove', onMouseMove)
114
+ window.addEventListener('mouseup', onMouseUp)
115
+ // #endif
116
+ }
117
+
118
+ function onMouseMove(e) {
119
+ handleMove(e)
120
+ }
121
+
122
+ function onMouseUp() {
123
+ handleEnd()
124
+ // #ifdef H5
125
+ window.removeEventListener('mousemove', onMouseMove)
126
+ window.removeEventListener('mouseup', onMouseUp)
127
+ // #endif
128
+ }
129
+
130
+ onMounted(() => {
131
+ setTimeout(() => {
132
+ updateTrackRect()
133
+ }, 100)
134
+ })
135
+
136
+ onUnmounted(() => {
137
+ // #ifdef H5
138
+ window.removeEventListener('mousemove', onMouseMove)
139
+ window.removeEventListener('mouseup', onMouseUp)
140
+ // #endif
141
+ })
142
+ </script>
143
+
144
+ <style lang="scss" scoped>
145
+ @import "@/uni.scss";
146
+
147
+ .mu-slider {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 24rpx;
151
+ padding: 20rpx 0;
152
+ width: 100%;
153
+ box-sizing: border-box;
154
+
155
+ &--disabled { opacity: $mu-opacity-disabled; }
156
+
157
+ &__track {
158
+ flex: 1;
159
+ height: 12rpx;
160
+ background: $mu-border;
161
+ border-radius: $mu-radius-full;
162
+ position: relative;
163
+ cursor: pointer;
164
+ touch-action: none;
165
+ }
166
+ &__bar {
167
+ height: 100%;
168
+ background: $mu-primary-gradient;
169
+ border-radius: $mu-radius-full;
170
+ position: absolute;
171
+ left: 0;
172
+ top: 0;
173
+ }
174
+ &__thumb {
175
+ width: 40rpx;
176
+ height: 40rpx;
177
+ border-radius: 50%;
178
+ background: #ffffff;
179
+ border: 4rpx solid $mu-primary;
180
+ position: absolute;
181
+ top: 50%;
182
+ transform: translate(-50%, -50%);
183
+ box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.2);
184
+ z-index: 1;
185
+ cursor: grab;
186
+ transition: transform 0.15s ease;
187
+
188
+ &:active {
189
+ transform: translate(-50%, -50%) scale(1.2);
190
+ }
191
+ }
192
+ &__tooltip {
193
+ position: absolute;
194
+ bottom: 52rpx;
195
+ left: 50%;
196
+ transform: translateX(-50%);
197
+ background: rgba(15, 23, 42, 0.88);
198
+ color: #ffffff;
199
+ font-size: 22rpx;
200
+ font-weight: 700;
201
+ padding: 4rpx 14rpx;
202
+ border-radius: 8rpx;
203
+ pointer-events: none;
204
+ white-space: nowrap;
205
+ }
206
+ &__value {
207
+ font-size: 26rpx;
208
+ font-weight: 700;
209
+ color: $mu-text-secondary;
210
+ min-width: 48rpx;
211
+ text-align: right;
212
+ }
213
+ }
214
+
215
+ .mu-dark, page.mu-dark {
216
+ .mu-slider {
217
+ &__track { background: rgba(255, 255, 255, 0.12); }
218
+ &__value { color: $mu-dark-text-secondary; }
219
+ }
220
+ }
221
+
222
+ @media (prefers-color-scheme: dark) {
223
+ .mu-slider {
224
+ &__track { background: rgba(255, 255, 255, 0.12); }
225
+ &__value { color: $mu-dark-text-secondary; }
226
+ }
227
+ }
228
+ </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <view v-if="hasSlot" class="mu-space" :class="spaceClasses" :style="containerStyle">
3
+ <slot></slot>
4
+ </view>
5
+ <view v-else class="mu-space-block" :style="blockStyle"></view>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { computed, useSlots } from 'vue'
10
+
11
+ const slots = useSlots()
12
+
13
+ const props = defineProps({
14
+ height: { type: [Number, String], default: '' },
15
+ size: { type: [Number, String], default: 20 },
16
+ direction: { type: String, default: 'row' },
17
+ dir: { type: String, default: '' },
18
+ align: { type: String, default: 'center' },
19
+ wrap: { type: Boolean, default: true },
20
+ block: { type: Boolean, default: false }
21
+ })
22
+
23
+ const hasSlot = computed(() => !!slots.default)
24
+
25
+ const computedDirection = computed(() => props.dir || props.direction || 'row')
26
+
27
+ const computedGap = computed(() => {
28
+ const val = props.height !== '' ? props.height : props.size
29
+ if (val === 'small' || val === 'sm') return '12rpx'
30
+ if (val === 'medium' || val === 'md') return '20rpx'
31
+ if (val === 'large' || val === 'lg') return '32rpx'
32
+ if (typeof val === 'number') return `${val}rpx`
33
+ if (typeof val === 'string') {
34
+ if (val.endsWith('rpx') || val.endsWith('px') || val.endsWith('%') || val.endsWith('rem')) return val
35
+ return `${val}rpx`
36
+ }
37
+ return '20rpx'
38
+ })
39
+
40
+ const spaceClasses = computed(() => [
41
+ `mu-space--${computedDirection.value}`,
42
+ props.wrap && 'mu-space--wrap',
43
+ props.block && 'mu-space--block'
44
+ ])
45
+
46
+ const containerStyle = computed(() => ({
47
+ gap: computedGap.value,
48
+ alignItems: props.align === 'start' ? 'flex-start' : (props.align === 'end' ? 'flex-end' : props.align)
49
+ }))
50
+
51
+ const blockStyle = computed(() => {
52
+ if (computedDirection.value === 'column' || computedDirection.value === 'vertical') {
53
+ return { height: computedGap.value, width: '100%' }
54
+ }
55
+ return { height: computedGap.value, width: '100%' }
56
+ })
57
+ </script>
58
+
59
+ <style lang="scss" scoped>
60
+ .mu-space {
61
+ display: inline-flex;
62
+ box-sizing: border-box;
63
+
64
+ &--row {
65
+ flex-direction: row;
66
+ }
67
+ &--column {
68
+ flex-direction: column;
69
+ }
70
+ &--wrap {
71
+ flex-wrap: wrap;
72
+ }
73
+ &--block {
74
+ display: flex;
75
+ width: 100%;
76
+ }
77
+ }
78
+
79
+ .mu-space-block {
80
+ display: block;
81
+ width: 100%;
82
+ box-sizing: border-box;
83
+ }
84
+ </style>
@@ -0,0 +1,311 @@
1
+ <template>
2
+ <view class="mu-steps" :class="{ 'mu-steps--vertical': direction === 'vertical' }">
3
+ <view
4
+ v-for="(item, index) in displaySteps"
5
+ :key="index"
6
+ class="mu-steps__item"
7
+ :class="[
8
+ `mu-steps__item--${getStatus(index)}`,
9
+ !showNumber && 'mu-steps__item--dot-mode'
10
+ ]"
11
+ >
12
+ <view class="mu-steps__indicator">
13
+ <!-- 圆点指示器 -->
14
+ <view
15
+ class="mu-steps__dot"
16
+ :class="[
17
+ isFinished(index) && 'mu-steps__dot--finish',
18
+ isProcess(index) && 'mu-steps__dot--process',
19
+ !showNumber && 'mu-steps__dot--point-only'
20
+ ]"
21
+ :style="getDotStyle(index)"
22
+ >
23
+ <template v-if="showNumber">
24
+ <mu-icon v-if="isFinished(index)" name="check" :size="12" color="#ffffff" />
25
+ <text v-else class="mu-steps__num">{{ getDisplayNumber(index) }}</text>
26
+ </template>
27
+ <view v-else class="mu-steps__inner-point"></view>
28
+ </view>
29
+
30
+ <!-- 连接线 -->
31
+ <view
32
+ v-if="index < displaySteps.length - 1"
33
+ class="mu-steps__line"
34
+ :class="{ 'mu-steps__line--finish': isLineFinished(index) }"
35
+ :style="getLineStyle(index)"
36
+ ></view>
37
+ </view>
38
+
39
+ <!-- 步骤内容 -->
40
+ <view class="mu-steps__content">
41
+ <text class="mu-steps__title" :style="getTitleStyle(index)">{{ getItemTitle(item) }}</text>
42
+ <text v-if="getItemTime(item)" class="mu-steps__time">{{ getItemTime(item) }}</text>
43
+ <text v-if="getItemDesc(item)" class="mu-steps__desc">{{ getItemDesc(item) }}</text>
44
+ </view>
45
+ </view>
46
+ </view>
47
+ </template>
48
+
49
+ <script setup>
50
+ import { computed } from 'vue'
51
+
52
+ const props = defineProps({
53
+ steps: { type: Array, default: () => [] },
54
+ items: { type: Array, default: () => [] },
55
+ current: { type: Number, default: 0 },
56
+ direction: { type: String, default: 'horizontal' },
57
+ activeColor: { type: String, default: '#171717' }, // 主主题色/黑金
58
+ reverse: { type: Boolean, default: false }, // 是否倒序展示时间线(最新在上/最前)
59
+ showNumber: { type: Boolean, default: true } // 是否显示圆点内部数字
60
+ })
61
+
62
+ const rawSteps = computed(() => {
63
+ if (props.steps && props.steps.length) return props.steps
64
+ return props.items || []
65
+ })
66
+
67
+ const displaySteps = computed(() => {
68
+ if (props.reverse) {
69
+ return [...rawSteps.value].reverse()
70
+ }
71
+ return rawSteps.value
72
+ })
73
+
74
+ function isProcess(index) {
75
+ if (props.reverse) {
76
+ return index === 0
77
+ }
78
+ return index === props.current
79
+ }
80
+
81
+ function isFinished(index) {
82
+ if (props.reverse) {
83
+ return index > 0
84
+ }
85
+ return index < props.current
86
+ }
87
+
88
+ function isLineFinished(index) {
89
+ if (props.reverse) {
90
+ return true
91
+ }
92
+ return index < props.current
93
+ }
94
+
95
+ function getStatus(index) {
96
+ if (isProcess(index)) return 'process'
97
+ if (isFinished(index)) return 'finish'
98
+ return 'wait'
99
+ }
100
+
101
+ function getDisplayNumber(index) {
102
+ if (props.reverse) {
103
+ return displaySteps.value.length - index
104
+ }
105
+ return index + 1
106
+ }
107
+
108
+ function getDotStyle(index) {
109
+ const active = isProcess(index) || isFinished(index)
110
+ if (active && props.activeColor && props.activeColor !== '#171717') {
111
+ return { background: props.activeColor, borderColor: 'transparent' }
112
+ }
113
+ return {}
114
+ }
115
+
116
+ function getLineStyle(index) {
117
+ const active = isLineFinished(index)
118
+ if (active && props.activeColor && props.activeColor !== '#171717') {
119
+ return { background: props.activeColor }
120
+ }
121
+ return {}
122
+ }
123
+
124
+ function getTitleStyle(index) {
125
+ const active = isProcess(index) || isFinished(index)
126
+ if (active && props.activeColor && props.activeColor !== '#171717') {
127
+ return { color: props.activeColor }
128
+ }
129
+ return {}
130
+ }
131
+
132
+ function getItemTitle(item) {
133
+ if (typeof item === 'object' && item !== null) {
134
+ return item.title || item.name || String(item)
135
+ }
136
+ return String(item)
137
+ }
138
+
139
+ function getItemDesc(item) {
140
+ if (typeof item === 'object' && item !== null) {
141
+ return item.desc || item.description || ''
142
+ }
143
+ return ''
144
+ }
145
+
146
+ function getItemTime(item) {
147
+ if (typeof item === 'object' && item !== null) {
148
+ return item.time || item.date || ''
149
+ }
150
+ return ''
151
+ }
152
+ </script>
153
+
154
+ <style lang="scss" scoped>
155
+ @import "@/uni.scss";
156
+
157
+ .mu-steps {
158
+ display: flex;
159
+ width: 100%;
160
+
161
+ &--vertical {
162
+ flex-direction: column;
163
+ .mu-steps__item {
164
+ flex-direction: row;
165
+ align-items: flex-start;
166
+ padding-bottom: 32rpx;
167
+ }
168
+ .mu-steps__indicator {
169
+ flex-direction: column;
170
+ width: auto;
171
+ margin-right: 24rpx;
172
+ }
173
+ .mu-steps__line {
174
+ width: 2rpx;
175
+ height: 100%;
176
+ min-height: 48rpx;
177
+ margin: 8rpx 0;
178
+ }
179
+ .mu-steps__content {
180
+ margin-top: 0;
181
+ text-align: left;
182
+ }
183
+ }
184
+
185
+ &__item {
186
+ flex: 1;
187
+ display: flex;
188
+ flex-direction: column;
189
+ align-items: center;
190
+ position: relative;
191
+
192
+ &--process .mu-steps__dot {
193
+ background: #171717;
194
+ color: #ffffff;
195
+ border-color: transparent;
196
+ box-shadow: 0 4rpx 14rpx rgba(23, 23, 23, 0.25);
197
+ }
198
+ &--process .mu-steps__title { color: #171717; font-weight: 800; }
199
+
200
+ &--finish .mu-steps__dot {
201
+ background: #171717;
202
+ color: #ffffff;
203
+ border-color: transparent;
204
+ }
205
+ &--finish .mu-steps__title { color: #171717; font-weight: 700; }
206
+
207
+ &--wait .mu-steps__dot { background: rgba(241, 245, 249, 0.8); color: #94A3B8; border: 1rpx solid $mu-border; }
208
+ &--wait .mu-steps__title { color: #94A3B8; }
209
+ }
210
+
211
+ &__indicator {
212
+ display: flex;
213
+ align-items: center;
214
+ width: 100%;
215
+ position: relative;
216
+ }
217
+
218
+ &__dot {
219
+ width: 44rpx;
220
+ height: 44rpx;
221
+ border-radius: 50%;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ flex-shrink: 0;
226
+ transition: all 0.2s ease;
227
+ z-index: 2;
228
+
229
+ &--point-only {
230
+ width: 20rpx;
231
+ height: 20rpx;
232
+ margin: 12rpx 0;
233
+
234
+ .mu-steps__inner-point {
235
+ width: 8rpx;
236
+ height: 8rpx;
237
+ border-radius: 50%;
238
+ background: #ffffff;
239
+ }
240
+ }
241
+ }
242
+
243
+ &__num {
244
+ font-size: 22rpx;
245
+ font-weight: 700;
246
+ }
247
+
248
+ &__line {
249
+ flex: 1;
250
+ height: 4rpx;
251
+ background: rgba(226, 232, 240, 0.8);
252
+
253
+ &--finish {
254
+ background: #171717;
255
+ }
256
+ }
257
+
258
+ &__content {
259
+ margin-top: 12rpx;
260
+ text-align: center;
261
+ }
262
+
263
+ &__title {
264
+ font-size: 24rpx;
265
+ color: $mu-text-primary;
266
+ font-weight: 600;
267
+ line-height: 1.4;
268
+ }
269
+
270
+ &__time {
271
+ font-size: 20rpx;
272
+ color: #94A3B8;
273
+ margin-top: 2rpx;
274
+ display: block;
275
+ }
276
+
277
+ &__desc {
278
+ font-size: 22rpx;
279
+ color: #64748B;
280
+ margin-top: 6rpx;
281
+ display: block;
282
+ line-height: 1.4;
283
+ }
284
+ }
285
+
286
+ .mu-dark, page.mu-dark {
287
+
288
+ .mu-steps {
289
+ &__title { color: $mu-dark-text-primary; }
290
+ &__desc { color: #94A3B8; }
291
+ &__line { background: rgba(255, 255, 255, 0.1); }
292
+ &--wait .mu-steps__dot { background: rgba(255, 255, 255, 0.06); border-color: $mu-dark-border; }
293
+ &--process .mu-steps__dot, &--finish .mu-steps__dot { background: #ffffff !important; color: #171717 !important; }
294
+ &--process .mu-steps__title, &--finish .mu-steps__title { color: #ffffff !important; }
295
+ &__line--finish { background: #ffffff !important; }
296
+ }
297
+ }
298
+
299
+ @media (prefers-color-scheme: dark) {
300
+
301
+ .mu-steps {
302
+ &__title { color: $mu-dark-text-primary; }
303
+ &__desc { color: #94A3B8; }
304
+ &__line { background: rgba(255, 255, 255, 0.1); }
305
+ &--wait .mu-steps__dot { background: rgba(255, 255, 255, 0.06); border-color: $mu-dark-border; }
306
+ &--process .mu-steps__dot, &--finish .mu-steps__dot { background: #ffffff !important; color: #171717 !important; }
307
+ &--process .mu-steps__title, &--finish .mu-steps__title { color: #ffffff !important; }
308
+ &__line--finish { background: #ffffff !important; }
309
+ }
310
+ }
311
+ </style>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <view class="mu-sticky" :style="stickyStyle">
3
+ <slot></slot>
4
+ </view>
5
+ </template>
6
+
7
+ <script setup>
8
+ /**
9
+ * mu-sticky 粘性布局组件
10
+ * @description 滚动到设定的顶部阈值时自动吸顶悬浮,向下滚动自动复位 (全端原生 CSS Sticky 极致性能支持)
11
+ */
12
+ import { computed } from 'vue'
13
+
14
+ const props = defineProps({
15
+ top: { type: [Number, String], default: 0 }, // 距离顶部的吸顶距离 (支持 0, '20rpx', '10px' 等)
16
+ zIndex: { type: [Number, String], default: 99 }, // 层级
17
+ disabled: { type: Boolean, default: false }, // 是否禁用吸顶
18
+ bgColor: { type: String, default: '' } // 吸顶背景色
19
+ })
20
+
21
+ const formattedTop = computed(() => {
22
+ if (typeof props.top === 'number') return `${props.top}rpx`
23
+ return props.top || '0rpx'
24
+ })
25
+
26
+ const stickyStyle = computed(() => {
27
+ if (props.disabled) return {}
28
+
29
+ const style = {
30
+ position: 'sticky',
31
+ zIndex: props.zIndex
32
+ }
33
+
34
+ // #ifndef H5
35
+ style.top = formattedTop.value
36
+ // #endif
37
+
38
+ // #ifdef H5
39
+ style.top = `calc(var(--window-top, 0px) + ${formattedTop.value})`
40
+ // #endif
41
+
42
+ if (props.bgColor) {
43
+ style.backgroundColor = props.bgColor
44
+ }
45
+ return style
46
+ })
47
+ </script>
48
+
49
+ <style lang="scss" scoped>
50
+ @import "@/uni.scss";
51
+
52
+ .mu-sticky {
53
+ position: sticky;
54
+ width: 100%;
55
+ box-sizing: border-box;
56
+ }
57
+ </style>