@didaoktv/didaoui-uniapp 1.0.0 → 1.2.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 (114) hide show
  1. package/README.md +16 -7
  2. package/components/dd-action-sheet/dd-action-sheet.vue +3 -3
  3. package/components/dd-album/album.js +19 -0
  4. package/components/dd-album/dd-album.vue +345 -0
  5. package/components/dd-album/props.js +89 -0
  6. package/components/dd-avatar/avatar.js +19 -0
  7. package/components/dd-avatar/dd-avatar.vue +172 -160
  8. package/components/dd-avatar/props.js +84 -0
  9. package/components/dd-avatar-group/avatarGroup.js +14 -0
  10. package/components/dd-avatar-group/dd-avatar-group.vue +110 -0
  11. package/components/dd-avatar-group/props.js +57 -0
  12. package/components/dd-badge/dd-badge.vue +2 -2
  13. package/components/dd-barcode/dd-barcode.vue +1030 -0
  14. package/components/dd-bill-detail/dd-bill-detail.vue +265 -0
  15. package/components/dd-button/dd-button.vue +17 -17
  16. package/components/dd-calendar/dd-calendar.vue +357 -0
  17. package/components/dd-canvas/dd-canvas.vue +1088 -0
  18. package/components/dd-capsule-button/dd-capsule-button.vue +6 -6
  19. package/components/dd-car-keyboard/carKeyboard.js +6 -0
  20. package/components/dd-car-keyboard/dd-car-keyboard.vue +315 -0
  21. package/components/dd-car-keyboard/props.js +19 -0
  22. package/components/dd-cascader/dd-cascader.vue +405 -0
  23. package/components/dd-cell/dd-cell.vue +50 -23
  24. package/components/dd-cell-group/dd-cell-group.vue +32 -25
  25. package/components/dd-champion-card/dd-champion-card.vue +4 -4
  26. package/components/dd-checkbox/dd-checkbox.vue +30 -39
  27. package/components/dd-checkbox-group/dd-checkbox-group.vue +67 -0
  28. package/components/dd-copy/dd-copy.vue +74 -0
  29. package/components/dd-coupon/dd-coupon.vue +368 -0
  30. package/components/dd-cropper/dd-cropper.vue +1297 -0
  31. package/components/dd-dialog/dd-dialog.vue +1 -1
  32. package/components/dd-divider/dd-divider.vue +3 -3
  33. package/components/dd-dropdown-item/dd-dropdown-item.vue +5 -5
  34. package/components/dd-empty-state/dd-empty-state.vue +1 -1
  35. package/components/dd-field/dd-field.vue +531 -106
  36. package/components/dd-form/dd-form.vue +116 -0
  37. package/components/dd-form/rules.ts +84 -0
  38. package/components/dd-goods-sku/dd-goods-sku.vue +434 -0
  39. package/components/dd-icon/dd-icon.vue +2 -2
  40. package/components/dd-image/dd-image.vue +4 -4
  41. package/components/dd-input/dd-input.vue +5 -5
  42. package/components/dd-keyboard/dd-keyboard.vue +178 -0
  43. package/components/dd-keyboard/keyboard.js +22 -0
  44. package/components/dd-keyboard/props.js +89 -0
  45. package/components/dd-link/dd-link.vue +87 -0
  46. package/components/dd-link/link.js +18 -0
  47. package/components/dd-link/props.js +44 -0
  48. package/components/dd-list-cell/dd-list-cell.vue +4 -4
  49. package/components/dd-loading/dd-loading.vue +1 -1
  50. package/components/dd-loadmore/dd-loadmore.vue +2 -2
  51. package/components/dd-markdown/dd-markdown.vue +344 -0
  52. package/components/dd-markdown/marked.esm.mjs +7 -0
  53. package/components/dd-mini-program-navbar/dd-mini-program-navbar.vue +9 -9
  54. package/components/dd-navigation/dd-navigation.vue +5 -5
  55. package/components/dd-number-keyboard/dd-number-keyboard.vue +200 -0
  56. package/components/dd-number-keyboard/numberKeyboard.js +8 -0
  57. package/components/dd-number-keyboard/props.js +24 -0
  58. package/components/dd-parse/dd-parse.vue +534 -0
  59. package/components/dd-parse/node/handler.wxs +26 -0
  60. package/components/dd-parse/node/node.vue +603 -0
  61. package/components/dd-parse/parse.js +13 -0
  62. package/components/dd-parse/parser.js +1400 -0
  63. package/components/dd-parse/props.js +57 -0
  64. package/components/dd-popover/dd-popover.vue +10 -10
  65. package/components/dd-popover-item/dd-popover-item.vue +3 -3
  66. package/components/dd-popup/dd-popup.vue +5 -5
  67. package/components/dd-poster/dd-poster.vue +695 -0
  68. package/components/dd-progress/dd-progress.vue +3 -3
  69. package/components/dd-pull-refresh/dd-pull-refresh.vue +3 -3
  70. package/components/dd-qrcode/dd-qrcode.vue +509 -0
  71. package/components/dd-qrcode/qrcode.js +1283 -0
  72. package/components/dd-radio/dd-radio.vue +19 -20
  73. package/components/dd-radio-group/dd-radio-group.vue +53 -0
  74. package/components/dd-rate/dd-rate.vue +1 -1
  75. package/components/dd-room-card/dd-room-card.vue +3 -3
  76. package/components/dd-search-bar/dd-search-bar.vue +8 -8
  77. package/components/dd-segmented-tab/dd-segmented-tab.vue +6 -6
  78. package/components/dd-signature/dd-signature.vue +513 -0
  79. package/components/dd-skeleton/dd-skeleton.vue +1 -1
  80. package/components/dd-slider/dd-slider.vue +4 -4
  81. package/components/dd-stat-card/dd-stat-card.vue +5 -5
  82. package/components/dd-step/dd-step.vue +11 -11
  83. package/components/dd-stepper/dd-stepper.vue +6 -6
  84. package/components/dd-swipe-action/dd-swipe-action.vue +1 -1
  85. package/components/dd-swipeable-tab/dd-swipeable-tab.vue +1 -1
  86. package/components/dd-tabbar-item/dd-tabbar-item.vue +8 -8
  87. package/components/dd-tag/dd-tag.vue +17 -1
  88. package/components/dd-text/dd-text.vue +250 -0
  89. package/components/dd-text/props.js +120 -0
  90. package/components/dd-text/text.js +30 -0
  91. package/components/dd-text/value.js +87 -0
  92. package/components/dd-toast/dd-toast.vue +2 -2
  93. package/components/dd-upload/dd-upload.vue +4 -4
  94. package/components/dd-workorder-card/dd-workorder-card.vue +237 -0
  95. package/index.ts +50 -9
  96. package/libs/config/color.js +16 -0
  97. package/libs/config/config.js +25 -0
  98. package/libs/config/props.js +185 -0
  99. package/libs/config/zIndex.js +20 -0
  100. package/libs/function/digit.js +167 -0
  101. package/libs/function/index.js +932 -0
  102. package/libs/function/test.js +327 -0
  103. package/libs/i18n/index.d.ts +2 -0
  104. package/libs/i18n/index.js +89 -0
  105. package/libs/i18n/locales/zh-Hans.js +86 -0
  106. package/libs/mixin/button.js +18 -0
  107. package/libs/mixin/mixin.js +44 -0
  108. package/libs/mixin/mpMixin.js +13 -0
  109. package/libs/mixin/openType.js +27 -0
  110. package/libs/utils.ts +462 -0
  111. package/libs/vue.js +3 -0
  112. package/package.json +1 -1
  113. package/scss/_dd-shared.scss +40 -0
  114. package/scss/_variables.scss +4 -1
@@ -0,0 +1,265 @@
1
+ <template>
2
+ <view class="dd-bill-detail">
3
+ <view v-if="header || $slots.header" class="dd-bill-detail__header">
4
+ <slot name="header">{{ header }}</slot>
5
+ </view>
6
+
7
+ <view v-if="items.length" class="dd-bill-detail__goods">
8
+ <view
9
+ v-for="(item, idx) in items"
10
+ :key="idx"
11
+ class="dd-bill-detail__row dd-bill-detail__row--goods"
12
+ hover-class="dd-bill-detail__row--press"
13
+ :hover-stay-time="40"
14
+ @click="onClick(item, idx)"
15
+ >
16
+ <view class="dd-bill-detail__gname dd-col">
17
+ <text class="dd-bill-detail__name">{{ item.name }}</text>
18
+ <text v-if="item.price" class="dd-bill-detail__price">{{ item.price }}</text>
19
+ </view>
20
+ <view class="dd-bill-detail__gcount dd-col">
21
+ <text v-if="item.count" class="dd-bill-detail__count">×{{ item.count }}</text>
22
+ </view>
23
+ <view class="dd-bill-detail__gamount dd-col dd-col--right">
24
+ <text class="dd-bill-detail__amount">{{ item.amount }}</text>
25
+ </view>
26
+ </view>
27
+ </view>
28
+ <view v-else class="dd-bill-detail__empty">
29
+ <slot name="empty"><text class="dd-bill-detail__empty-text">暂无商品</text></slot>
30
+ </view>
31
+
32
+ <view v-if="subtotal || deliveryFee || total" class="dd-bill-detail__summary">
33
+ <view v-if="subtotal" class="dd-bill-detail__sum-row">
34
+ <text class="dd-bill-detail__sum-label">小计</text>
35
+ <text class="dd-bill-detail__sum-value">{{ subtotal }}</text>
36
+ </view>
37
+ <view v-if="deliveryFee" class="dd-bill-detail__sum-row">
38
+ <text class="dd-bill-detail__sum-label">服务/配送费</text>
39
+ <text class="dd-bill-detail__sum-value">{{ deliveryFee }}</text>
40
+ </view>
41
+ <view v-if="total" class="dd-bill-detail__sum-row dd-bill-detail__sum-row--total">
42
+ <view class="dd-bill-detail__total-left">
43
+ <text class="dd-bill-detail__total-label">合计</text>
44
+ <text v-if="status" class="dd-bill-detail__status" :class="`dd-bill-detail__status--${status}`">{{ statusText }}</text>
45
+ </view>
46
+ <text class="dd-bill-detail__total">{{ total }}</text>
47
+ </view>
48
+ </view>
49
+
50
+ <view v-if="$slots.actions" class="dd-bill-detail__actions">
51
+ <slot name="actions" />
52
+ </view>
53
+ <view v-if="$slots.footer" class="dd-bill-detail__footer">
54
+ <slot name="footer" />
55
+ </view>
56
+ </view>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ import { computed } from 'vue'
61
+
62
+ export interface BillItem {
63
+ name: string
64
+ price?: string
65
+ count?: number
66
+ amount: string
67
+ }
68
+
69
+ interface Props {
70
+ items: BillItem[]
71
+ total?: string
72
+ subtotal?: string
73
+ deliveryFee?: string
74
+ status?: 'unpaid' | 'paid' | 'refunding' | 'refunded' | 'cancelled'
75
+ header?: string
76
+ }
77
+
78
+ const props = withDefaults(defineProps<Props>(), {
79
+ items: () => [],
80
+ total: '',
81
+ subtotal: '',
82
+ deliveryFee: '',
83
+ status: undefined,
84
+ header: '',
85
+ })
86
+
87
+ const emit = defineEmits<{ (e: 'click', val: { item: BillItem; index: number }): void }>()
88
+
89
+ const statusMap: Record<string, string> = {
90
+ unpaid: '待支付',
91
+ paid: '已支付',
92
+ refunding: '退款中',
93
+ refunded: '已退款',
94
+ cancelled: '已取消',
95
+ }
96
+
97
+ const statusText = computed(() => statusMap[props.status || ''] || '')
98
+
99
+ function onClick(item: BillItem, index: number) {
100
+ emit('click', { item, index })
101
+ }
102
+ </script>
103
+
104
+ <style lang="scss" scoped>
105
+ @import '../../scss/variables';
106
+ @import '../../scss/mixins';
107
+
108
+ .dd-bill-detail {
109
+ @include dd-glass(sm);
110
+ border-radius: $dd-radius-xl;
111
+ overflow: hidden;
112
+ }
113
+
114
+ .dd-bill-detail__header {
115
+ padding: $dd-space-3 $dd-space-4;
116
+ font-size: $dd-font-size-body;
117
+ font-weight: $dd-font-weight-lead;
118
+ color: $dd-text-primary;
119
+ border-bottom: 1px solid $dd-border-subtle;
120
+ }
121
+
122
+ .dd-bill-detail__goods {
123
+ padding: $dd-space-2 $dd-space-4;
124
+ }
125
+
126
+ .dd-bill-detail__row {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: $dd-space-2;
130
+ padding: $dd-space-2 0;
131
+
132
+ &--press {
133
+ background: $dd-interactive-hover;
134
+ }
135
+ }
136
+
137
+ .dd-col {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 4rpx;
141
+
142
+ &--right {
143
+ align-items: flex-end;
144
+ margin-left: auto;
145
+ }
146
+ }
147
+
148
+ .dd-bill-detail__gname {
149
+ flex: 1;
150
+ min-width: 0;
151
+ }
152
+
153
+ .dd-bill-detail__name {
154
+ font-size: $dd-font-size-body;
155
+ color: $dd-text-primary;
156
+ @include dd-ellipsis(1);
157
+ }
158
+
159
+ .dd-bill-detail__price {
160
+ font-size: $dd-font-size-caption;
161
+ color: $dd-text-tertiary;
162
+ }
163
+
164
+ .dd-bill-detail__gcount {
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ .dd-bill-detail__count {
169
+ font-size: $dd-font-size-caption;
170
+ color: $dd-text-secondary;
171
+ }
172
+
173
+ .dd-bill-detail__gamount {
174
+ flex-shrink: 0;
175
+ }
176
+
177
+ .dd-bill-detail__amount {
178
+ font-size: $dd-font-size-body;
179
+ font-weight: $dd-font-weight-lead;
180
+ color: $dd-text-primary;
181
+ font-family: $dd-font-mono;
182
+ }
183
+
184
+ .dd-bill-detail__empty {
185
+ padding: $dd-space-6 $dd-space-4;
186
+ text-align: center;
187
+
188
+ &-text {
189
+ font-size: $dd-font-size-body;
190
+ color: $dd-text-tertiary;
191
+ }
192
+ }
193
+
194
+ .dd-bill-detail__summary {
195
+ border-top: 1px solid $dd-border-subtle;
196
+ padding: $dd-space-2 $dd-space-4 $dd-space-3;
197
+ }
198
+
199
+ .dd-bill-detail__sum-row {
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: space-between;
203
+ padding: 6rpx 0;
204
+
205
+ &--total {
206
+ margin-top: $dd-space-1;
207
+ padding-top: $dd-space-2;
208
+ border-top: 1px solid $dd-border-subtle;
209
+ }
210
+ }
211
+
212
+ .dd-bill-detail__sum-label {
213
+ font-size: $dd-font-size-caption;
214
+ color: $dd-text-tertiary;
215
+ }
216
+
217
+ .dd-bill-detail__sum-value {
218
+ font-size: $dd-font-size-caption;
219
+ color: $dd-text-secondary;
220
+ font-family: $dd-font-mono;
221
+ }
222
+
223
+ .dd-bill-detail__total-left {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: $dd-space-2;
227
+ }
228
+
229
+ .dd-bill-detail__total-label {
230
+ font-size: $dd-font-size-body;
231
+ font-weight: $dd-font-weight-lead;
232
+ color: $dd-text-primary;
233
+ }
234
+
235
+ .dd-bill-detail__total {
236
+ font-size: $dd-font-size-h4;
237
+ font-weight: $dd-font-weight-h3;
238
+ color: $dd-primary-400;
239
+ font-family: $dd-font-mono;
240
+ }
241
+
242
+ .dd-bill-detail__status {
243
+ font-size: 20rpx;
244
+ padding: 2rpx 12rpx;
245
+ border-radius: $dd-radius-sm;
246
+
247
+ &--unpaid { color: $dd-warning-400; background: rgba(249, 168, 37, 0.12); }
248
+ &--paid { color: $dd-success-400; background: rgba(76, 175, 80, 0.12); }
249
+ &--refunding { color: $dd-info-400; background: rgba(0, 188, 212, 0.12); }
250
+ &--refunded { color: $dd-warning-400; background: rgba(249, 168, 37, 0.12); }
251
+ &--cancelled { color: $dd-text-tertiary; background: rgba(255, 255, 255, 0.08); }
252
+ }
253
+
254
+ .dd-bill-detail__actions {
255
+ padding: $dd-space-3 $dd-space-4;
256
+ border-top: 1px solid $dd-border-subtle;
257
+ display: flex;
258
+ justify-content: flex-end;
259
+ gap: $dd-space-3;
260
+ }
261
+
262
+ .dd-bill-detail__footer {
263
+ padding: $dd-space-2 $dd-space-4 $dd-space-3;
264
+ }
265
+ </style>
@@ -70,21 +70,21 @@ function onClick(e: Event) {
70
70
  .dd-btn {
71
71
  @include dd-flex-center;
72
72
  @include dd-transition(background 0.2s, box-shadow 0.2s, opacity 0.2s);
73
- gap: 8rpx;
73
+ gap: $dd-space-1;
74
74
  box-sizing: border-box;
75
75
  border: 1px solid transparent;
76
76
  border-radius: $dd-radius-md;
77
77
  font-family: $dd-font-body;
78
78
  white-space: nowrap;
79
79
  user-select: none;
80
- height: 88rpx;
81
- padding: 0 32rpx;
82
- font-size: 28rpx;
80
+ height: $dd-size-button-md;
81
+ padding: 0 $dd-space-4;
82
+ font-size: $dd-font-size-body;
83
83
  font-weight: 600;
84
84
  line-height: 1;
85
85
 
86
86
  &__icon {
87
- font-size: 32rpx;
87
+ font-size: $dd-font-size-h4;
88
88
  line-height: 1;
89
89
  }
90
90
 
@@ -103,7 +103,7 @@ function onClick(e: Event) {
103
103
 
104
104
  .dd-btn--icon-only {
105
105
  padding: 0;
106
- width: 88rpx;
106
+ width: $dd-size-button-md;
107
107
  gap: 0;
108
108
  }
109
109
 
@@ -124,11 +124,11 @@ function onClick(e: Event) {
124
124
  }
125
125
 
126
126
  .dd-btn--sm {
127
- height: 64rpx;
128
- padding: 0 24rpx;
129
- font-size: 24rpx;
127
+ height: $dd-size-button-sm;
128
+ padding: 0 $dd-space-3;
129
+ font-size: $dd-font-size-caption;
130
130
  &.dd-btn--icon-only {
131
- width: 64rpx;
131
+ width: $dd-size-button-sm;
132
132
  }
133
133
  .dd-btn__loading {
134
134
  width: 22rpx;
@@ -137,11 +137,11 @@ function onClick(e: Event) {
137
137
  }
138
138
 
139
139
  .dd-btn--lg {
140
- height: 100rpx;
141
- padding: 0 48rpx;
142
- font-size: 32rpx;
140
+ height: $dd-size-button-lg;
141
+ padding: 0 $dd-space-5;
142
+ font-size: $dd-font-size-h4;
143
143
  &.dd-btn--icon-only {
144
- width: 100rpx;
144
+ width: $dd-size-button-lg;
145
145
  }
146
146
  .dd-btn__loading {
147
147
  width: 34rpx;
@@ -168,9 +168,9 @@ function onClick(e: Event) {
168
168
  border-color: transparent;
169
169
  border-radius: 0;
170
170
  height: auto;
171
- min-height: 48rpx;
172
- padding: 0 16rpx;
173
- font-size: 24rpx;
171
+ min-height: $dd-space-5;
172
+ padding: 0 $dd-space-2;
173
+ font-size: $dd-font-size-caption;
174
174
  font-weight: 500;
175
175
  &.dd-btn--icon-only {
176
176
  width: auto;
@@ -0,0 +1,357 @@
1
+ <template>
2
+ <view class="dd-calendar">
3
+ <view class="dd-calendar__head">
4
+ <view class="dd-calendar__nav" hover-class="dd-calendar__nav--press" @click="prevMonth">‹</view>
5
+ <text class="dd-calendar__title">{{ currentYear }}年{{ currentMonth + 1 }}月</text>
6
+ <view class="dd-calendar__nav" hover-class="dd-calendar__nav--press" @click="nextMonth">›</view>
7
+ </view>
8
+
9
+ <view class="dd-calendar__week">
10
+ <text v-for="w in weekLabels" :key="w" class="dd-calendar__week-item">{{ w }}</text>
11
+ </view>
12
+
13
+ <view class="dd-calendar__grid">
14
+ <view
15
+ v-for="cell in dayCells"
16
+ :key="cell.key"
17
+ class="dd-calendar__cell"
18
+ :class="{
19
+ 'dd-calendar__cell--blank': cell.blank,
20
+ 'dd-calendar__cell--disabled': cell.disabled,
21
+ 'dd-calendar__cell--selected': cell.selected,
22
+ 'dd-calendar__cell--in-range': cell.inRange,
23
+ 'dd-calendar__cell--today': cell.today,
24
+ }"
25
+ hover-class="dd-calendar__cell--press"
26
+ @click="cell.disabled || cell.blank ? undefined : onClickDay(cell)"
27
+ >
28
+ <text class="dd-calendar__day">{{ cell.day }}</text>
29
+ </view>
30
+ </view>
31
+
32
+ <view v-if="timeSlots && timeSlots.length && selectedDate" class="dd-calendar__slots">
33
+ <view class="dd-calendar__slots-title">
34
+ <text>时段({{ selectedDate }})</text>
35
+ </view>
36
+ <view class="dd-calendar__slots-body">
37
+ <text
38
+ v-for="t in timeSlots"
39
+ :key="selectedDate + t"
40
+ class="dd-calendar__slot"
41
+ :class="{ 'dd-calendar__slot--active': activeSlot === t }"
42
+ hover-class="dd-calendar__slot--press"
43
+ @click="onSlot(t)"
44
+ >{{ t }}</text>
45
+ </view>
46
+ </view>
47
+
48
+ <view class="dd-calendar__footer">
49
+ <text class="dd-calendar__footer-btn" hover-class="dd-calendar__footer-btn--press" @click="onCancel">取消</text>
50
+ <text class="dd-calendar__footer-btn dd-calendar__footer-btn--primary" hover-class="dd-calendar__footer-btn--press" @click="onConfirm">确定</text>
51
+ </view>
52
+ </view>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ import { ref, computed } from 'vue'
57
+ import { daysInMonth, firstDayOfMonth, dateFormat, pad2 } from '../../libs/utils'
58
+
59
+ interface DayCell {
60
+ key: string
61
+ day: number
62
+ date: string
63
+ blank: boolean
64
+ disabled: boolean
65
+ selected: boolean
66
+ inRange: boolean
67
+ today: boolean
68
+ }
69
+
70
+ interface Props {
71
+ modelValue?: string[]
72
+ range?: boolean
73
+ minDate?: string
74
+ maxDate?: string
75
+ disabledDate?: (date: string) => boolean
76
+ timeSlots?: string[]
77
+ }
78
+
79
+ const props = withDefaults(defineProps<Props>(), {
80
+ modelValue: () => [],
81
+ range: false,
82
+ minDate: '',
83
+ maxDate: '',
84
+ disabledDate: () => false,
85
+ timeSlots: () => [],
86
+ })
87
+
88
+ const emit = defineEmits<{
89
+ (e: 'update:modelValue', val: string[]): void
90
+ (e: 'confirm', val: string | string[]): void
91
+ (e: 'change', val: string | string[]): void
92
+ (e: 'slot-click', val: { date: string; time: string }): void
93
+ }>()
94
+
95
+ const now = new Date()
96
+ const currentYear = ref(now.getFullYear())
97
+ const currentMonth = ref(now.getMonth())
98
+ const activeSlot = ref<string>('')
99
+
100
+ const weekLabels = ['日', '一', '二', '三', '四', '五', '六']
101
+ const todayStr = dateFormat(now)
102
+
103
+ const selected = computed<string[]>(() => props.modelValue.slice().sort())
104
+
105
+ const selection = computed<string | string[]>(() => {
106
+ if (!props.range) return selected.value[selected.value.length - 1] || ''
107
+ if (selected.value.length < 2) return selected.value[0] || ''
108
+ return [selected.value[0], selected.value[selected.value.length - 1]]
109
+ })
110
+
111
+ const selectedDate = computed(() => {
112
+ if (props.range) return Array.isArray(selection.value) ? selection.value.join(' ~ ') : ''
113
+ return selection.value as string
114
+ })
115
+
116
+ const dayCells = computed<DayCell[]>(() => {
117
+ const today = new Date()
118
+ const prevOffset = firstDayOfMonth(currentYear.value, currentMonth.value)
119
+ const dim = daysInMonth(currentYear.value, currentMonth.value)
120
+ const cells: DayCell[] = []
121
+ for (let i = 0; i < prevOffset; i++) {
122
+ cells.push({ key: 'b' + i, day: 0, date: '', blank: true, disabled: false, selected: false, inRange: false, today: false })
123
+ }
124
+ const [selStart, selEnd] = selected.value.length >= 2 ? [selected.value[0], selected.value[selected.value.length - 1]] : [null, null]
125
+ for (let d = 1; d <= dim; d++) {
126
+ const date = `${currentYear.value}-${pad2(currentMonth.value + 1)}-${pad2(d)}`
127
+ const disabled =
128
+ (props.minDate && date < props.minDate) ||
129
+ (props.maxDate && date > props.maxDate) ||
130
+ props.disabledDate(date)
131
+ const selectedFlag = selected.value.includes(date)
132
+ const inRange = !selectedFlag && selStart && selEnd && date > selStart && date < selEnd
133
+ cells.push({
134
+ key: date,
135
+ day: d,
136
+ date,
137
+ blank: false,
138
+ disabled,
139
+ selected: selectedFlag,
140
+ inRange: !!inRange,
141
+ today: date === todayStr,
142
+ })
143
+ }
144
+ return cells
145
+ })
146
+
147
+ function onClickDay(cell: DayCell) {
148
+ let next: string[]
149
+ if (!props.range) {
150
+ next = selected.value.includes(cell.date) ? [] : [cell.date]
151
+ } else if (selected.value.length === 0) {
152
+ next = [cell.date]
153
+ } else {
154
+ // 第二个选择点:若在已选区间内则重置,否则与首点组成区间
155
+ const first = selected.value[0]
156
+ if (cell.date < first) next = [cell.date, first]
157
+ else if (selected.value.length === 1) next = [first, cell.date]
158
+ else next = [cell.date] // 再次点击重置为新起点
159
+ }
160
+ next.sort()
161
+ emit('update:modelValue', next)
162
+ const payload: string | string[] = props.range ? (next.length >= 2 ? [next[0], next[next.length - 1]] : next[0] || '') : next[0] || ''
163
+ emit('change', payload)
164
+ }
165
+
166
+ function nextMonth() {
167
+ if (currentMonth.value === 11) {
168
+ currentMonth.value = 0
169
+ currentYear.value++
170
+ } else {
171
+ currentMonth.value++
172
+ }
173
+ }
174
+
175
+ function prevMonth() {
176
+ if (currentMonth.value === 0) {
177
+ currentMonth.value = 11
178
+ currentYear.value--
179
+ } else {
180
+ currentMonth.value--
181
+ }
182
+ }
183
+
184
+ function onSlot(t: string) {
185
+ activeSlot.value = t
186
+ const date = props.range ? (Array.isArray(selection.value) ? selection.value[0] : selection.value) : (selection.value as string)
187
+ emit('slot-click', { date: date || '', time: t })
188
+ }
189
+
190
+ function onCancel() {
191
+ emit('change', props.range ? '' : '')
192
+ }
193
+
194
+ function onConfirm() {
195
+ emit('confirm', selection.value)
196
+ }
197
+ </script>
198
+
199
+ <style lang="scss" scoped>
200
+ @import '../../scss/variables';
201
+ @import '../../scss/mixins';
202
+
203
+ .dd-calendar {
204
+ @include dd-glass(sm);
205
+ border-radius: $dd-radius-xl;
206
+ padding: $dd-space-3;
207
+ }
208
+
209
+ .dd-calendar__head {
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: space-between;
213
+ padding: 0 $dd-space-2 $dd-space-2;
214
+ }
215
+
216
+ .dd-calendar__nav {
217
+ width: 56rpx;
218
+ height: 56rpx;
219
+ border-radius: $dd-radius-md;
220
+ background: $dd-surface-container-high;
221
+ @include dd-flex-center;
222
+ color: $dd-text-secondary;
223
+ font-size: $dd-font-size-h4;
224
+
225
+ &--press {
226
+ background: $dd-surface-press;
227
+ }
228
+ }
229
+
230
+ .dd-calendar__title {
231
+ font-size: $dd-font-size-body;
232
+ font-weight: $dd-font-weight-lead;
233
+ color: $dd-text-primary;
234
+ }
235
+
236
+ .dd-calendar__week {
237
+ display: grid;
238
+ grid-template-columns: repeat(7, 1fr);
239
+ padding: $dd-space-1 0;
240
+ }
241
+
242
+ .dd-calendar__week-item {
243
+ text-align: center;
244
+ font-size: $dd-font-size-caption;
245
+ color: $dd-text-tertiary;
246
+ }
247
+
248
+ .dd-calendar__grid {
249
+ display: grid;
250
+ grid-template-columns: repeat(7, 1fr);
251
+ row-gap: 4rpx;
252
+ }
253
+
254
+ .dd-calendar__cell {
255
+ height: 72rpx;
256
+ @include dd-flex-center;
257
+ border-radius: $dd-radius-md;
258
+
259
+ &--press:not(.dd-calendar__cell--disabled, .dd-calendar__cell--blank) {
260
+ background: $dd-surface-hover;
261
+ }
262
+ }
263
+
264
+ .dd-calendar__day {
265
+ font-size: $dd-font-size-body;
266
+ color: $dd-text-primary;
267
+ }
268
+
269
+ .dd-calendar__cell--blank .dd-calendar__day {
270
+ color: transparent;
271
+ }
272
+
273
+ .dd-calendar__cell--disabled .dd-calendar__day {
274
+ color: $dd-text-tertiary;
275
+ opacity: 0.4;
276
+ }
277
+
278
+ .dd-calendar__cell--today .dd-calendar__day {
279
+ color: $dd-primary-400;
280
+ font-weight: 700;
281
+ }
282
+
283
+ .dd-calendar__cell--selected {
284
+ background: $dd-gradient-primary;
285
+ .dd-calendar__day {
286
+ color: $dd-neutral-900;
287
+ font-weight: 700;
288
+ }
289
+ }
290
+
291
+ .dd-calendar__cell--in-range {
292
+ background: rgba(245, 166, 35, 0.18);
293
+ border-radius: 0;
294
+ }
295
+
296
+ .dd-calendar__slots {
297
+ margin-top: $dd-space-3;
298
+ border-top: 1px solid $dd-border-subtle;
299
+ padding-top: $dd-space-3;
300
+ }
301
+
302
+ .dd-calendar__slots-title {
303
+ font-size: $dd-font-size-caption;
304
+ color: $dd-text-tertiary;
305
+ margin-bottom: $dd-space-2;
306
+ }
307
+
308
+ .dd-calendar__slots-body {
309
+ display: flex;
310
+ flex-wrap: wrap;
311
+ gap: $dd-space-2;
312
+ }
313
+
314
+ .dd-calendar__slot {
315
+ padding: $dd-space-1 $dd-space-3;
316
+ font-size: $dd-font-size-caption;
317
+ color: $dd-text-secondary;
318
+ background: $dd-surface-container-high;
319
+ border-radius: $dd-radius-md;
320
+
321
+ &--active {
322
+ color: $dd-neutral-900;
323
+ background: $dd-gradient-primary;
324
+ font-weight: 700;
325
+ }
326
+
327
+ &--press {
328
+ opacity: 0.75;
329
+ }
330
+ }
331
+
332
+ .dd-calendar__footer {
333
+ display: flex;
334
+ justify-content: flex-end;
335
+ gap: $dd-space-3;
336
+ margin-top: $dd-space-3;
337
+ border-top: 1px solid $dd-border-subtle;
338
+ padding-top: $dd-space-3;
339
+ }
340
+
341
+ .dd-calendar__footer-btn {
342
+ padding: $dd-space-2 $dd-space-4;
343
+ font-size: $dd-font-size-body;
344
+ color: $dd-text-secondary;
345
+ border-radius: $dd-radius-md;
346
+
347
+ &--primary {
348
+ color: $dd-neutral-900;
349
+ background: $dd-gradient-primary;
350
+ font-weight: 700;
351
+ }
352
+
353
+ &--press {
354
+ opacity: 0.75;
355
+ }
356
+ }
357
+ </style>