@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,309 @@
1
+ <template>
2
+ <view
3
+ class="dd-room-card"
4
+ :class="[`dd-room-card--${view}`, `dd-room-card--${status}`, `dd-room-card--${type}`]"
5
+ :hover-class="hoverable ? 'dd-room-card--press' : ''"
6
+ :hover-stay-time="100"
7
+ @click="onClick"
8
+ >
9
+ <!-- grid 模式: 方形卡 + 顶部状态栏 -->
10
+ <template v-if="view === 'grid'">
11
+ <view class="dd-room-card__topbar"></view>
12
+ <view v-if="type === 'vip'" class="dd-room-card__vip-tag"><text>VIP</text></view>
13
+ <view class="dd-room-card__grid-inner">
14
+ <text class="dd-room-card__no">{{ roomNo }}</text>
15
+ <view class="dd-room-card__status">
16
+ <view class="dd-room-card__dot"></view>
17
+ <text class="dd-room-card__status-text">{{ statusText }}</text>
18
+ </view>
19
+ <text v-if="capacity" class="dd-room-card__capacity">容纳{{ capacity }}人</text>
20
+ </view>
21
+ </template>
22
+
23
+ <!-- list 模式: 头像 + 信息 + 右侧 -->
24
+ <template v-else>
25
+ <view class="dd-room-card__avatar">
26
+ <text class="dd-room-card__avatar-text">{{ avatarText }}</text>
27
+ </view>
28
+ <view class="dd-room-card__list-info">
29
+ <view class="dd-room-card__list-head">
30
+ <text class="dd-room-card__name">{{ roomName || roomNo }}</text>
31
+ <view class="dd-room-card__status dd-room-card__status--inline">
32
+ <view class="dd-room-card__dot"></view>
33
+ <text class="dd-room-card__status-text">{{ statusText }}</text>
34
+ </view>
35
+ </view>
36
+ <text v-if="capacity" class="dd-room-card__capacity dd-room-card__capacity--inline">容纳{{ capacity }}人</text>
37
+ </view>
38
+ <view class="dd-room-card__list-right">
39
+ <text v-if="time" class="dd-room-card__time">{{ time }}</text>
40
+ </view>
41
+ </template>
42
+ </view>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { computed } from 'vue'
47
+
48
+ interface Props {
49
+ view?: 'grid' | 'list'
50
+ status?: 'occupied' | 'idle' | 'reserved'
51
+ type?: 'vip' | 'standard' | 'theme'
52
+ roomNo?: string
53
+ roomName?: string
54
+ capacity?: number | string
55
+ time?: string
56
+ hoverable?: boolean
57
+ }
58
+
59
+ const props = withDefaults(defineProps<Props>(), {
60
+ view: 'grid',
61
+ status: 'idle',
62
+ type: 'standard',
63
+ roomNo: '',
64
+ roomName: '',
65
+ capacity: '',
66
+ time: '',
67
+ hoverable: true,
68
+ })
69
+
70
+ const emit = defineEmits<{ (e: 'click', val: { roomNo: string; status: string }): void }>()
71
+
72
+ const statusMap: Record<string, string> = {
73
+ occupied: '使用中',
74
+ idle: '空闲',
75
+ reserved: '已预订',
76
+ }
77
+
78
+ const statusText = computed(() => statusMap[props.status] || '空闲')
79
+
80
+ // 头像文字: 房间号末尾数字或首字
81
+ const avatarText = computed(() => {
82
+ const no = props.roomNo || props.roomName || ''
83
+ const m = no.match(/\d+$/)
84
+ return m ? m[0].slice(-2) : no.slice(0, 2)
85
+ })
86
+
87
+ function onClick() {
88
+ emit('click', { roomNo: props.roomNo, status: props.status })
89
+ }
90
+ </script>
91
+
92
+ <style lang="scss" scoped>
93
+ @import '../../scss/variables';
94
+ @import '../../scss/mixins';
95
+
96
+ .dd-room-card {
97
+ box-sizing: border-box;
98
+ background: $dd-bg-elevated;
99
+ @include dd-transition(transform 0.2s ease);
100
+ }
101
+
102
+ // === GRID ===
103
+ .dd-room-card--grid {
104
+ position: relative;
105
+ border: 1px solid $dd-border-default;
106
+ border-radius: $dd-radius-lg;
107
+ overflow: hidden;
108
+ aspect-ratio: 1 / 1;
109
+ display: flex;
110
+ flex-direction: column;
111
+ }
112
+
113
+ .dd-room-card__topbar {
114
+ height: 12rpx;
115
+ width: 100%;
116
+ background: $dd-surface-container-highest;
117
+ flex-shrink: 0;
118
+ }
119
+
120
+ .dd-room-card__grid-inner {
121
+ flex: 1;
122
+ display: flex;
123
+ flex-direction: column;
124
+ align-items: center;
125
+ justify-content: center;
126
+ gap: $dd-space-2;
127
+ padding: $dd-space-2;
128
+ }
129
+
130
+ .dd-room-card__no {
131
+ font-size: $dd-font-size-h1;
132
+ font-weight: $dd-font-weight-h1;
133
+ color: $dd-text-primary;
134
+ line-height: 1.2;
135
+ }
136
+
137
+ .dd-room-card__status {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 8rpx;
141
+
142
+ &--inline {
143
+ margin-left: $dd-space-2;
144
+ }
145
+ }
146
+
147
+ .dd-room-card__dot {
148
+ width: 12rpx;
149
+ height: 12rpx;
150
+ border-radius: 50%;
151
+ background: $dd-neutral-500;
152
+ }
153
+
154
+ .dd-room-card__status-text {
155
+ font-size: $dd-font-size-caption;
156
+ color: $dd-text-tertiary;
157
+ }
158
+
159
+ .dd-room-card__capacity {
160
+ font-size: $dd-font-size-caption;
161
+ color: $dd-text-tertiary;
162
+
163
+ &--inline {
164
+ font-size: $dd-font-size-caption;
165
+ }
166
+ }
167
+
168
+ .dd-room-card__vip-tag {
169
+ position: absolute;
170
+ top: 12rpx;
171
+ right: 12rpx;
172
+ z-index: 2;
173
+ padding: 2rpx 12rpx;
174
+ background: $dd-gradient-primary;
175
+ border-radius: $dd-radius-sm;
176
+ text {
177
+ font-size: 20rpx;
178
+ font-weight: 700;
179
+ color: $dd-neutral-900;
180
+ line-height: 1.6;
181
+ }
182
+ }
183
+
184
+ // === 顶部栏/状态色: occupied=gold, reserved=accent, idle=neutral ===
185
+ .dd-room-card--occupied.dd-room-card--grid {
186
+ .dd-room-card__topbar {
187
+ background: $dd-gradient-primary-h;
188
+ }
189
+ .dd-room-card__dot {
190
+ background: $dd-primary-400;
191
+ box-shadow: 0 0 12rpx rgba(245, 166, 35, 0.6);
192
+ }
193
+ .dd-room-card__status-text {
194
+ color: $dd-primary-400;
195
+ }
196
+ }
197
+
198
+ .dd-room-card--reserved.dd-room-card--grid {
199
+ .dd-room-card__topbar {
200
+ background: $dd-gradient-accent-h;
201
+ }
202
+ .dd-room-card__dot {
203
+ background: $dd-accent-400;
204
+ box-shadow: 0 0 12rpx rgba(45, 75, 160, 0.6);
205
+ }
206
+ .dd-room-card__status-text {
207
+ color: $dd-accent-300;
208
+ }
209
+ }
210
+
211
+ .dd-room-card--idle.dd-room-card--grid {
212
+ .dd-room-card__topbar {
213
+ background: $dd-surface-container-highest;
214
+ }
215
+ }
216
+
217
+ // === LIST ===
218
+ .dd-room-card--list {
219
+ display: flex;
220
+ flex-direction: row;
221
+ align-items: center;
222
+ gap: $dd-space-3;
223
+ padding: $dd-space-3 $dd-space-4;
224
+ border: 1px solid $dd-border-default;
225
+ border-radius: $dd-radius-md;
226
+ }
227
+
228
+ .dd-room-card__avatar {
229
+ flex-shrink: 0;
230
+ width: 96rpx;
231
+ height: 96rpx;
232
+ border-radius: 50%;
233
+ display: flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ background: $dd-gradient-accent;
237
+
238
+ &-text {
239
+ font-size: $dd-font-size-body;
240
+ font-weight: 700;
241
+ color: $dd-color-white;
242
+ }
243
+ }
244
+
245
+ .dd-room-card--vip .dd-room-card__avatar {
246
+ background: $dd-gradient-primary;
247
+ }
248
+
249
+ .dd-room-card--theme .dd-room-card__avatar {
250
+ background: $dd-gradient-accent;
251
+ }
252
+
253
+ .dd-room-card__list-info {
254
+ flex: 1;
255
+ min-width: 0;
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 4rpx;
259
+ }
260
+
261
+ .dd-room-card__list-head {
262
+ display: flex;
263
+ align-items: center;
264
+ }
265
+
266
+ .dd-room-card__name {
267
+ font-size: $dd-font-size-body;
268
+ font-weight: 600;
269
+ color: $dd-text-primary;
270
+ @include dd-ellipsis(1);
271
+ }
272
+
273
+ .dd-room-card__list-right {
274
+ flex-shrink: 0;
275
+ display: flex;
276
+ align-items: center;
277
+ }
278
+
279
+ .dd-room-card__time {
280
+ font-size: $dd-font-size-caption;
281
+ color: $dd-text-secondary;
282
+ }
283
+
284
+ // list 状态色
285
+ .dd-room-card--occupied.dd-room-card--list {
286
+ .dd-room-card__dot {
287
+ background: $dd-primary-400;
288
+ box-shadow: 0 0 12rpx rgba(245, 166, 35, 0.6);
289
+ }
290
+ .dd-room-card__status-text {
291
+ color: $dd-primary-400;
292
+ }
293
+ }
294
+
295
+ .dd-room-card--reserved.dd-room-card--list {
296
+ .dd-room-card__dot {
297
+ background: $dd-accent-400;
298
+ box-shadow: 0 0 12rpx rgba(45, 75, 160, 0.6);
299
+ }
300
+ .dd-room-card__status-text {
301
+ color: $dd-accent-300;
302
+ }
303
+ }
304
+
305
+ .dd-room-card--press {
306
+ transform: scale(0.98);
307
+ opacity: 0.92;
308
+ }
309
+ </style>
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <view class="dd-search" :class="{ 'dd-search--disabled': disabled }">
3
+ <view
4
+ class="dd-search__wrap"
5
+ :class="{ 'dd-search__wrap--focus': isFocused, 'dd-search__wrap--square': !round }"
6
+ >
7
+ <dd-icon name="search" class="dd-search__icon" :class="{ 'dd-search__icon--focus': isFocused }" />
8
+ <input
9
+ class="dd-search__inner"
10
+ type="text"
11
+ :value="modelValue"
12
+ :placeholder="placeholder"
13
+ placeholder-style="color:#9E9E9E"
14
+ :disabled="disabled"
15
+ :focus="focusFlag"
16
+ confirm-type="search"
17
+ @input="onInput"
18
+ @focus="onFocus"
19
+ @blur="onBlur"
20
+ @confirm="onConfirm"
21
+ />
22
+ <view v-if="showClear" class="dd-search__clear" @click="onClear"><dd-icon name="cross" /></view>
23
+ </view>
24
+ <text v-if="showCancel" class="dd-search__cancel" @click="onCancel">取消</text>
25
+ </view>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import { computed, nextTick, onMounted, ref } from 'vue'
30
+ import DdIcon from '../dd-icon/dd-icon.vue'
31
+
32
+ interface Props {
33
+ modelValue?: string
34
+ placeholder?: string
35
+ round?: boolean
36
+ showCancel?: boolean
37
+ autofocus?: boolean
38
+ disabled?: boolean
39
+ }
40
+
41
+ const props = withDefaults(defineProps<Props>(), {
42
+ modelValue: '',
43
+ placeholder: '搜索歌曲、歌手',
44
+ round: true,
45
+ showCancel: false,
46
+ autofocus: false,
47
+ disabled: false,
48
+ })
49
+
50
+ const emit = defineEmits<{
51
+ (e: 'update:modelValue', val: string): void
52
+ (e: 'focus', val: Event): void
53
+ (e: 'blur', val: Event): void
54
+ (e: 'clear'): void
55
+ (e: 'search', val: string): void
56
+ (e: 'cancel'): void
57
+ }>()
58
+
59
+ const isFocused = ref(false)
60
+ const focusFlag = ref(false)
61
+ const showClear = computed(() => !!props.modelValue && !props.disabled)
62
+
63
+ onMounted(() => {
64
+ if (props.autofocus) focusFlag.value = true
65
+ })
66
+
67
+ function onInput(e: any) {
68
+ emit('update:modelValue', e?.detail?.value ?? e?.target?.value ?? '')
69
+ }
70
+ function onFocus(e: Event) {
71
+ isFocused.value = true
72
+ emit('focus', e)
73
+ }
74
+ function onBlur(e: Event) {
75
+ isFocused.value = false
76
+ emit('blur', e)
77
+ }
78
+ function onConfirm(e: any) {
79
+ const v = e?.detail?.value ?? e?.target?.value ?? ''
80
+ emit('search', v)
81
+ }
82
+ function onClear() {
83
+ emit('update:modelValue', '')
84
+ emit('clear')
85
+ // ponytail: 清除后通过 :focus 标志位翻转重新聚焦,低端机时序可靠性未知
86
+ focusFlag.value = false
87
+ nextTick(() => {
88
+ focusFlag.value = true
89
+ })
90
+ }
91
+ function onCancel() {
92
+ emit('cancel')
93
+ }
94
+ </script>
95
+
96
+ <style lang="scss" scoped>
97
+ @import '../../scss/variables';
98
+ @import '../../scss/mixins';
99
+
100
+ .dd-search {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 16rpx;
104
+
105
+ &__wrap {
106
+ flex: 1;
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 16rpx;
110
+ height: 88rpx;
111
+ padding: 0 24rpx;
112
+ background: $dd-neutral-800;
113
+ border: 1px solid $dd-neutral-700;
114
+ border-radius: $dd-radius-full;
115
+ box-sizing: border-box;
116
+ @include dd-transition(border-color 0.2s, box-shadow 0.2s);
117
+
118
+ &--square {
119
+ border-radius: $dd-radius-md;
120
+ }
121
+
122
+ &--focus {
123
+ border-color: $dd-primary-500;
124
+ box-shadow: $dd-shadow-glow-gold-lg;
125
+ }
126
+ }
127
+
128
+ &__icon {
129
+ font-size: 28rpx;
130
+ color: $dd-neutral-400;
131
+ line-height: 1;
132
+ @include dd-transition(color 0.2s);
133
+
134
+ &--focus {
135
+ color: $dd-primary-400;
136
+ }
137
+ }
138
+
139
+ &__inner {
140
+ flex: 1;
141
+ min-width: 0;
142
+ height: 100%;
143
+ font-size: 28rpx;
144
+ color: $dd-neutral-100;
145
+ background: transparent;
146
+ border: none;
147
+ }
148
+
149
+ &__clear {
150
+ @include dd-flex-center;
151
+ width: 36rpx;
152
+ height: 36rpx;
153
+ border-radius: 50%;
154
+ background: $dd-neutral-800;
155
+ color: $dd-neutral-300;
156
+ font-size: 24rpx;
157
+ line-height: 1;
158
+ }
159
+
160
+ &__cancel {
161
+ flex-shrink: 0;
162
+ padding: 0 8rpx;
163
+ font-size: 28rpx;
164
+ font-weight: 500;
165
+ color: $dd-primary-400;
166
+ }
167
+
168
+ &--disabled {
169
+ opacity: 0.4;
170
+ @include dd-no-touch;
171
+ .dd-search__icon {
172
+ color: $dd-neutral-600;
173
+ }
174
+ }
175
+ }
176
+ </style>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <view
3
+ v-if="variant === 'pill'"
4
+ class="dd-seg dd-seg--pill"
5
+ >
6
+ <view
7
+ ref="trackRef"
8
+ class="dd-seg__track"
9
+ >
10
+ <view
11
+ v-for="(opt, index) in options"
12
+ :key="index"
13
+ class="dd-seg__item"
14
+ :class="{ 'dd-seg__item--active': index === modelValue }"
15
+ @click="onSelect(index)"
16
+ >
17
+ <text class="dd-seg__label">{{ labelOf(opt) }}</text>
18
+ </view>
19
+ <view
20
+ class="dd-seg__indicator"
21
+ :style="indicatorStyle"
22
+ ></view>
23
+ </view>
24
+ </view>
25
+ <view v-else class="dd-seg dd-seg--text">
26
+ <view
27
+ v-for="(opt, index) in options"
28
+ :key="index"
29
+ class="dd-seg-text__item"
30
+ :class="{ 'dd-seg-text__item--active': index === modelValue }"
31
+ @click="onSelect(index)"
32
+ >
33
+ <text class="dd-seg__label">{{ labelOf(opt) }}</text>
34
+ <view v-if="index === modelValue" class="dd-seg-text__underline"></view>
35
+ </view>
36
+ </view>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { nextTick, onMounted, ref, watch } from 'vue'
41
+ import { getCurrentInstance } from 'vue'
42
+
43
+ interface Props {
44
+ modelValue?: number
45
+ options?: (string | { label: string })[]
46
+ variant?: 'pill' | 'text'
47
+ }
48
+
49
+ const props = withDefaults(defineProps<Props>(), {
50
+ modelValue: 0,
51
+ options: () => [],
52
+ variant: 'pill',
53
+ })
54
+
55
+ const emit = defineEmits<{
56
+ (e: 'update:modelValue', val: number): void
57
+ (e: 'change', val: number): void
58
+ }>()
59
+
60
+ const instance = getCurrentInstance()
61
+ const indicatorStyle = ref({ width: '0px', transform: 'translateX(0px)' })
62
+
63
+ function labelOf(opt: string | { label: string }): string {
64
+ return typeof opt === 'string' ? opt : opt.label
65
+ }
66
+
67
+ function onSelect(index: number) {
68
+ if (index === props.modelValue) return
69
+ emit('update:modelValue', index)
70
+ emit('change', index)
71
+ }
72
+
73
+ function measure() {
74
+ if (props.variant !== 'pill') return
75
+ nextTick(() => {
76
+ const query = uni.createSelectorQuery().in(instance)
77
+ query
78
+ .selectAll('.dd-seg__item')
79
+ .boundingClientRect()
80
+ .select('.dd-seg__track')
81
+ .boundingClientRect()
82
+ .exec((res) => {
83
+ const items = (res[0] || []) as UniApp.NodeInfo[]
84
+ const track = res[1] as UniApp.NodeInfo
85
+ if (!items.length || !track) return
86
+ const target = items[props.modelValue]
87
+ if (!target) return
88
+ const left = (target.left || 0) - (track.left || 0)
89
+ indicatorStyle.value = {
90
+ width: (target.width || 0) + 'px',
91
+ transform: `translateX(${left}px)`,
92
+ }
93
+ })
94
+ })
95
+ }
96
+
97
+ onMounted(measure)
98
+ watch(() => props.modelValue, measure)
99
+ watch(() => props.options, measure)
100
+ </script>
101
+
102
+ <style lang="scss" scoped>
103
+ @import '../../scss/variables';
104
+ @import '../../scss/mixins';
105
+
106
+ .dd-seg {
107
+ font-family: $dd-font-body;
108
+ }
109
+
110
+ // === pill ===
111
+ .dd-seg--pill {
112
+ .dd-seg__track {
113
+ position: relative;
114
+ display: inline-flex;
115
+ padding: 8rpx;
116
+ background: $dd-neutral-800;
117
+ border: 1px solid $dd-border-default;
118
+ border-radius: $dd-radius-full;
119
+ }
120
+
121
+ .dd-seg__item {
122
+ position: relative;
123
+ z-index: 1;
124
+ min-width: 120rpx;
125
+ padding: 12rpx $dd-space-4;
126
+ @include dd-flex-center;
127
+ color: $dd-text-tertiary;
128
+ font-size: $dd-font-size-body;
129
+ font-weight: 500;
130
+ @include dd-transition(color 0.3s);
131
+
132
+ &--active {
133
+ color: $dd-neutral-900;
134
+ font-weight: 600;
135
+ }
136
+ }
137
+
138
+ .dd-seg__indicator {
139
+ position: absolute;
140
+ top: 8rpx;
141
+ bottom: 8rpx;
142
+ left: 0;
143
+ background: $dd-color-white;
144
+ border-radius: $dd-radius-full;
145
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
146
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
147
+ width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
148
+ }
149
+ }
150
+
151
+ // === text ===
152
+ .dd-seg--text {
153
+ display: flex;
154
+ flex-direction: row;
155
+ gap: 64rpx;
156
+
157
+ .dd-seg-text__item {
158
+ position: relative;
159
+ padding: 16rpx 0;
160
+ color: $dd-text-tertiary;
161
+ font-size: $dd-font-size-body;
162
+ font-weight: 500;
163
+ @include dd-flex-center;
164
+ @include dd-transition(color 0.3s);
165
+
166
+ &--active {
167
+ color: $dd-primary-400;
168
+ font-weight: 600;
169
+ }
170
+ }
171
+
172
+ .dd-seg-text__underline {
173
+ position: absolute;
174
+ bottom: 0;
175
+ left: 20%;
176
+ width: 60%;
177
+ height: 4rpx;
178
+ background: $dd-primary-400;
179
+ border-radius: $dd-radius-full;
180
+ }
181
+ }
182
+
183
+ .dd-seg__label {
184
+ line-height: 1.2;
185
+ @include dd-ellipsis(1);
186
+ }
187
+ </style>