@didaoktv/didaoui-uniapp 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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/components/dd-action-sheet/dd-action-sheet.vue +207 -0
  4. package/components/dd-alert/dd-alert.vue +240 -0
  5. package/components/dd-avatar/dd-avatar.vue +168 -0
  6. package/components/dd-backtop/dd-backtop.vue +75 -0
  7. package/components/dd-badge/dd-badge.vue +150 -0
  8. package/components/dd-button/dd-button.vue +215 -0
  9. package/components/dd-capsule-button/dd-capsule-button.vue +109 -0
  10. package/components/dd-card/dd-card.vue +155 -0
  11. package/components/dd-cell/dd-cell.vue +201 -0
  12. package/components/dd-cell-group/dd-cell-group.vue +56 -0
  13. package/components/dd-champion-card/dd-champion-card.vue +258 -0
  14. package/components/dd-checkbox/dd-checkbox.vue +145 -0
  15. package/components/dd-collapse/dd-collapse.vue +63 -0
  16. package/components/dd-collapse-item/dd-collapse-item.vue +154 -0
  17. package/components/dd-count-down/dd-count-down.vue +163 -0
  18. package/components/dd-date-picker/dd-date-picker.vue +253 -0
  19. package/components/dd-dialog/dd-dialog.vue +264 -0
  20. package/components/dd-divider/dd-divider.vue +104 -0
  21. package/components/dd-drawer/dd-drawer.vue +214 -0
  22. package/components/dd-dropdown-item/dd-dropdown-item.vue +203 -0
  23. package/components/dd-dropdown-menu/dd-dropdown-menu.vue +177 -0
  24. package/components/dd-empty-state/dd-empty-state.vue +140 -0
  25. package/components/dd-feature-grid/dd-feature-grid.vue +139 -0
  26. package/components/dd-field/dd-field.vue +240 -0
  27. package/components/dd-icon/dd-icon.vue +464 -0
  28. package/components/dd-image/dd-image.vue +158 -0
  29. package/components/dd-input/dd-input.vue +172 -0
  30. package/components/dd-list-cell/dd-list-cell.vue +152 -0
  31. package/components/dd-loading/dd-loading.vue +187 -0
  32. package/components/dd-loadmore/dd-loadmore.vue +152 -0
  33. package/components/dd-mini-program-navbar/dd-mini-program-navbar.vue +245 -0
  34. package/components/dd-modal/dd-modal.vue +286 -0
  35. package/components/dd-navigation/dd-navigation.vue +134 -0
  36. package/components/dd-overlay/dd-overlay.vue +114 -0
  37. package/components/dd-picker/dd-picker.vue +203 -0
  38. package/components/dd-popover/dd-popover.vue +190 -0
  39. package/components/dd-popover-item/dd-popover-item.vue +88 -0
  40. package/components/dd-popup/dd-popup.vue +243 -0
  41. package/components/dd-progress/dd-progress.vue +255 -0
  42. package/components/dd-pull-refresh/dd-pull-refresh.vue +232 -0
  43. package/components/dd-radio/dd-radio.vue +129 -0
  44. package/components/dd-rate/dd-rate.vue +133 -0
  45. package/components/dd-room-card/dd-room-card.vue +309 -0
  46. package/components/dd-search-bar/dd-search-bar.vue +176 -0
  47. package/components/dd-segmented-tab/dd-segmented-tab.vue +187 -0
  48. package/components/dd-skeleton/dd-skeleton.vue +184 -0
  49. package/components/dd-slider/dd-slider.vue +227 -0
  50. package/components/dd-stat-card/dd-stat-card.vue +174 -0
  51. package/components/dd-step/dd-step.vue +188 -0
  52. package/components/dd-stepper/dd-stepper.vue +171 -0
  53. package/components/dd-steps/dd-steps.vue +61 -0
  54. package/components/dd-sticky/dd-sticky.vue +132 -0
  55. package/components/dd-swipe/dd-swipe.vue +146 -0
  56. package/components/dd-swipe-action/dd-swipe-action.vue +248 -0
  57. package/components/dd-swipe-item/dd-swipe-item.vue +27 -0
  58. package/components/dd-swipeable-tab/dd-swipeable-tab.vue +202 -0
  59. package/components/dd-switch/dd-switch.vue +106 -0
  60. package/components/dd-tabbar/dd-tabbar.vue +112 -0
  61. package/components/dd-tabbar-item/dd-tabbar-item.vue +122 -0
  62. package/components/dd-tag/dd-tag.vue +178 -0
  63. package/components/dd-toast/dd-toast.vue +195 -0
  64. package/components/dd-top-navbar/dd-top-navbar.vue +184 -0
  65. package/components/dd-upload/dd-upload.vue +187 -0
  66. package/index.ts +127 -0
  67. package/libs/utils.ts +106 -0
  68. package/package.json +58 -0
  69. package/scss/_functions.scss +68 -0
  70. package/scss/_mixins.scss +154 -0
  71. package/scss/_reset.scss +30 -0
  72. package/scss/_variables.scss +317 -0
  73. package/uni.scss +4 -0
@@ -0,0 +1,248 @@
1
+ <template>
2
+ <view class="dd-swipe-action" :class="{ 'dd-swipe-action--disabled': disabled }">
3
+ <!-- 左侧操作层 (右滑露出) -->
4
+ <view class="dd-swipe-action__actions dd-swipe-action__actions--left" :style="{ width: leftWidthPx + 'px' }">
5
+ <view
6
+ v-for="(action, i) in leftActions"
7
+ :key="'l' + i"
8
+ class="dd-swipe-action__btn"
9
+ :style="{ width: actionWidthPx + 'px', background: action.color || defaultActionColor }"
10
+ @click="onActionClick('left', i, action)"
11
+ >
12
+ <dd-icon v-if="action.icon" :name="action.icon" class="dd-swipe-action__icon" />
13
+ <text class="dd-swipe-action__label">{{ action.text }}</text>
14
+ </view>
15
+ </view>
16
+ <!-- 右侧操作层 (左滑露出) -->
17
+ <view class="dd-swipe-action__actions dd-swipe-action__actions--right" :style="{ width: rightWidthPx + 'px' }">
18
+ <view
19
+ v-for="(action, i) in rightActions"
20
+ :key="'r' + i"
21
+ class="dd-swipe-action__btn"
22
+ :style="{ width: actionWidthPx + 'px', background: action.color || defaultActionColor }"
23
+ @click="onActionClick('right', i, action)"
24
+ >
25
+ <dd-icon v-if="action.icon" :name="action.icon" class="dd-swipe-action__icon" />
26
+ <text class="dd-swipe-action__label">{{ action.text }}</text>
27
+ </view>
28
+ </view>
29
+ <!-- 内容层 (前置,可平移) -->
30
+ <view
31
+ class="dd-swipe-action__content"
32
+ :class="{ 'dd-swipe-action__content--anim': animating }"
33
+ :style="{ transform: `translateX(${translateX}px)` }"
34
+ @touchstart="onTouchStart"
35
+ @touchmove.stop.prevent="onTouchMove"
36
+ @touchend="onTouchEnd"
37
+ @touchcancel="onTouchEnd"
38
+ >
39
+ <slot></slot>
40
+ </view>
41
+ </view>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { computed, ref } from 'vue'
46
+ import DdIcon from '../dd-icon/dd-icon.vue'
47
+
48
+ interface SwipeAction {
49
+ text?: string
50
+ icon?: string
51
+ color?: string
52
+ }
53
+
54
+ interface Props {
55
+ leftActions?: SwipeAction[]
56
+ rightActions?: SwipeAction[]
57
+ disabled?: boolean
58
+ actionWidth?: number // rpx
59
+ threshold?: number
60
+ damping?: number
61
+ }
62
+
63
+ const props = withDefaults(defineProps<Props>(), {
64
+ leftActions: () => [],
65
+ rightActions: () => [],
66
+ disabled: false,
67
+ actionWidth: 144,
68
+ threshold: 0.33,
69
+ damping: 0.3,
70
+ })
71
+
72
+ const emit = defineEmits<{
73
+ (e: 'click', payload: { side: 'left' | 'right'; index: number; action: SwipeAction }): void
74
+ (e: 'open', side: 'left' | 'right'): void
75
+ (e: 'close'): void
76
+ }>()
77
+
78
+ // 默认操作色用 error 红 ($dd-error-500)
79
+ const defaultActionColor = '#E53935'
80
+
81
+ const translateX = ref(0)
82
+ const animating = ref(false)
83
+
84
+ let startX = 0
85
+ let startTranslate = 0
86
+ let dragging = false
87
+ let moved = false
88
+
89
+ const actionWidthPx = computed(() => rpxToPx(props.actionWidth))
90
+ const leftWidthPx = computed(() => actionWidthPx.value * props.leftActions.length)
91
+ const rightWidthPx = computed(() => actionWidthPx.value * props.rightActions.length)
92
+
93
+ function rpxToPx(rpx: number): number {
94
+ try {
95
+ const info = uni.getWindowInfo()
96
+ return (rpx / 750) * info.windowWidth
97
+ } catch {
98
+ return rpx / 2
99
+ }
100
+ }
101
+
102
+ function onTouchStart(e: any) {
103
+ if (props.disabled) return
104
+ dragging = true
105
+ moved = false
106
+ animating.value = false
107
+ startX = e.touches?.[0]?.clientX ?? 0
108
+ startTranslate = translateX.value
109
+ }
110
+
111
+ function onTouchMove(e: any) {
112
+ if (!dragging || props.disabled) return
113
+ const x = e.touches?.[0]?.clientX ?? 0
114
+ let delta = x - startX
115
+ if (Math.abs(delta) > 4) moved = true
116
+ let next = startTranslate + delta
117
+ // 边界阻尼:超出左/右操作宽度时按 (1-damping) 衰减
118
+ if (next > leftWidthPx.value) {
119
+ next = leftWidthPx.value + (next - leftWidthPx.value) * (1 - props.damping)
120
+ }
121
+ if (next < -rightWidthPx.value) {
122
+ next = -rightWidthPx.value + (next + rightWidthPx.value) * (1 - props.damping)
123
+ }
124
+ translateX.value = next
125
+ }
126
+
127
+ function onTouchEnd() {
128
+ if (!dragging) return
129
+ dragging = false
130
+ animating.value = true
131
+ const cur = translateX.value
132
+ // 轻点内容关闭已展开操作
133
+ if (!moved) {
134
+ if (cur !== 0) {
135
+ translateX.value = 0
136
+ emit('close')
137
+ }
138
+ return
139
+ }
140
+ // 0.33 阈值吸附
141
+ const leftOpen = cur > leftWidthPx.value * props.threshold
142
+ const rightOpen = cur < -rightWidthPx.value * props.threshold
143
+ if (leftOpen && leftWidthPx.value > 0) {
144
+ translateX.value = leftWidthPx.value
145
+ emit('open', 'left')
146
+ } else if (rightOpen && rightWidthPx.value > 0) {
147
+ translateX.value = -rightWidthPx.value
148
+ emit('open', 'right')
149
+ } else {
150
+ translateX.value = 0
151
+ emit('close')
152
+ }
153
+ }
154
+
155
+ function onActionClick(side: 'left' | 'right', index: number, action: SwipeAction) {
156
+ emit('click', { side, index, action })
157
+ animating.value = true
158
+ translateX.value = 0
159
+ emit('close')
160
+ }
161
+
162
+ function close() {
163
+ animating.value = true
164
+ translateX.value = 0
165
+ }
166
+
167
+ defineExpose({ close })
168
+ </script>
169
+
170
+ <style lang="scss" scoped>
171
+ @import '../../scss/variables';
172
+ @import '../../scss/mixins';
173
+
174
+ .dd-swipe-action {
175
+ position: relative;
176
+ overflow: hidden;
177
+ background: $dd-bg-elevated;
178
+
179
+ &--disabled {
180
+ .dd-swipe-action__content {
181
+ pointer-events: auto;
182
+ }
183
+ }
184
+
185
+ &__actions {
186
+ position: absolute;
187
+ top: 0;
188
+ bottom: 0;
189
+ display: flex;
190
+ z-index: 0;
191
+
192
+ &--left {
193
+ left: 0;
194
+ .dd-swipe-action__btn {
195
+ @include dd-flex-center;
196
+ flex-direction: column;
197
+ }
198
+ }
199
+ &--right {
200
+ right: 0;
201
+ flex-direction: row-reverse;
202
+ .dd-swipe-action__btn {
203
+ @include dd-flex-center;
204
+ flex-direction: column;
205
+ }
206
+ }
207
+ }
208
+
209
+ &__btn {
210
+ height: 100%;
211
+ box-sizing: border-box;
212
+ padding: 0 12rpx;
213
+ color: $dd-color-white;
214
+ @include dd-flex-center;
215
+ flex-direction: column;
216
+ gap: 8rpx;
217
+ overflow: hidden;
218
+ }
219
+
220
+ &__icon {
221
+ font-size: $dd-font-size-h3;
222
+ line-height: 1;
223
+ }
224
+
225
+ &__label {
226
+ font-size: $dd-font-size-body;
227
+ font-weight: 500;
228
+ line-height: 1.2;
229
+ white-space: nowrap;
230
+ overflow: hidden;
231
+ text-overflow: ellipsis;
232
+ max-width: 100%;
233
+ }
234
+
235
+ &__content {
236
+ position: relative;
237
+ z-index: 1;
238
+ width: 100%;
239
+ background: $dd-bg-elevated;
240
+ transform: translateX(0);
241
+
242
+ &--anim {
243
+ // cubic-bezier 来自 JSON traits: 0.25, 0.46, 0.45, 0.94
244
+ transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
245
+ }
246
+ }
247
+ }
248
+ </style>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <swiper-item class="dd-swipe-item">
3
+ <slot></slot>
4
+ </swiper-item>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { inject, onMounted, onUnmounted } from 'vue'
9
+
10
+ // inject 父级 dd-swipe 的注册器;不在 dd-swipe 内时为 null,静默忽略
11
+ const swipe = inject<{ register: () => void; unregister: () => void } | null>('ddSwipe', null)
12
+
13
+ onMounted(() => {
14
+ swipe?.register()
15
+ })
16
+
17
+ onUnmounted(() => {
18
+ swipe?.unregister()
19
+ })
20
+ </script>
21
+
22
+ <style lang="scss" scoped>
23
+ .dd-swipe-item {
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+ </style>
@@ -0,0 +1,202 @@
1
+ <template>
2
+ <view class="dd-swipe">
3
+ <view
4
+ v-if="mode === 'fixed'"
5
+ ref="trackRef"
6
+ class="dd-swipe__track dd-swipe__track--fixed"
7
+ >
8
+ <view
9
+ v-for="(opt, index) in options"
10
+ :key="index"
11
+ class="dd-swipe__item"
12
+ :class="{ 'dd-swipe__item--active': index === modelValue }"
13
+ @click="onSelect(index)"
14
+ >
15
+ <text class="dd-swipe__label">{{ labelOf(opt) }}</text>
16
+ </view>
17
+ <view class="dd-swipe__indicator" :style="indicatorStyle"></view>
18
+ </view>
19
+
20
+ <scroll-view
21
+ v-if="mode === 'scrollable'"
22
+ scroll-x
23
+ class="dd-swipe__scroll"
24
+ :scroll-left="scrollLeft"
25
+ :scroll-with-animation="true"
26
+ >
27
+ <view ref="trackRef" class="dd-swipe__track dd-swipe__track--scroll">
28
+ <view
29
+ v-for="(opt, index) in options"
30
+ :key="index"
31
+ class="dd-swipe__item dd-swipe__item--scroll"
32
+ :class="{ 'dd-swipe__item--active': index === modelValue }"
33
+ @click="onSelect(index)"
34
+ >
35
+ <text class="dd-swipe__label">{{ labelOf(opt) }}</text>
36
+ </view>
37
+ <view class="dd-swipe__indicator" :style="indicatorStyle"></view>
38
+ </view>
39
+ </scroll-view>
40
+ <view class="dd-swipe__divider"></view>
41
+ </view>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { nextTick, onMounted, ref, watch } from 'vue'
46
+ import { getCurrentInstance } from 'vue'
47
+
48
+ interface Props {
49
+ modelValue?: number
50
+ options?: (string | { label: string })[]
51
+ mode?: 'fixed' | 'scrollable'
52
+ lineWidth?: number
53
+ }
54
+
55
+ const props = withDefaults(defineProps<Props>(), {
56
+ modelValue: 0,
57
+ options: () => [],
58
+ mode: 'fixed',
59
+ lineWidth: 0,
60
+ })
61
+
62
+ const emit = defineEmits<{
63
+ (e: 'update:modelValue', val: number): void
64
+ (e: 'change', val: number): void
65
+ }>()
66
+
67
+ const instance = getCurrentInstance()
68
+ const indicatorStyle = ref({ width: '0px', transform: 'translateX(0px)' })
69
+ const scrollLeft = ref(0)
70
+
71
+ function labelOf(opt: string | { label: string }): string {
72
+ return typeof opt === 'string' ? opt : opt.label
73
+ }
74
+
75
+ function onSelect(index: number) {
76
+ if (index === props.modelValue) return
77
+ emit('update:modelValue', index)
78
+ emit('change', index)
79
+ measure()
80
+ }
81
+
82
+ function rpxToPx(rpx: number): number {
83
+ const w = uni.getWindowInfo?.().windowWidth || 375
84
+ return (rpx * w) / 750
85
+ }
86
+
87
+ function measure() {
88
+ nextTick(() => {
89
+ const query = uni.createSelectorQuery().in(instance)
90
+ const itemSel =
91
+ props.mode === 'scrollable' ? '.dd-swipe__item--scroll' : '.dd-swipe__item'
92
+ query
93
+ .selectAll(itemSel)
94
+ .boundingClientRect()
95
+ .select('.dd-swipe__track')
96
+ .boundingClientRect()
97
+ .exec((res) => {
98
+ const items = (res[0] || []) as UniApp.NodeInfo[]
99
+ const track = res[1] as UniApp.NodeInfo
100
+ if (!items.length || !track) return
101
+ const target = items[props.modelValue]
102
+ if (!target) return
103
+ const itemLeft = (target.left || 0) - (track.left || 0)
104
+ const itemWidth = target.width || 0
105
+ const indWidth = props.lineWidth
106
+ ? rpxToPx(props.lineWidth)
107
+ : itemWidth * 0.6
108
+ const indLeft = itemLeft + (itemWidth - indWidth) / 2
109
+ indicatorStyle.value = {
110
+ width: indWidth + 'px',
111
+ transform: `translateX(${indLeft}px)`,
112
+ }
113
+ if (props.mode === 'scrollable') {
114
+ const viewW = track.width || 0
115
+ scrollLeft.value = Math.max(0, itemLeft + itemWidth / 2 - viewW / 2)
116
+ }
117
+ })
118
+ })
119
+ }
120
+
121
+ onMounted(measure)
122
+ watch(() => props.modelValue, measure)
123
+ watch(() => props.options, measure)
124
+ </script>
125
+
126
+ <style lang="scss" scoped>
127
+ @import '../../scss/variables';
128
+ @import '../../scss/mixins';
129
+
130
+ .dd-swipe {
131
+ width: 100%;
132
+ }
133
+
134
+ .dd-swipe__track {
135
+ position: relative;
136
+ height: 88rpx;
137
+ display: flex;
138
+ flex-direction: row;
139
+ align-items: center;
140
+ }
141
+
142
+ .dd-swipe__track--fixed {
143
+ width: 100%;
144
+ .dd-swipe__item {
145
+ flex: 1;
146
+ }
147
+ }
148
+
149
+ .dd-swipe__track--scroll {
150
+ display: inline-flex;
151
+ .dd-swipe__item--scroll {
152
+ flex-shrink: 0;
153
+ padding: 0 $dd-space-4;
154
+ }
155
+ }
156
+
157
+ .dd-swipe__scroll {
158
+ width: 100%;
159
+ white-space: nowrap;
160
+ }
161
+
162
+ .dd-swipe__item {
163
+ @include dd-flex-center;
164
+ height: 100%;
165
+ padding: 0 $dd-space-4;
166
+ color: $dd-text-tertiary;
167
+ font-size: $dd-font-size-body;
168
+ font-weight: 500;
169
+ box-sizing: border-box;
170
+ @include dd-transition(color 0.3s);
171
+
172
+ &--active {
173
+ color: $dd-primary-400;
174
+ font-weight: 600;
175
+ .dd-swipe__label {
176
+ text-shadow: 0 0 12px rgba(245, 166, 35, 0.5);
177
+ }
178
+ }
179
+ }
180
+
181
+ .dd-swipe__label {
182
+ line-height: 1.2;
183
+ @include dd-ellipsis(1);
184
+ }
185
+
186
+ .dd-swipe__indicator {
187
+ position: absolute;
188
+ bottom: 0;
189
+ left: 0;
190
+ height: 4rpx;
191
+ background: $dd-gradient-primary-h;
192
+ border-radius: $dd-radius-full;
193
+ box-shadow: $dd-shadow-glow-gold-sm;
194
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
195
+ width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
196
+ }
197
+
198
+ .dd-swipe__divider {
199
+ height: 1px;
200
+ background: $dd-neutral-800;
201
+ }
202
+ </style>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <view
3
+ class="dd-switch"
4
+ :class="[
5
+ `dd-switch--${size}`,
6
+ {
7
+ 'dd-switch--on': modelValue,
8
+ 'dd-switch--disabled': disabled,
9
+ },
10
+ ]"
11
+ @click="onToggle"
12
+ >
13
+ <view class="dd-switch__thumb"></view>
14
+ </view>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ interface Props {
19
+ modelValue?: boolean
20
+ size?: 'md' | 'sm'
21
+ disabled?: boolean
22
+ }
23
+
24
+ const props = withDefaults(defineProps<Props>(), {
25
+ modelValue: false,
26
+ size: 'md',
27
+ disabled: false,
28
+ })
29
+
30
+ const emit = defineEmits<{
31
+ (e: 'update:modelValue', val: boolean): void
32
+ (e: 'change', val: boolean): void
33
+ }>()
34
+
35
+ function onToggle() {
36
+ if (props.disabled) return
37
+ const next = !props.modelValue
38
+ emit('update:modelValue', next)
39
+ emit('change', next)
40
+ }
41
+ </script>
42
+
43
+ <style lang="scss" scoped>
44
+ @import '../../scss/variables';
45
+ @import '../../scss/mixins';
46
+
47
+ .dd-switch {
48
+ position: relative;
49
+ flex-shrink: 0;
50
+ border-radius: $dd-radius-full;
51
+ box-sizing: border-box;
52
+ @include dd-transition(background 0.3s, box-shadow 0.3s, border-color 0.3s);
53
+
54
+ &__thumb {
55
+ position: absolute;
56
+ top: 50%;
57
+ left: 6rpx;
58
+ transform: translateY(-50%);
59
+ background: #ffffff;
60
+ border-radius: 50%;
61
+ box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.3);
62
+ @include dd-transition(left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1));
63
+ }
64
+
65
+ // off
66
+ background: $dd-neutral-800;
67
+ border: 1px solid $dd-glass-border;
68
+ box-shadow: inset 0 2rpx 6rpx rgba(0, 0, 0, 0.3);
69
+
70
+ // on
71
+ &--on {
72
+ background: $dd-gradient-primary;
73
+ border-color: rgba($dd-primary-300, 0.5);
74
+ box-shadow: $dd-shadow-glow-gold-lg, inset 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
75
+ }
76
+
77
+ &--disabled {
78
+ opacity: 0.4;
79
+ @include dd-no-touch;
80
+ }
81
+ }
82
+
83
+ .dd-switch--md {
84
+ width: 96rpx;
85
+ height: 56rpx;
86
+ .dd-switch__thumb {
87
+ width: 44rpx;
88
+ height: 44rpx;
89
+ }
90
+ &.dd-switch--on .dd-switch__thumb {
91
+ left: calc(100% - 50rpx);
92
+ }
93
+ }
94
+
95
+ .dd-switch--sm {
96
+ width: 76rpx;
97
+ height: 44rpx;
98
+ .dd-switch__thumb {
99
+ width: 32rpx;
100
+ height: 32rpx;
101
+ }
102
+ &.dd-switch--on .dd-switch__thumb {
103
+ left: calc(100% - 38rpx);
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <view
3
+ v-if="placeholder && fixed"
4
+ class="dd-tabbar__placeholder"
5
+ :class="{ 'dd-tabbar__placeholder--safe': safeAreaInsetBottom }"
6
+ ></view>
7
+ <view
8
+ class="dd-tabbar"
9
+ :class="{
10
+ 'dd-tabbar--fixed': fixed,
11
+ 'dd-tabbar--border': border,
12
+ 'dd-tabbar--safe': safeAreaInsetBottom,
13
+ }"
14
+ >
15
+ <slot></slot>
16
+ </view>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { computed, provide, ref } from 'vue'
21
+
22
+ interface Props {
23
+ modelValue?: string | number
24
+ fixed?: boolean
25
+ border?: boolean
26
+ placeholder?: boolean
27
+ safeAreaInsetBottom?: boolean
28
+ activeColor?: string
29
+ inactiveColor?: string
30
+ }
31
+
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ modelValue: 0,
34
+ fixed: true,
35
+ border: true,
36
+ placeholder: true,
37
+ safeAreaInsetBottom: true,
38
+ activeColor: '#F5A623',
39
+ inactiveColor: '#9E9E9E',
40
+ })
41
+
42
+ const emit = defineEmits<{
43
+ (e: 'update:modelValue', val: string | number): void
44
+ (e: 'change', val: string | number): void
45
+ }>()
46
+
47
+ const counter = ref(0)
48
+
49
+ function register(): number {
50
+ const idx = counter.value
51
+ counter.value += 1
52
+ return idx
53
+ }
54
+
55
+ function onItemClick(val: string | number) {
56
+ if (val === props.modelValue) return
57
+ emit('update:modelValue', val)
58
+ emit('change', val)
59
+ }
60
+
61
+ const active = computed(() => props.modelValue)
62
+
63
+ provide('ddTabbar', {
64
+ active,
65
+ activeColor: computed(() => props.activeColor),
66
+ inactiveColor: computed(() => props.inactiveColor),
67
+ register,
68
+ onItemClick,
69
+ })
70
+ </script>
71
+
72
+ <style lang="scss" scoped>
73
+ @import '../../scss/variables';
74
+ @import '../../scss/mixins';
75
+
76
+ .dd-tabbar {
77
+ display: flex;
78
+ flex-direction: row;
79
+ align-items: center;
80
+ width: 100%;
81
+ height: 100rpx;
82
+ background: $dd-surface;
83
+ box-sizing: border-box;
84
+
85
+ &--border {
86
+ @include dd-hairline-top($dd-border-default);
87
+ }
88
+
89
+ &--safe {
90
+ box-sizing: content-box;
91
+ @include dd-safe-area-bottom;
92
+ }
93
+
94
+ &--fixed {
95
+ position: fixed;
96
+ left: 0;
97
+ right: 0;
98
+ bottom: 0;
99
+ z-index: $dd-z-index-sticky;
100
+ }
101
+ }
102
+
103
+ .dd-tabbar__placeholder {
104
+ width: 100%;
105
+ height: 100rpx;
106
+ box-sizing: content-box;
107
+
108
+ &--safe {
109
+ @include dd-safe-area-bottom;
110
+ }
111
+ }
112
+ </style>