@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,168 @@
1
+ <template>
2
+ <view class="dd-avatar" :class="[`dd-avatar--${size}`, `dd-avatar--${shape}`, { 'dd-avatar--vip': vip }]">
3
+ <view class="dd-avatar__inner">
4
+ <image
5
+ v-if="src && !fallback"
6
+ class="dd-avatar__img"
7
+ :src="src"
8
+ mode="aspectFill"
9
+ @error="onImgError"
10
+ />
11
+ <view v-else class="dd-avatar__text-wrap">
12
+ <text class="dd-avatar__text">{{ displayText }}</text>
13
+ </view>
14
+ </view>
15
+ <view v-if="online" class="dd-avatar__online"></view>
16
+ </view>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { computed, ref } from 'vue'
21
+
22
+ interface Props {
23
+ src?: string
24
+ text?: string
25
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
26
+ shape?: 'circle' | 'square'
27
+ vip?: boolean
28
+ online?: boolean
29
+ }
30
+
31
+ const props = withDefaults(defineProps<Props>(), {
32
+ src: '',
33
+ text: '',
34
+ size: 'md',
35
+ shape: 'circle',
36
+ vip: false,
37
+ online: false,
38
+ })
39
+
40
+ const fallback = ref(false)
41
+
42
+ const displayText = computed(() => {
43
+ const t = props.text || ''
44
+ return t ? t.charAt(0).toUpperCase() : ''
45
+ })
46
+
47
+ function onImgError() {
48
+ fallback.value = true
49
+ }
50
+ </script>
51
+
52
+ <style lang="scss" scoped>
53
+ @import '../../scss/variables';
54
+ @import '../../scss/mixins';
55
+
56
+ .dd-avatar {
57
+ position: relative;
58
+ display: inline-flex;
59
+ flex-shrink: 0;
60
+
61
+ &__inner {
62
+ width: 100%;
63
+ height: 100%;
64
+ overflow: hidden;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ box-sizing: border-box;
69
+ background: $dd-gradient-primary;
70
+ }
71
+
72
+ &__img {
73
+ width: 100%;
74
+ height: 100%;
75
+ }
76
+
77
+ &__text-wrap {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ width: 100%;
82
+ height: 100%;
83
+ }
84
+
85
+ &__text {
86
+ font-family: $dd-font-heading;
87
+ font-weight: 600;
88
+ color: $dd-color-white;
89
+ line-height: 1;
90
+ }
91
+
92
+ &__online {
93
+ position: absolute;
94
+ right: 0;
95
+ bottom: 0;
96
+ width: 25%;
97
+ height: 25%;
98
+ min-width: 16rpx;
99
+ min-height: 16rpx;
100
+ max-width: 28rpx;
101
+ max-height: 28rpx;
102
+ border-radius: 50%;
103
+ background: $dd-success-500;
104
+ border: 4rpx solid $dd-bg-elevated;
105
+ box-shadow: 0 0 12rpx rgba(76, 175, 80, 0.6);
106
+ box-sizing: border-box;
107
+ }
108
+ }
109
+
110
+ // === 形状 ===
111
+ .dd-avatar--circle .dd-avatar__inner {
112
+ border-radius: 50%;
113
+ }
114
+
115
+ .dd-avatar--square .dd-avatar__inner {
116
+ border-radius: $dd-radius-md;
117
+ }
118
+
119
+ // === 5 尺寸 (px→rpx x2) ===
120
+ .dd-avatar--xs {
121
+ width: 48rpx;
122
+ height: 48rpx;
123
+ .dd-avatar__text { font-size: $dd-font-size-caption; }
124
+ }
125
+
126
+ .dd-avatar--sm {
127
+ width: 64rpx;
128
+ height: 64rpx;
129
+ .dd-avatar__text { font-size: $dd-font-size-body; }
130
+ }
131
+
132
+ .dd-avatar--md {
133
+ width: 88rpx;
134
+ height: 88rpx;
135
+ .dd-avatar__text { font-size: $dd-font-size-h3; }
136
+ }
137
+
138
+ .dd-avatar--lg {
139
+ width: 128rpx;
140
+ height: 128rpx;
141
+ .dd-avatar__text { font-size: $dd-font-size-h2; }
142
+ }
143
+
144
+ .dd-avatar--xl {
145
+ width: 192rpx;
146
+ height: 192rpx;
147
+ .dd-avatar__text { font-size: $dd-font-size-h1; }
148
+ }
149
+
150
+ // === VIP: 金色渐变环 + 发光 ===
151
+ .dd-avatar--vip {
152
+ padding: 4rpx;
153
+ background: $dd-gradient-primary-wide;
154
+ box-shadow: $dd-shadow-glow-gold-lg;
155
+
156
+ .dd-avatar__inner {
157
+ border-radius: inherit;
158
+ }
159
+ }
160
+
161
+ .dd-avatar--vip.dd-avatar--circle {
162
+ border-radius: 50%;
163
+ }
164
+
165
+ .dd-avatar--vip.dd-avatar--square {
166
+ border-radius: $dd-radius-lg;
167
+ }
168
+ </style>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <view
3
+ class="dd-backtop"
4
+ :class="{ 'dd-backtop--show': visible }"
5
+ :style="{ right: right + 'rpx', bottom: bottom + 'rpx' }"
6
+ @click="onClick"
7
+ >
8
+ <slot name="icon">
9
+ <view class="dd-backtop__icon"><dd-icon name="back-top" /></view>
10
+ </slot>
11
+ </view>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { computed } from 'vue'
16
+ import DdIcon from '../dd-icon/dd-icon.vue'
17
+
18
+ interface Props {
19
+ scrollTop?: number
20
+ visibilityHeight?: number
21
+ right?: number
22
+ bottom?: number
23
+ duration?: number
24
+ }
25
+
26
+ const props = withDefaults(defineProps<Props>(), {
27
+ scrollTop: 0,
28
+ visibilityHeight: 400,
29
+ right: 40,
30
+ bottom: 40,
31
+ duration: 300,
32
+ })
33
+
34
+ const emit = defineEmits<{ (e: 'click'): void }>()
35
+
36
+ const visible = computed(() => props.scrollTop > props.visibilityHeight)
37
+
38
+ function onClick() {
39
+ emit('click')
40
+ uni.pageScrollTo({ scrollTop: 0, duration: props.duration })
41
+ }
42
+ </script>
43
+
44
+ <style lang="scss" scoped>
45
+ @import '../../scss/variables';
46
+ @import '../../scss/mixins';
47
+
48
+ .dd-backtop {
49
+ position: fixed;
50
+ z-index: $dd-z-index-fixed;
51
+ width: 80rpx;
52
+ height: 80rpx;
53
+ border-radius: $dd-radius-full;
54
+ background: $dd-surface;
55
+ color: $dd-text-primary;
56
+ box-shadow: $dd-shadow-2;
57
+ @include dd-flex-center;
58
+ opacity: 0;
59
+ transform: scale(0);
60
+ @include dd-no-touch;
61
+ @include dd-transition(opacity 0.3s, transform 0.3s);
62
+
63
+ &--show {
64
+ opacity: 1;
65
+ transform: scale(1);
66
+ pointer-events: auto;
67
+ user-select: auto;
68
+ }
69
+
70
+ &__icon {
71
+ font-size: $dd-font-size-lead;
72
+ line-height: 1;
73
+ }
74
+ }
75
+ </style>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <view class="dd-badge">
3
+ <slot></slot>
4
+ <view
5
+ v-if="visible"
6
+ class="dd-badge__sup"
7
+ :class="[`dd-badge__sup--${type}`, `dd-badge__sup--${variant}`]"
8
+ >
9
+ <template v-if="type === 'dot'"></template>
10
+ <text v-else-if="type === 'number'" class="dd-badge__num">{{ displayContent }}</text>
11
+ <text v-else class="dd-badge__txt"><slot>{{ content }}</slot></text>
12
+ </view>
13
+ </view>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { computed } from 'vue'
18
+
19
+ interface Props {
20
+ type?: 'dot' | 'number' | 'text'
21
+ variant?: 'primary' | 'error' | 'success' | 'warning'
22
+ content?: string | number
23
+ max?: number
24
+ showZero?: boolean
25
+ }
26
+
27
+ const props = withDefaults(defineProps<Props>(), {
28
+ type: 'number',
29
+ variant: 'error',
30
+ content: '',
31
+ max: 99,
32
+ showZero: false,
33
+ })
34
+
35
+ const numericContent = computed(() => {
36
+ return typeof props.content === 'number' ? props.content : Number(props.content)
37
+ })
38
+
39
+ const isNumber = computed(() => !Number.isNaN(numericContent.value))
40
+
41
+ const displayContent = computed(() => {
42
+ if (!isNumber.value) return String(props.content)
43
+ const n = numericContent.value
44
+ if (n > props.max) return `${props.max}+`
45
+ return String(n)
46
+ })
47
+
48
+ // dot 始终显示; number 当 0 且 !showZero 时隐藏; text 当内容为空时隐藏
49
+ const visible = computed(() => {
50
+ if (props.type === 'dot') return true
51
+ if (props.type === 'number') {
52
+ if (!isNumber.value) return false
53
+ const n = numericContent.value
54
+ if (n === 0 && !props.showZero) return false
55
+ return true
56
+ }
57
+ // text
58
+ const hasSlot = false // slot 默认内容由父级传入, 此处依赖 content prop
59
+ return hasSlot || String(props.content).length > 0
60
+ })
61
+ </script>
62
+
63
+ <style lang="scss" scoped>
64
+ @import '../../scss/variables';
65
+ @import '../../scss/mixins';
66
+
67
+ .dd-badge {
68
+ position: relative;
69
+ display: inline-flex;
70
+ flex-shrink: 0;
71
+
72
+ &__sup {
73
+ position: absolute;
74
+ top: 0;
75
+ right: 0;
76
+ transform: translate(25%, -25%);
77
+ z-index: 1;
78
+ box-sizing: border-box;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ }
83
+
84
+ &__num {
85
+ font-family: $dd-font-mono;
86
+ font-weight: 600;
87
+ color: #ffffff;
88
+ line-height: 1;
89
+ padding: 0 10rpx;
90
+ }
91
+
92
+ &__txt {
93
+ font-family: $dd-font-body;
94
+ font-weight: 500;
95
+ color: $dd-color-white;
96
+ line-height: 1;
97
+ padding: 0 $dd-space-2;
98
+ }
99
+ }
100
+
101
+ // === dot: 16rpx 圆点 ===
102
+ .dd-badge__sup--dot {
103
+ width: 16rpx;
104
+ height: 16rpx;
105
+ border-radius: 50%;
106
+ padding: 0;
107
+ }
108
+
109
+ // === number: 胶囊, 最小宽度 36rpx, 高度 36rpx ===
110
+ .dd-badge__sup--number {
111
+ min-width: 36rpx;
112
+ height: 36rpx;
113
+ border-radius: $dd-radius-full;
114
+ font-size: $dd-font-size-caption;
115
+ }
116
+
117
+ // === text: 胶囊 ===
118
+ .dd-badge__sup--text {
119
+ height: 36rpx;
120
+ border-radius: $dd-radius-full;
121
+ font-size: 20rpx;
122
+ }
123
+
124
+ // === 4 色变体 ===
125
+ .dd-badge__sup--primary {
126
+ background: $dd-gradient-primary;
127
+ box-shadow: $dd-shadow-glow-gold-sm;
128
+ }
129
+
130
+ .dd-badge__sup--error {
131
+ background: $dd-error-500;
132
+ box-shadow: $dd-shadow-glow-error-sm;
133
+ }
134
+
135
+ .dd-badge__sup--success {
136
+ background: $dd-success-500;
137
+ box-shadow: $dd-shadow-glow-success-sm;
138
+ }
139
+
140
+ .dd-badge__sup--warning {
141
+ background: $dd-warning-500;
142
+ color: $dd-warning-contrast;
143
+ box-shadow: $dd-shadow-glow-warning-md;
144
+ }
145
+
146
+ // text 变体强制反色字
147
+ .dd-badge__sup--text.dd-badge__sup--warning .dd-badge__txt {
148
+ color: $dd-warning-contrast;
149
+ }
150
+ </style>
@@ -0,0 +1,215 @@
1
+ <template>
2
+ <view
3
+ class="dd-btn"
4
+ :class="[
5
+ `dd-btn--${type}`,
6
+ `dd-btn--${size}`,
7
+ {
8
+ 'dd-btn--block': block,
9
+ 'dd-btn--round': round,
10
+ 'dd-btn--icon-right': iconPosition === 'right',
11
+ 'dd-btn--icon-only': isIconOnly,
12
+ 'dd-btn--loading': loading,
13
+ 'dd-btn--disabled': disabled,
14
+ },
15
+ ]"
16
+ :hover-class="!disabled && !loading ? 'dd-btn--hover' : ''"
17
+ :hover-stay-time="120"
18
+ @click="onClick"
19
+ >
20
+ <view v-if="loading" class="dd-btn__loading"></view>
21
+ <template v-else>
22
+ <slot v-if="$slots.icon" name="icon"></slot>
23
+ <dd-icon v-else-if="icon" :name="icon" class="dd-btn__icon" />
24
+ </template>
25
+ <text v-if="$slots.default" class="dd-btn__label"><slot></slot></text>
26
+ </view>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { computed, useSlots } from 'vue'
31
+ import DdIcon from '../dd-icon/dd-icon.vue'
32
+
33
+ interface Props {
34
+ type?: 'primary' | 'secondary' | 'ghost' | 'text' | 'success' | 'warning' | 'danger'
35
+ size?: 'sm' | 'md' | 'lg'
36
+ block?: boolean
37
+ round?: boolean
38
+ iconPosition?: 'left' | 'right'
39
+ icon?: string
40
+ loading?: boolean
41
+ disabled?: boolean
42
+ }
43
+
44
+ const props = withDefaults(defineProps<Props>(), {
45
+ type: 'primary',
46
+ size: 'md',
47
+ block: false,
48
+ round: false,
49
+ iconPosition: 'left',
50
+ icon: '',
51
+ loading: false,
52
+ disabled: false,
53
+ })
54
+
55
+ const emit = defineEmits<{ (e: 'click', val: Event): void }>()
56
+
57
+ const slots = useSlots()
58
+ const isIconOnly = computed(() => !slots.default && (!!props.icon || !!slots.icon))
59
+
60
+ function onClick(e: Event) {
61
+ if (props.disabled || props.loading) return
62
+ emit('click', e)
63
+ }
64
+ </script>
65
+
66
+ <style lang="scss" scoped>
67
+ @import '../../scss/variables';
68
+ @import '../../scss/mixins';
69
+
70
+ .dd-btn {
71
+ @include dd-flex-center;
72
+ @include dd-transition(background 0.2s, box-shadow 0.2s, opacity 0.2s);
73
+ gap: 8rpx;
74
+ box-sizing: border-box;
75
+ border: 1px solid transparent;
76
+ border-radius: $dd-radius-md;
77
+ font-family: $dd-font-body;
78
+ white-space: nowrap;
79
+ user-select: none;
80
+ height: 88rpx;
81
+ padding: 0 32rpx;
82
+ font-size: 28rpx;
83
+ font-weight: 600;
84
+ line-height: 1;
85
+
86
+ &__icon {
87
+ font-size: 32rpx;
88
+ line-height: 1;
89
+ }
90
+
91
+ &__label {
92
+ @include dd-ellipsis(1);
93
+ }
94
+
95
+ &__loading {
96
+ width: 28rpx;
97
+ height: 28rpx;
98
+ border: 3rpx solid currentColor;
99
+ border-top-color: transparent;
100
+ border-radius: 50%;
101
+ animation: dd-btn-spin 0.6s linear infinite;
102
+ }
103
+
104
+ .dd-btn--icon-only {
105
+ padding: 0;
106
+ width: 88rpx;
107
+ gap: 0;
108
+ }
109
+
110
+ &--block {
111
+ display: flex;
112
+ width: 100%;
113
+ }
114
+
115
+ &--icon-right {
116
+ flex-direction: row-reverse;
117
+ }
118
+ }
119
+
120
+ @keyframes dd-btn-spin {
121
+ to {
122
+ transform: rotate(360deg);
123
+ }
124
+ }
125
+
126
+ .dd-btn--sm {
127
+ height: 64rpx;
128
+ padding: 0 24rpx;
129
+ font-size: 24rpx;
130
+ &.dd-btn--icon-only {
131
+ width: 64rpx;
132
+ }
133
+ .dd-btn__loading {
134
+ width: 22rpx;
135
+ height: 22rpx;
136
+ }
137
+ }
138
+
139
+ .dd-btn--lg {
140
+ height: 100rpx;
141
+ padding: 0 48rpx;
142
+ font-size: 32rpx;
143
+ &.dd-btn--icon-only {
144
+ width: 100rpx;
145
+ }
146
+ .dd-btn__loading {
147
+ width: 34rpx;
148
+ height: 34rpx;
149
+ }
150
+ }
151
+
152
+ .dd-btn--primary {
153
+ background: $dd-primary-500;
154
+ color: $dd-neutral-50;
155
+ }
156
+ .dd-btn--secondary {
157
+ background: $dd-neutral-800;
158
+ color: $dd-primary-400;
159
+ border-color: $dd-neutral-700;
160
+ }
161
+ .dd-btn--ghost {
162
+ background: transparent;
163
+ color: $dd-neutral-300;
164
+ }
165
+ .dd-btn--text {
166
+ background: transparent;
167
+ color: $dd-primary-400;
168
+ border-color: transparent;
169
+ border-radius: 0;
170
+ height: auto;
171
+ min-height: 48rpx;
172
+ padding: 0 16rpx;
173
+ font-size: 24rpx;
174
+ font-weight: 500;
175
+ &.dd-btn--icon-only {
176
+ width: auto;
177
+ padding: 0;
178
+ }
179
+ }
180
+ .dd-btn--success {
181
+ background: $dd-success-500;
182
+ color: $dd-success-contrast;
183
+ }
184
+ .dd-btn--warning {
185
+ background: $dd-warning-500;
186
+ color: $dd-warning-contrast;
187
+ }
188
+ .dd-btn--danger {
189
+ background: $dd-error-500;
190
+ color: $dd-error-contrast;
191
+ }
192
+
193
+ .dd-btn--round {
194
+ border-radius: $dd-radius-full;
195
+ }
196
+ .dd-btn--text.dd-btn--round {
197
+ border-radius: $dd-radius-full;
198
+ }
199
+
200
+ .dd-btn--hover {
201
+ opacity: 0.85;
202
+ }
203
+ .dd-btn--secondary.dd-btn--hover,
204
+ .dd-btn--ghost.dd-btn--hover {
205
+ opacity: 0.7;
206
+ }
207
+
208
+ .dd-btn--disabled {
209
+ opacity: 0.5;
210
+ @include dd-no-touch;
211
+ }
212
+ .dd-btn--loading {
213
+ @include dd-no-touch;
214
+ }
215
+ </style>
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <view class="dd-capsule-button" :class="`dd-capsule-button--${variant}`">
3
+ <view
4
+ v-if="showBack && variant !== 'close-only'"
5
+ class="dd-capsule-button__btn dd-capsule-button__btn--back"
6
+ @click="onBack"
7
+ >
8
+ <view class="dd-capsule-button__arrow"></view>
9
+ </view>
10
+ <view
11
+ v-if="variant === 'default' && showBack && showMenu"
12
+ class="dd-capsule-button__divider"
13
+ ></view>
14
+ <view
15
+ v-if="showMenu && variant !== 'back-only'"
16
+ class="dd-capsule-button__btn dd-capsule-button__btn--menu"
17
+ @click="onMenu"
18
+ >
19
+ <dd-icon name="ellipsis" class="dd-capsule-button__dots" />
20
+ </view>
21
+ </view>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import DdIcon from '../dd-icon/dd-icon.vue'
26
+
27
+ interface Props {
28
+ variant?: 'default' | 'back-only' | 'close-only'
29
+ showBack?: boolean
30
+ showMenu?: boolean
31
+ }
32
+
33
+ const props = withDefaults(defineProps<Props>(), {
34
+ variant: 'default',
35
+ showBack: true,
36
+ showMenu: true,
37
+ })
38
+
39
+ const emit = defineEmits<{
40
+ (e: 'back'): void
41
+ (e: 'menu'): void
42
+ }>()
43
+
44
+ function onBack() {
45
+ emit('back')
46
+ }
47
+ function onMenu() {
48
+ emit('menu')
49
+ }
50
+ </script>
51
+
52
+ <style lang="scss" scoped>
53
+ @import '../../scss/variables';
54
+ @import '../../scss/mixins';
55
+
56
+ // 微信小程序标准 87×32px → 174×64rpx (ponytail: 固定尺寸,H5 降级同样尺寸)
57
+ .dd-capsule-button {
58
+ width: 174rpx;
59
+ height: 64rpx;
60
+ border-radius: $dd-radius-full;
61
+ @include dd-glass;
62
+ display: flex;
63
+ align-items: stretch;
64
+ overflow: hidden;
65
+
66
+ &__btn {
67
+ flex: 1;
68
+ @include dd-flex-center;
69
+ color: $dd-text-secondary;
70
+ @include dd-transition(background 0.2s ease);
71
+
72
+ &:active {
73
+ background: rgba(255, 255, 255, 0.1);
74
+ }
75
+ }
76
+
77
+ // CSS 绘制返回箭头 "<": border-left+border-bottom + rotate(45deg) (无 SVG,MP 兼容)
78
+ &__arrow {
79
+ width: 20rpx;
80
+ height: 20rpx;
81
+ border-left: 4rpx solid $dd-text-secondary;
82
+ border-bottom: 4rpx solid $dd-text-secondary;
83
+ transform: rotate(45deg);
84
+ margin-left: 4rpx;
85
+ }
86
+
87
+ &__dots {
88
+ font-size: 36rpx;
89
+ line-height: 1;
90
+ color: $dd-text-secondary;
91
+ }
92
+
93
+ // 1px 竖向分隔线,仅 default 变体
94
+ &__divider {
95
+ width: 1px;
96
+ height: 32rpx;
97
+ align-self: center;
98
+ background: $dd-border-default;
99
+ flex-shrink: 0;
100
+ }
101
+
102
+ &--back-only &__btn--back {
103
+ flex: 1;
104
+ }
105
+ &--close-only &__btn--menu {
106
+ flex: 1;
107
+ }
108
+ }
109
+ </style>