@xlui/xux-ui 0.2.0 → 0.2.1

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.
@@ -0,0 +1,919 @@
1
+ <template>
2
+ <div class="x-datetime-picker" :class="[`datetime-${size}`, { 'datetime-disabled': disabled }]">
3
+ <!-- 输入框 -->
4
+ <div class="datetime-input-wrapper" @click="togglePicker">
5
+ <input
6
+ ref="inputRef"
7
+ type="text"
8
+ :value="displayValue"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
11
+ readonly
12
+ class="datetime-input"
13
+ />
14
+ <div class="datetime-icon">
15
+ <Icon :icon="iconType" />
16
+ </div>
17
+ </div>
18
+
19
+ <!-- 选择器面板 -->
20
+ <div v-if="isOpen" class="datetime-panel" :class="panelPosition">
21
+ <!-- 头部 -->
22
+ <div class="datetime-header">
23
+ <div class="datetime-title">{{ title }}</div>
24
+ <XButton icon="mdi:close" size="small" @click="closePicker">
25
+ <Icon icon="mdi:close" />
26
+ </XButton>
27
+ </div>
28
+
29
+ <!-- 模式切换 -->
30
+ <div v-if="mode === 'datetime'" class="datetime-mode-switch">
31
+ <XButton
32
+ size="small"
33
+ :class="{ active: currentMode === 'date' }"
34
+ icon="mdi:calendar"
35
+ @click="currentMode = 'date'"
36
+ >
37
+ <Icon icon="mdi:calendar" />
38
+ <span>日期</span>
39
+ </XButton>
40
+ <XButton
41
+ size="small"
42
+ icon="mdi:clock"
43
+ :class="{ active: currentMode === 'time' }"
44
+ @click="currentMode = 'time'"
45
+ >
46
+ <Icon icon="mdi:clock" />
47
+ <span>时间</span>
48
+ </XButton>
49
+ </div>
50
+
51
+ <!-- 日期选择器 -->
52
+ <div v-if="currentMode === 'date' || mode === 'date'" class="datetime-date-picker">
53
+ <!-- 年月导航 -->
54
+ <div class="date-navigation">
55
+ <XButton size="small" @click="previousMonth">
56
+ <Icon icon="mdi:chevron-left" />
57
+ </XButton>
58
+ <div class="current-month">
59
+ <span class="month-year">{{ currentYear }}年{{ currentMonth + 1 }}月</span>
60
+ </div>
61
+ <XButton size="small" @click="nextMonth">
62
+ <Icon icon="mdi:chevron-right" />
63
+ </XButton>
64
+ </div>
65
+
66
+ <!-- 星期标题 -->
67
+ <div class="weekdays">
68
+ <div v-for="day in weekdays" :key="day" class="weekday">{{ day }}</div>
69
+ </div>
70
+
71
+ <!-- 日期网格 -->
72
+ <div class="date-grid">
73
+ <div
74
+ v-for="calendarDate in calendarDates"
75
+ :key="calendarDate.date.format('YYYY-MM-DD')"
76
+ class="date-cell"
77
+ :class="{
78
+ 'date-other-month': !calendarDate.isCurrentMonth,
79
+ 'date-today': calendarDate.isToday,
80
+ 'date-selected': calendarDate.isSelected,
81
+ 'date-disabled': calendarDate.isDisabled
82
+ }"
83
+ @click="selectDate(calendarDate)"
84
+ >
85
+ {{ calendarDate.date.date() }}
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- 时间选择器 -->
91
+ <div v-if="currentMode === 'time' || mode === 'time'" class="datetime-time-picker">
92
+ <div class="time-inputs">
93
+ <div class="time-input-group">
94
+ <label class="time-label">时</label>
95
+ <input
96
+ :value="selectedHour" type="number" min="0" max="23" class="time-input" @change="updateTime" />
97
+ </div>
98
+ <div class="time-separator">:</div>
99
+ <div class="time-input-group">
100
+ <label class="time-label">分</label>
101
+ <input
102
+ :value="selectedMinute" type="number" min="0" max="59" class="time-input" @change="updateTime" />
103
+ </div>
104
+ <div v-if="showSeconds" class="time-separator">:</div>
105
+ <div v-if="showSeconds" class="time-input-group">
106
+ <label class="time-label">秒</label>
107
+ <input :value="selectedSecond" type="number" min="0" max="59" class="time-input" @change="updateTime" />
108
+ </div>
109
+ </div>
110
+
111
+ <!-- 时间滑块 -->
112
+ <div class="time-sliders">
113
+ <div class="time-slider-group">
114
+ <label class="slider-label">小时</label>
115
+ <input :value="selectedHour" type="range" min="0" max="23" class="time-slider" @input="updateTime" />
116
+ <span class="slider-value">{{ selectedHour.toString().padStart(2, '0') }}</span>
117
+ </div>
118
+ <div class="time-slider-group">
119
+ <label class="slider-label">分钟</label>
120
+ <input :value="selectedMinute" type="range" min="0" max="59" class="time-slider" @input="updateTime" />
121
+ <span class="slider-value">{{ selectedMinute.toString().padStart(2, '0') }}</span>
122
+ </div>
123
+ <div v-if="showSeconds" class="time-slider-group">
124
+ <label class="slider-label">秒钟</label>
125
+ <input :value="selectedSecond" type="range" min="0" max="59" step="1" class="time-slider" @input="updateTime" />
126
+ <span class="slider-value">{{ selectedSecond.toString().padStart(2, '0') }}</span>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- 底部操作 -->
132
+ <div class="datetime-footer">
133
+ <XButton size="small" ghost type="danger" @click="clearValue">
134
+ <Icon icon="mdi:delete" />
135
+ <span>清空</span>
136
+ </XButton>
137
+ <XButton size="small" ghost type="primary" @click="confirmSelection">
138
+ <Icon icon="mdi:check" />
139
+ <span>确定</span>
140
+ </XButton>
141
+ </div>
142
+ </div>
143
+
144
+ </div>
145
+ </template>
146
+
147
+ <script setup lang="ts">
148
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
149
+ import { Icon } from '@iconify/vue'
150
+ import { useDateTime } from '../../composables/DateTime'
151
+ export interface DateTimePickerProps {
152
+ /** 当前值 */
153
+ modelValue?: Date | string | null
154
+ /** 选择模式 */
155
+ mode?: 'date' | 'time' | 'datetime'
156
+ /** 组件尺寸 */
157
+ size?: 'small' | 'medium' | 'large'
158
+ /** 是否禁用 */
159
+ disabled?: boolean
160
+ /** 占位符 */
161
+ placeholder?: string
162
+ /** 标题 */
163
+ title?: string
164
+ /** 是否显示秒 */
165
+ showSeconds?: boolean
166
+ /** 最小日期 */
167
+ minDate?: Date | string
168
+ /** 最大日期 */
169
+ maxDate?: Date | string
170
+ /** 日期格式 */
171
+ dateFormat?: string
172
+ /** 时间格式 */
173
+ timeFormat?: '12' | '24'
174
+ /** 是否显示今天按钮 */
175
+ showToday?: boolean
176
+ /** 是否显示清空按钮 */
177
+ showClear?: boolean
178
+ /** 语言 */
179
+ locale?: 'zh-CN' | 'en-US'
180
+ }
181
+
182
+ const props = withDefaults(defineProps<DateTimePickerProps>(), {
183
+ modelValue: null,
184
+ mode: 'datetime',
185
+ size: 'medium',
186
+ disabled: false,
187
+ placeholder: '请选择日期时间',
188
+ title: '选择日期时间',
189
+ showSeconds: false,
190
+ dateFormat: 'YYYY-MM-DD',
191
+ timeFormat: '24',
192
+ showToday: true,
193
+ showClear: true,
194
+ locale: 'zh-CN'
195
+ })
196
+
197
+ const emit = defineEmits<{
198
+ 'update:modelValue': [value: string | null]
199
+ change: [value: string | null]
200
+ open: []
201
+ close: []
202
+ }>()
203
+
204
+ // 响应式数据
205
+ const isOpen = ref(false)
206
+ const currentMode = ref<'date' | 'time'>('date')
207
+ const currentYear = ref(new Date().getFullYear())
208
+ const currentMonth = ref(new Date().getMonth())
209
+ const selectedDate = ref<Date | null>(null)
210
+ const selectedHour = ref(0)
211
+ const selectedMinute = ref(0)
212
+ const selectedSecond = ref(0)
213
+ const isMobile = ref(false)
214
+ const panelPosition = ref('panel-bottom')
215
+
216
+ // 引用
217
+ const inputRef = ref<HTMLInputElement>()
218
+
219
+ // 使用时间处理 composable
220
+ const {
221
+ format,
222
+ create,
223
+ getCalendarDates,
224
+ formats
225
+ } = useDateTime({
226
+ timezone: 'Asia/Shanghai',
227
+ locale: 'zh-cn'
228
+ })
229
+
230
+ // 星期标题
231
+ const weekdays = computed(() => {
232
+ return props.locale === 'zh-CN'
233
+ ? ['日', '一', '二', '三', '四', '五', '六']
234
+ : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
235
+ })
236
+
237
+ // 显示值
238
+ const displayValue = computed(() => {
239
+ if (!selectedDate.value) return ''
240
+
241
+ const date = create(selectedDate.value)
242
+ const timeStr = props.showSeconds
243
+ ? `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}:${selectedSecond.value.toString().padStart(2, '0')}`
244
+ : `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}`
245
+
246
+ if (props.mode === 'date') {
247
+ return format(date, formats.date)
248
+ } else if (props.mode === 'time') {
249
+ return timeStr
250
+ } else {
251
+ return `${format(date, formats.date)} ${timeStr}`
252
+ }
253
+ })
254
+
255
+ // 图标类型
256
+ const iconType = computed(() => {
257
+ if (props.mode === 'date') return 'mdi:calendar'
258
+ if (props.mode === 'time') return 'mdi:clock'
259
+ return 'mdi:calendar-clock'
260
+ })
261
+
262
+ // 日历日期
263
+ const calendarDates = computed(() => {
264
+ const selected = selectedDate.value ? create(selectedDate.value) : undefined
265
+ const min = props.minDate ? create(props.minDate) : undefined
266
+ const max = props.maxDate ? create(props.maxDate) : undefined
267
+
268
+ return getCalendarDates(
269
+ currentYear.value,
270
+ currentMonth.value,
271
+ selected,
272
+ min,
273
+ max
274
+ )
275
+ })
276
+
277
+
278
+ // 方法
279
+ const togglePicker = () => {
280
+ if (props.disabled) return
281
+ isOpen.value = !isOpen.value
282
+ if (isOpen.value) {
283
+ calculatePanelPosition()
284
+ emit('open')
285
+ } else {
286
+ emit('close')
287
+ }
288
+ }
289
+
290
+ const closePicker = () => {
291
+ isOpen.value = false
292
+ emit('close')
293
+ }
294
+
295
+ const previousMonth = () => {
296
+ if (currentMonth.value === 0) {
297
+ currentMonth.value = 11
298
+ currentYear.value--
299
+ } else {
300
+ currentMonth.value--
301
+ }
302
+ }
303
+
304
+ const nextMonth = () => {
305
+ if (currentMonth.value === 11) {
306
+ currentMonth.value = 0
307
+ currentYear.value++
308
+ } else {
309
+ currentMonth.value++
310
+ }
311
+ }
312
+
313
+ const selectDate = (calendarDate: any) => {
314
+ if (calendarDate.isDisabled) return
315
+
316
+ selectedDate.value = calendarDate.date.toDate()
317
+
318
+ if (props.mode === 'date') {
319
+ confirmSelection()
320
+ }
321
+ }
322
+
323
+ const updateTime = () => {
324
+ // 确保时间值在有效范围内
325
+ selectedHour.value = Math.max(0, Math.min(23, selectedHour.value))
326
+ selectedMinute.value = Math.max(0, Math.min(59, selectedMinute.value))
327
+ selectedSecond.value = Math.max(0, Math.min(59, selectedSecond.value))
328
+ }
329
+
330
+ const confirmSelection = () => {
331
+ if (props.mode === 'date' && selectedDate.value) {
332
+ const date = create(selectedDate.value)
333
+ const formattedDate = format(date, props.dateFormat)
334
+ emit('update:modelValue', formattedDate)
335
+ emit('change', formattedDate)
336
+ } else if (props.mode === 'time') {
337
+ const timeStr = props.showSeconds
338
+ ? `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}:${selectedSecond.value.toString().padStart(2, '0')}`
339
+ : `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}`
340
+ emit('update:modelValue', timeStr)
341
+ emit('change', timeStr)
342
+ } else if (props.mode === 'datetime' && selectedDate.value) {
343
+ const datetime = create(selectedDate.value).hour(selectedHour.value).minute(selectedMinute.value).second(selectedSecond.value).millisecond(0)
344
+ const timeStr = props.showSeconds
345
+ ? `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}:${selectedSecond.value.toString().padStart(2, '0')}`
346
+ : `${selectedHour.value.toString().padStart(2, '0')}:${selectedMinute.value.toString().padStart(2, '0')}`
347
+ const formattedDateTime = `${format(datetime, props.dateFormat)} ${timeStr}`
348
+ emit('update:modelValue', formattedDateTime)
349
+ emit('change', formattedDateTime)
350
+ }
351
+
352
+ closePicker()
353
+ }
354
+
355
+ const clearValue = () => {
356
+ selectedDate.value = null
357
+ selectedHour.value = 0
358
+ selectedMinute.value = 0
359
+ selectedSecond.value = 0
360
+ emit('update:modelValue', null)
361
+ emit('change', null)
362
+ closePicker()
363
+ }
364
+
365
+ // 检测移动端
366
+ const checkMobile = () => {
367
+ isMobile.value = window.innerWidth <= 768
368
+ }
369
+
370
+ // 计算面板位置
371
+ const calculatePanelPosition = () => {
372
+ if (!inputRef.value) return
373
+
374
+ const rect = inputRef.value.getBoundingClientRect()
375
+ const viewportHeight = window.innerHeight
376
+ const spaceBelow = viewportHeight - rect.bottom
377
+ const spaceAbove = rect.top
378
+
379
+ // 面板高度估算(大约400px)
380
+ const panelHeight = 400
381
+
382
+ if (spaceBelow >= panelHeight || spaceBelow > spaceAbove) {
383
+ panelPosition.value = 'panel-bottom'
384
+ } else {
385
+ panelPosition.value = 'panel-top'
386
+ }
387
+ }
388
+
389
+ // 监听器
390
+ watch(() => props.modelValue, (newValue) => {
391
+ if (newValue) {
392
+ const date = create(newValue)
393
+ selectedDate.value = date.toDate()
394
+ selectedHour.value = date.hour()
395
+ selectedMinute.value = date.minute()
396
+ selectedSecond.value = date.second()
397
+
398
+ currentYear.value = date.year()
399
+ currentMonth.value = date.month()
400
+ } else {
401
+ selectedDate.value = null
402
+ selectedHour.value = 0
403
+ selectedMinute.value = 0
404
+ selectedSecond.value = 0
405
+ }
406
+ }, { immediate: true })
407
+
408
+ watch(() => props.mode, (newMode) => {
409
+ if (newMode === 'datetime') {
410
+ currentMode.value = 'date'
411
+ } else {
412
+ currentMode.value = newMode as 'date' | 'time'
413
+ }
414
+ }, { immediate: true })
415
+
416
+ // 点击外部关闭
417
+ const handleClickOutside = (event: Event) => {
418
+ if (isOpen.value && !(event.target as Element)?.closest('.x-datetime-picker')) {
419
+ closePicker()
420
+ }
421
+ }
422
+
423
+ // 生命周期
424
+ onMounted(() => {
425
+ checkMobile()
426
+ window.addEventListener('resize', checkMobile)
427
+ document.addEventListener('click', handleClickOutside)
428
+ })
429
+
430
+ onUnmounted(() => {
431
+ window.removeEventListener('resize', checkMobile)
432
+ document.removeEventListener('click', handleClickOutside)
433
+ })
434
+ </script>
435
+
436
+ <style scoped>
437
+ /* 基础样式 */
438
+ .x-datetime-picker {
439
+ position: relative;
440
+ display: inline-block;
441
+ width: 100%;
442
+ }
443
+
444
+ .datetime-input-wrapper {
445
+ position: relative;
446
+ display: flex;
447
+ align-items: center;
448
+ border: 1px solid #d1d5db;
449
+ border-radius: 0.5rem;
450
+ background-color: #ffffff;
451
+ transition: all 0.2s ease;
452
+ cursor: pointer;
453
+ }
454
+
455
+ .datetime-input-wrapper:hover {
456
+ border-color: #1a1a1a;
457
+ }
458
+
459
+ .datetime-input-wrapper:focus-within {
460
+ border-color: #1a1a1a;
461
+ box-shadow: 0 0 0 1px #1a1a1a;
462
+ }
463
+
464
+ .datetime-input {
465
+ flex: 1;
466
+ padding: 0.75rem 1rem;
467
+ border: none;
468
+ outline: none;
469
+ background: transparent;
470
+ font-size: 0.875rem;
471
+ color: #1a1a1a;
472
+ cursor: pointer;
473
+ }
474
+
475
+ .datetime-input::placeholder {
476
+ color: #9ca3af;
477
+ }
478
+
479
+ .datetime-icon {
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: center;
483
+ width: 2rem;
484
+ height: 2rem;
485
+ color: #6b7280;
486
+ margin-right: 0.5rem;
487
+ }
488
+
489
+ /* 尺寸变体 */
490
+ .datetime-small .datetime-input {
491
+ padding: 0.5rem 0.75rem;
492
+ font-size: 0.75rem;
493
+ }
494
+
495
+ .datetime-small .datetime-icon {
496
+ width: 1.5rem;
497
+ height: 1.5rem;
498
+ }
499
+
500
+ .datetime-large .datetime-input {
501
+ padding: 1rem 1.25rem;
502
+ font-size: 1rem;
503
+ }
504
+
505
+ .datetime-large .datetime-icon {
506
+ width: 2.5rem;
507
+ height: 2.5rem;
508
+ }
509
+
510
+ /* 禁用状态 */
511
+ .datetime-disabled .datetime-input-wrapper {
512
+ opacity: 0.5;
513
+ cursor: not-allowed;
514
+ background-color: #f9fafb;
515
+ }
516
+
517
+ .datetime-disabled .datetime-input {
518
+ cursor: not-allowed;
519
+ }
520
+
521
+ /* 选择器面板 */
522
+ .datetime-panel {
523
+ position: absolute;
524
+ left: 0;
525
+ right: 0;
526
+ z-index: 1000;
527
+ background: #ffffff;
528
+ border: 1px solid #e5e7eb;
529
+ border-radius: 0.75rem;
530
+ box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
531
+ overflow: hidden;
532
+ max-height: 400px;
533
+ overflow-y: auto;
534
+ }
535
+
536
+ .panel-bottom {
537
+ top: 100%;
538
+ margin-top: 0.25rem;
539
+ }
540
+
541
+ .panel-top {
542
+ bottom: 100%;
543
+ margin-bottom: 0.25rem;
544
+ }
545
+
546
+ /* 头部 */
547
+ .datetime-header {
548
+ display: flex;
549
+ align-items: center;
550
+ justify-content: space-between;
551
+ padding: 1rem;
552
+ border-bottom: 1px solid #e5e7eb;
553
+ background-color: #f9fafb;
554
+ }
555
+
556
+ .datetime-title {
557
+ font-weight: 600;
558
+ color: #1a1a1a;
559
+ font-size: 1rem;
560
+ }
561
+
562
+ .datetime-close {
563
+ display: flex;
564
+ align-items: center;
565
+ justify-content: center;
566
+ width: 2rem;
567
+ height: 2rem;
568
+ border: none;
569
+ background: none;
570
+ color: #6b7280;
571
+ cursor: pointer;
572
+ border-radius: 0.25rem;
573
+ transition: all 0.2s ease;
574
+ }
575
+
576
+ .datetime-close:hover {
577
+ background-color: #e5e7eb;
578
+ color: #1a1a1a;
579
+ }
580
+
581
+ /* 模式切换 */
582
+ .datetime-mode-switch {
583
+ display: flex;
584
+ padding: 0.75rem 1rem;
585
+ background-color: #f3f4f6;
586
+ border-bottom: 1px solid #e5e7eb;
587
+ }
588
+
589
+ .mode-btn {
590
+ flex: 1;
591
+ display: flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ gap: 0.5rem;
595
+ padding: 0.5rem;
596
+ border: none;
597
+ background: none;
598
+ color: #6b7280;
599
+ cursor: pointer;
600
+ border-radius: 0.375rem;
601
+ transition: all 0.2s ease;
602
+ font-size: 0.875rem;
603
+ }
604
+
605
+ .mode-btn.active {
606
+ background-color: #1a1a1a;
607
+ color: #ffffff;
608
+ }
609
+
610
+ .mode-btn:hover:not(.active) {
611
+ background-color: #e5e7eb;
612
+ color: #1a1a1a;
613
+ }
614
+
615
+ /* 日期选择器 */
616
+ .datetime-date-picker {
617
+ padding: 1rem;
618
+ }
619
+
620
+ .date-navigation {
621
+ display: flex;
622
+ align-items: center;
623
+ justify-content: space-between;
624
+ margin-bottom: 1rem;
625
+ }
626
+
627
+ .nav-btn {
628
+ display: flex;
629
+ align-items: center;
630
+ justify-content: center;
631
+ width: 2rem;
632
+ height: 2rem;
633
+ border: none;
634
+ background: none;
635
+ color: #6b7280;
636
+ cursor: pointer;
637
+ border-radius: 0.25rem;
638
+ transition: all 0.2s ease;
639
+ }
640
+
641
+ .nav-btn:hover {
642
+ background-color: #e5e7eb;
643
+ color: #1a1a1a;
644
+ }
645
+
646
+ .current-month {
647
+ font-weight: 600;
648
+ color: #1a1a1a;
649
+ font-size: 1rem;
650
+ }
651
+
652
+ .weekdays {
653
+ display: grid;
654
+ grid-template-columns: repeat(7, 1fr);
655
+ gap: 0.25rem;
656
+ margin-bottom: 0.5rem;
657
+ }
658
+
659
+ .weekday {
660
+ display: flex;
661
+ align-items: center;
662
+ justify-content: center;
663
+ height: 2rem;
664
+ font-size: 0.75rem;
665
+ font-weight: 500;
666
+ color: #6b7280;
667
+ }
668
+
669
+ .date-grid {
670
+ display: grid;
671
+ grid-template-columns: repeat(7, 1fr);
672
+ gap: 0.25rem;
673
+ }
674
+
675
+ .date-cell {
676
+ display: flex;
677
+ align-items: center;
678
+ justify-content: center;
679
+ height: 2.5rem;
680
+ font-size: 0.875rem;
681
+ color: #1a1a1a;
682
+ cursor: pointer;
683
+ border-radius: 0.375rem;
684
+ transition: all 0.2s ease;
685
+ }
686
+
687
+ .date-cell:hover:not(.date-disabled) {
688
+ background-color: #f3f4f6;
689
+ }
690
+
691
+ .date-other-month {
692
+ color: #9ca3af;
693
+ }
694
+
695
+ .date-today {
696
+ background-color: #fef3c7;
697
+ color: #92400e;
698
+ font-weight: 600;
699
+ }
700
+
701
+ .date-selected {
702
+ background-color: #1a1a1a;
703
+ color: #ffffff;
704
+ font-weight: 600;
705
+ }
706
+
707
+ .date-disabled {
708
+ color: #d1d5db;
709
+ cursor: not-allowed;
710
+ }
711
+
712
+ .date-disabled:hover {
713
+ background-color: transparent;
714
+ }
715
+
716
+ /* 时间选择器 */
717
+ .datetime-time-picker {
718
+ padding: 1rem;
719
+ }
720
+
721
+ .time-inputs {
722
+ display: flex;
723
+ align-items: center;
724
+ justify-content: center;
725
+ gap: 0.5rem;
726
+ margin-bottom: 1.5rem;
727
+ }
728
+
729
+ .time-input-group {
730
+ display: flex;
731
+ flex-direction: column;
732
+ align-items: center;
733
+ gap: 0.25rem;
734
+ }
735
+
736
+ .time-label {
737
+ font-size: 0.75rem;
738
+ color: #6b7280;
739
+ font-weight: 500;
740
+ }
741
+
742
+ .time-input {
743
+ width: 3rem;
744
+ padding: 0.5rem;
745
+ border: 1px solid #d1d5db;
746
+ border-radius: 0.375rem;
747
+ text-align: center;
748
+ font-size: 1rem;
749
+ color: #1a1a1a;
750
+ background-color: #ffffff;
751
+ transition: all 0.2s ease;
752
+ }
753
+
754
+ .time-input:focus {
755
+ outline: none;
756
+ border-color: #1a1a1a;
757
+ box-shadow: 0 0 0 1px #1a1a1a;
758
+ }
759
+
760
+ .time-separator {
761
+ font-size: 1.5rem;
762
+ color: #1a1a1a;
763
+ font-weight: 600;
764
+ margin-top: 1.5rem;
765
+ }
766
+
767
+ .time-sliders {
768
+ display: flex;
769
+ flex-direction: column;
770
+ gap: 1rem;
771
+ }
772
+
773
+ .time-slider-group {
774
+ display: flex;
775
+ flex-direction: column;
776
+ gap: 0.5rem;
777
+ }
778
+
779
+ .slider-label {
780
+ font-size: 0.875rem;
781
+ color: #1a1a1a;
782
+ font-weight: 500;
783
+ }
784
+
785
+ .time-slider {
786
+ width: 100%;
787
+ height: 0.5rem;
788
+ border-radius: 0.25rem;
789
+ background: #e5e7eb;
790
+ outline: none;
791
+ cursor: pointer;
792
+ -webkit-appearance: none;
793
+ appearance: none;
794
+ }
795
+
796
+ .time-slider::-webkit-slider-thumb {
797
+ -webkit-appearance: none;
798
+ appearance: none;
799
+ width: 1.25rem;
800
+ height: 1.25rem;
801
+ border-radius: 50%;
802
+ background: #1a1a1a;
803
+ cursor: pointer;
804
+ transition: all 0.2s ease;
805
+ }
806
+
807
+ .time-slider::-webkit-slider-thumb:hover {
808
+ transform: scale(1.1);
809
+ }
810
+
811
+ .time-slider::-moz-range-thumb {
812
+ width: 1.25rem;
813
+ height: 1.25rem;
814
+ border-radius: 50%;
815
+ background: #1a1a1a;
816
+ cursor: pointer;
817
+ border: none;
818
+ transition: all 0.2s ease;
819
+ }
820
+
821
+ .slider-value {
822
+ font-size: 0.875rem;
823
+ color: #1a1a1a;
824
+ font-weight: 600;
825
+ text-align: center;
826
+ }
827
+
828
+ /* 底部操作 */
829
+ .datetime-footer {
830
+ display: flex;
831
+ gap: 0.75rem;
832
+ padding: 1rem;
833
+ border-top: 1px solid #e5e7eb;
834
+ background-color: #f9fafb;
835
+ }
836
+
837
+ .btn {
838
+ flex: 1;
839
+ display: flex;
840
+ align-items: center;
841
+ justify-content: center;
842
+ gap: 0.5rem;
843
+ padding: 0.75rem 1rem;
844
+ border: none;
845
+ border-radius: 0.5rem;
846
+ font-size: 0.875rem;
847
+ font-weight: 500;
848
+ cursor: pointer;
849
+ transition: all 0.2s ease;
850
+ }
851
+
852
+ .btn-secondary {
853
+ background-color: #f3f4f6;
854
+ color: #6b7280;
855
+ }
856
+
857
+ .btn-secondary:hover {
858
+ background-color: #e5e7eb;
859
+ color: #1a1a1a;
860
+ }
861
+
862
+ .btn-primary {
863
+ background-color: #1a1a1a;
864
+ color: #ffffff;
865
+ }
866
+
867
+ .btn-primary:hover {
868
+ background-color: #000000;
869
+ }
870
+
871
+
872
+ /* 移动端适配 */
873
+ @media (max-width: 768px) {
874
+ .datetime-panel {
875
+ max-height: 60vh;
876
+ width: 100vw;
877
+ left: 0;
878
+ right: 0;
879
+ border-radius: 0.75rem 0.75rem 0 0;
880
+ }
881
+
882
+ .panel-bottom {
883
+ top: 100%;
884
+ margin-top: 0;
885
+ border-radius: 0.75rem 0.75rem 0 0;
886
+ }
887
+
888
+ .panel-top {
889
+ bottom: 100%;
890
+ margin-bottom: 0;
891
+ border-radius: 0 0 0.75rem 0.75rem;
892
+ }
893
+
894
+ .date-cell {
895
+ height: 3rem;
896
+ font-size: 1rem;
897
+ }
898
+
899
+ .time-input {
900
+ width: 4rem;
901
+ padding: 0.75rem;
902
+ font-size: 1.25rem;
903
+ }
904
+
905
+ .time-slider {
906
+ height: 0.75rem;
907
+ }
908
+
909
+ .time-slider::-webkit-slider-thumb {
910
+ width: 1.5rem;
911
+ height: 1.5rem;
912
+ }
913
+
914
+ .time-slider::-moz-range-thumb {
915
+ width: 1.5rem;
916
+ height: 1.5rem;
917
+ }
918
+ }
919
+ </style>