centaline-data-driven-v3 0.1.15 → 0.1.16

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,748 @@
1
+ <template>
2
+ <div class="el-input" v-bind="model.attrs">
3
+ <!-- 输入框 -->
4
+ <div class="el-input__inner" ref="inputRef" @mouseenter="isHovered = true" @mouseleave="isHovered = false">
5
+ <el-icon style="color: rgb(181 185 196);">
6
+ <Clock />
7
+ </el-icon>
8
+ <input v-model="internalValue" @click="togglePicker" @blur.stop="handleBlur('dateTime')"
9
+ :disabled="model.locked" :disabled-date="disabledDate" />
10
+
11
+ <span class="el-input__close" @click.stop="clearDate" v-if="displayDateTime && isHovered">
12
+ <el-icon>
13
+ <CircleClose />
14
+ </el-icon>
15
+ </span>
16
+ </div>
17
+
18
+ <!-- 使用el-popover重构弹层面板 -->
19
+ <el-popover ref="popoverRef" :virtual-ref="inputRef" trigger="click" :width="panelWidth" :show-arrow="true"
20
+ virtual-triggering popper-class="datetime-popover" @show="onPopoverShow" @hide="onPopoverHide" :transition="0" :hide-after="0">
21
+ <div class="datetime-panel" @click.stop>
22
+ <div class="panel-container">
23
+ <!-- 左侧日历 -->
24
+ <div class="date-section" v-if="showDateSection">
25
+ <div class="calendar-header">
26
+ <div class="calendar-nav">
27
+ <button @click="prevYear" title="上一年">«</button>
28
+ <button @click="prevMonth">‹</button>
29
+ </div>
30
+ <div class="calendar-title">
31
+ {{ currentYear }}年{{ currentMonth + 1 }}月
32
+ </div>
33
+
34
+ <div class="calendar-nav">
35
+ <button @click="nextMonth">›</button>
36
+ <button @click="nextYear" title="下一年">»</button>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="calendar-grid">
41
+ <div v-for="day in weekDays" :key="day" class="week-header">
42
+ {{ day }}
43
+ </div>
44
+ <div v-for="item in calendarDates" :key="item.date.toString()" class="calendar-cell" :class="{
45
+ selected: isDateSelected(item.date),
46
+ disabled: !item.isCurrentMonth,
47
+ today: isToday(item.date),
48
+ }" @click="selectDate(item.date, item.isCurrentMonth)">
49
+ {{ item.day }}
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- 右侧时间 -->
55
+ <div v-if="showTimeSection" class="time-section">
56
+ <div class="time-selectors">
57
+ <div class="time-column">
58
+ <div class="time-label">时</div>
59
+ <div class="time-selector" ref="hourSelectorRef">
60
+ <div v-for="h in hours" :key="h" class="time-option hour-selector"
61
+ :class="{ selected: selectedHour === h }" @click="selectHour(h)">
62
+ {{ h.toString().padStart(2, '0') }}
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div class="time-column" v-if="showMinuteSection">
67
+ <div class="time-label">分</div>
68
+ <div class="time-selector" ref="minuteSelectorRef">
69
+ <div v-for="m in minutes" :key="m" class="time-option minute-selector"
70
+ :class="{ selected: selectedMinute === m }" @click="selectMinute(m)">
71
+ {{ m.toString().padStart(2, '0') }}
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="time-column" v-if="showSecondSection">
77
+ <div class="time-label">秒</div>
78
+ <div class="time-selector">
79
+ <div v-for="s in seconds" :key="s" class="time-option second-selector"
80
+ :class="{ selected: selectedSecond === s }" @click="selectSecond(s)">
81
+ {{ s.toString().padStart(2, '0') }}
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- 底部按钮 -->
90
+ <div class="panel-footer">
91
+ <button class="el-button el-button--default" @click="cancelSelection">
92
+ 取消
93
+ </button>
94
+ <button class="el-button el-button--now" @click="selectNow">
95
+ 此刻
96
+ </button>
97
+ <button class="el-button el-button--primary" @click="confirmSelection">
98
+ 确定
99
+ </button>
100
+ </div>
101
+ </div>
102
+ </el-popover>
103
+ </div>
104
+ </template>
105
+
106
+ <script setup>
107
+ import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
108
+ import dayjs from 'dayjs'
109
+ import { ElPopover } from 'element-plus'
110
+
111
+ /* props 支持外部 v-model */
112
+ const props = defineProps({
113
+ modelValue: { type: [Date, String], default: null },
114
+ model: { type: [Object, Array], default: null },
115
+ format: { type: String, default: 'YYYY-MM-DD HH:mm:ss' },
116
+ minuteStep: { type: Number, default: 1 }
117
+ })
118
+ const emit = defineEmits(['update:modelValue', 'change'])
119
+ const initialValueRef = ref() // 保存初始值
120
+ const isHovered = ref(false)
121
+
122
+ /* 响应式数据 */
123
+ const showPicker = ref(false)
124
+ const selectedDate = ref(null)
125
+ const selectedHour = ref(null)
126
+ const selectedMinute = ref(null)
127
+ const selectedSecond = ref(null)
128
+ const currentMonth = ref(new Date().getMonth())
129
+ const currentYear = ref(new Date().getFullYear())
130
+ const inputRef = ref()
131
+ const popoverRef = ref()
132
+
133
+ const hourSelectorRef = ref(null);
134
+ const minuteSelectorRef = ref(null);
135
+ const secondSelectorRef = ref(null);
136
+
137
+ /* 计算属性 */
138
+ const weekDays = ['日', '一', '二', '三', '四', '五', '六']
139
+ const showDateSection = computed(() => /(YYYY|MM|DD)/.test(props.format))
140
+
141
+ const showTimeSection = computed(() => /(HH|hh|mm|ss)/.test(props.format))
142
+ const showMinuteSection = computed(() => /mm/.test(props.format))
143
+ const showSecondSection = computed(() => /ss/.test(props.format))
144
+ const is12HourFormat = computed(() => /hh/.test(props.format))
145
+
146
+ const hours = computed(() =>
147
+ is12HourFormat.value
148
+ ? Array.from({ length: 12 }, (_, i) => i + 1)
149
+ : Array.from({ length: 24 }, (_, i) => i)
150
+ )
151
+ const minutes = computed(() => {
152
+ const step = Math.max(1, Math.floor(props.minuteStep)) // 防非法值
153
+ return Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step % 60)
154
+ })
155
+ const seconds = computed(() => Array.from({ length: 60 }, (_, i) => i))
156
+
157
+ const panelWidth = computed(() => {
158
+ let width = 320
159
+ if (showTimeSection.value) {
160
+ if (showMinuteSection.value && showSecondSection.value) width = 508
161
+ else if (showMinuteSection.value) width = 450
162
+ else width = 389
163
+ }
164
+ return width
165
+ })
166
+
167
+ const calendarDates = computed(() => {
168
+ const first = new Date(currentYear.value, currentMonth.value, 1)
169
+ const last = new Date(currentYear.value, currentMonth.value + 1, 0)
170
+ const firstDay = first.getDay()
171
+ const prevLast = new Date(currentYear.value, currentMonth.value, 0).getDate()
172
+ const list = []
173
+
174
+ /* 上月尾部 */
175
+ for (let i = firstDay; i > 0; i--) {
176
+ const day = prevLast - i + 1
177
+ list.push({ day, date: new Date(currentYear.value, currentMonth.value - 1, day), isCurrentMonth: false })
178
+ }
179
+ /* 当月 */
180
+ for (let d = 1; d <= last.getDate(); d++) {
181
+ list.push({ day: d, date: new Date(currentYear.value, currentMonth.value, d), isCurrentMonth: true })
182
+ }
183
+ /* 下月头部 */
184
+ const remain = 42 - list.length
185
+ for (let d = 1; d <= remain; d++) {
186
+ list.push({ day: d, date: new Date(currentYear.value, currentMonth.value + 1, d), isCurrentMonth: false })
187
+ }
188
+ return list
189
+ })
190
+
191
+ const internalValue = ref(props.modelValue)
192
+ watch(() => props.modelValue, val => (internalValue.value = val), { immediate: true })
193
+
194
+ const displayDateTime = computed(() =>
195
+ internalValue.value ? formatDateTime(new Date(internalValue.value), props.format) : ''
196
+ )
197
+
198
+ /* 工具函数 */
199
+ function formatDateTime(date, fmt) {
200
+ const y = date.getFullYear()
201
+ const M = date.getMonth() + 1
202
+ const D = date.getDate()
203
+ const H = date.getHours()
204
+ const m = date.getMinutes()
205
+ const s = date.getSeconds()
206
+ const h12 = H % 12 || 12
207
+ const A = H >= 12 ? 'PM' : 'AM'
208
+ return fmt
209
+ .replace('YYYY', y)
210
+ .replace('MM', String(M).padStart(2, '0'))
211
+ .replace('DD', String(D).padStart(2, '0'))
212
+ .replace('HH', String(H).padStart(2, '0'))
213
+ .replace('hh', String(h12).padStart(2, '0'))
214
+ .replace('mm', String(m).padStart(2, '0'))
215
+ .replace('ss', String(s).padStart(2, '0'))
216
+ .replace('A', A)
217
+ }
218
+
219
+ function isToday(date) {
220
+ const t = new Date()
221
+ return date.getDate() === t.getDate() &&
222
+ date.getMonth() === t.getMonth() &&
223
+ date.getFullYear() === t.getFullYear()
224
+ }
225
+
226
+ function isDateSelected(date) {
227
+ if (!selectedDate.value) return false
228
+ return date.getTime() === selectedDate.value.getTime()
229
+ }
230
+
231
+ /* 交互函数 */
232
+ function togglePicker() {
233
+ showPicker.value = !showPicker.value
234
+ if (!showPicker.value) return
235
+ initialValueRef.value = internalValue.value // 保存初始值
236
+ // 初始化
237
+ const now = internalValue.value ? new Date(internalValue.value) : new Date(new Date().setHours(0, 0, 0, 0));
238
+ selectedDate.value = new Date(now.getFullYear(), now.getMonth(), now.getDate())
239
+ selectedHour.value = now.getHours()
240
+ selectedMinute.value = now.getMinutes()
241
+ selectedSecond.value = now.getSeconds()
242
+ currentYear.value = now.getFullYear()
243
+ currentMonth.value = now.getMonth()
244
+ }
245
+
246
+ function selectDate(date, isCurrentMonth) {
247
+ if (!isCurrentMonth) {
248
+ currentMonth.value = date.getMonth()
249
+ currentYear.value = date.getFullYear()
250
+ }
251
+ selectedDate.value = date;
252
+ // 时间未选择时重置为00:00:00
253
+ if (!selectedHour.value) {
254
+ selectedHour.value = 0
255
+ selectedMinute.value = 0
256
+ selectedSecond.value = 0
257
+ }
258
+ setinternalValue();
259
+ }
260
+
261
+ function selectHour(h) { selectedHour.value = h; setinternalValue() }
262
+ function selectMinute(m) { selectedMinute.value = m; setinternalValue() }
263
+ function selectSecond(s) { selectedSecond.value = s; setinternalValue() }
264
+
265
+
266
+ function prevYear() {
267
+ currentYear.value--
268
+ }
269
+ function nextYear() {
270
+ currentYear.value++
271
+ }
272
+ function prevMonth() {
273
+ if (currentMonth.value === 0) {
274
+ currentMonth.value = 11
275
+ currentYear.value--
276
+ } else {
277
+ currentMonth.value--
278
+ }
279
+ }
280
+ function nextMonth() {
281
+ if (currentMonth.value === 11) {
282
+ currentMonth.value = 0
283
+ currentYear.value++
284
+ } else {
285
+ currentMonth.value++
286
+ }
287
+ }
288
+
289
+ function confirmSelection() {
290
+ setinternalValue();
291
+ showPicker.value = false;
292
+ popoverRef.value.hide(); // 调用组件API关闭
293
+
294
+ }
295
+
296
+ function setinternalValue() {
297
+ if (!selectedDate.value) {
298
+ selectedDate.value = new Date() // 未选日期时强制当前日期
299
+ }
300
+ // 时间部分补全逻辑
301
+ const date = new Date(
302
+ selectedDate.value.getFullYear(),
303
+ selectedDate.value.getMonth(),
304
+ selectedDate.value.getDate(),
305
+ selectedHour.value || 0,
306
+ selectedMinute.value || 0,
307
+ selectedSecond.value || 0
308
+ )
309
+
310
+ internalValue.value = formatDateTime(date, props.format);
311
+ emit('update:modelValue', internalValue.value)
312
+ emit('change');
313
+
314
+ }
315
+ function selectNow() {
316
+ const now = new Date();
317
+ const step = Math.max(1, Math.floor(props.minuteStep));
318
+ now.setMinutes(Math.round(now.getMinutes() / step) * step % 60, 0, 0);
319
+ internalValue.value = formatDateTime(now, props.format); // 赋给你绑定的 v-model
320
+ emit('update:modelValue', internalValue.value);
321
+ emit('change');
322
+ showPicker.value = false;
323
+ popoverRef.value.hide(); // 调用组件API关闭
324
+
325
+ }
326
+ function clearDate() {
327
+ internalValue.value = null; // 赋给你绑定的 v-model
328
+ emit('update:modelValue', null)
329
+ emit('change');
330
+ showPicker.value = false
331
+ }
332
+ function cancelSelection() {
333
+ internalValue.value = (initialValueRef.value ? formatDateTime(new Date(initialValueRef.value), props.format) : '');
334
+ showPicker.value = false
335
+ emit('update:modelValue', internalValue.value);
336
+ popoverRef.value.hide(); // 调用组件API关闭
337
+
338
+ }
339
+ function isValidDateFormat(dateStr, format) {
340
+ const normalized = normalizeDateTimeInput(dateStr, format)
341
+ return normalized ? dayjs(normalized, format, true).isValid() : false
342
+ }
343
+
344
+ const handleBlur = () => {
345
+ if (internalValue.value && isValidDateFormat(internalValue.value, props.format)) {
346
+ // 时间部分补全逻辑
347
+ const date = new Date(normalizeDateTimeInput(internalValue.value, props.format));
348
+
349
+ internalValue.value = formatDateTime(date, props.format);
350
+ emit('update:modelValue', internalValue.value);
351
+ showPicker.value = false
352
+ }
353
+ else {
354
+ internalValue.value = initialValueRef.value;
355
+ emit('update:modelValue', internalValue.value);
356
+ }
357
+ }
358
+ function normalizeDateTimeInput(input, format) {
359
+ const hasYear = /YYYY/.test(format)
360
+ const hasMonth = /MM/.test(format)
361
+ const hasDay = /DD/.test(format)
362
+ const hasHour = /HH|hh/.test(format)
363
+ const hasMinute = /mm/.test(format)
364
+ const hasSecond = /ss/.test(format)
365
+
366
+ let padded = input.trim()
367
+
368
+ // 如果格式中有日期部分,但输入中没有,补全
369
+ if (hasYear && !/\d{4}/.test(padded)) return '' // 年必须有,否则无效
370
+ if (hasMonth && !/\d{2}/.test(padded)) return '' // 月必须有,否则无效
371
+ if (hasDay && !/\d{2}/.test(padded)) return '' // 日必须有,否则无效
372
+
373
+ // 补全时分秒
374
+ if (hasHour && !/(\d{2}):(\d{2})(?::(\d{2}))?/.test(padded)) {
375
+ const parts = padded.split(' ')
376
+ const datePart = parts[0] || ''
377
+ let timePart = parts[1] || ''
378
+
379
+ if (hasHour && !timePart) timePart = '00'
380
+ if (hasMinute && !timePart.includes(':')) timePart += ':00'
381
+ if (hasMinute && !timePart.split(':')[1]) timePart += '00'
382
+ if (hasSecond && !timePart.split(':')[2]) timePart += ':00'
383
+
384
+ padded = `${datePart} ${timePart}`.trim()
385
+ }
386
+
387
+ return padded
388
+ }
389
+
390
+ // 新增函数:滚动到选中的时间位置
391
+ function scrollToSelectedTime() {
392
+ // 小时选择器滚动
393
+ if (hourSelectorRef.value && selectedHour.value !== null) {
394
+ let hourValue = selectedHour.value;
395
+ // 处理12小时制
396
+ if (is12HourFormat.value) {
397
+ hourValue = selectedHour.value % 12;
398
+ if (hourValue === 0) hourValue = 12;
399
+ }
400
+ const hourIndex = hours.value.indexOf(hourValue);
401
+ if (hourIndex !== -1) {
402
+ hourSelectorRef.value.scrollTop = hourIndex * 26.8; // 30是每个选项的高度
403
+ }
404
+ }
405
+
406
+ // 分钟选择器滚动
407
+ if (minuteSelectorRef.value && selectedMinute.value !== null) {
408
+ const minuteIndex = minutes.value.indexOf(selectedMinute.value);
409
+ if (minuteIndex !== -1) {
410
+ minuteSelectorRef.value.scrollTop = minuteIndex * 26.8;
411
+ }
412
+ }
413
+
414
+ // 秒选择器滚动
415
+ if (secondSelectorRef.value && selectedSecond.value !== null) {
416
+ const secondIndex = seconds.value.indexOf(selectedSecond.value);
417
+ if (secondIndex !== -1) {
418
+ secondSelectorRef.value.scrollTop = secondIndex * 26.8;
419
+ }
420
+ }
421
+ }
422
+ // 处理popover显示事件
423
+ function onPopoverShow() {
424
+ nextTick(() => {
425
+ scrollToSelectedTime();
426
+ });
427
+ }
428
+
429
+ // 处理popover隐藏事件
430
+ function onPopoverHide() {
431
+ showPicker.value = false
432
+ }
433
+
434
+ // 移除原来的全局点击事件监听,因为el-popover会处理
435
+ </script>
436
+
437
+ <style scoped>
438
+ /* Element-UI风格输入框 */
439
+
440
+ .el-input__inner {
441
+ flex: 1 1 auto;
442
+ /* 限制Flex容器自动扩展 */
443
+ display: flex;
444
+ align-items: center;
445
+ border: 1px solid #dcdfe6;
446
+ border-radius: 4px;
447
+ background: white;
448
+ cursor: pointer;
449
+ transition: border-color 0.2s;
450
+ height: 26px;
451
+ color: var(--el-input-text-color, var(--el-text-color-regular));
452
+ font-size: 12px;
453
+ padding: 1px 8px;
454
+
455
+ }
456
+
457
+ .el-input__inner:hover {
458
+ border-color: #c0c4cc;
459
+ }
460
+
461
+ .el-input__inner:focus-within {
462
+ border-color: #409eff;
463
+ }
464
+
465
+ .el-input__inner input {
466
+ border: none;
467
+ padding: 0;
468
+ flex: 1;
469
+ cursor: pointer;
470
+ background: transparent;
471
+ height: 100%;
472
+ width: calc(100% - 20px);
473
+ }
474
+
475
+ .el-input__close {
476
+ position: absolute;
477
+ right: 8px;
478
+ /* 明确右侧定位 */
479
+ flex-shrink: 0;
480
+ /* 阻止子元素收缩 */
481
+ color: rgb(181 185 196);
482
+ }
483
+
484
+ /* 日期时间选择面板 */
485
+ .datetime-panel {
486
+ width: 100%;
487
+ background: white;
488
+ border-radius: 4px;
489
+ padding: 16px;
490
+ border: 1px solid #e4e7ed;
491
+ }
492
+
493
+ /* 修改后的日期时间面板布局 */
494
+ .panel-container {
495
+ display: flex;
496
+ flex-direction: row;
497
+ /* 强制水平排列 */
498
+ flex-wrap: nowrap;
499
+ /* 禁止换行 */
500
+ gap: 7px;
501
+ width: 100%;
502
+ /* 填充父容器宽度 */
503
+ }
504
+
505
+ .date-section {
506
+ flex: 1 1 300px;
507
+ /* 最小宽度300px,自动扩展 */
508
+ min-width: 300px;
509
+ /* 保证基础宽度 */
510
+ max-width: 50%;
511
+ /* 最大占50%宽度 */
512
+ border-right: 1px solid #ebeef5;
513
+ padding-right: 20px;
514
+ }
515
+
516
+ .time-section {
517
+ flex: 1 1 200px;
518
+ /* 最小宽度200px,自动扩展 */
519
+ max-width: 50%;
520
+ /* 最大占50%宽度 */
521
+ display: flex;
522
+ flex-direction: column;
523
+ gap: 12px;
524
+ }
525
+
526
+ /* 日历头部 */
527
+ .calendar-header {
528
+ display: flex;
529
+ justify-content: space-between;
530
+ align-items: center;
531
+ margin-bottom: 15px;
532
+ }
533
+
534
+ .calendar-title {
535
+ font-size: 16px;
536
+ font-weight: 500;
537
+ color: #606266;
538
+ }
539
+
540
+ .calendar-nav button {
541
+ background: none;
542
+ border: none;
543
+ cursor: pointer;
544
+ color: #606266;
545
+ font-size: 16px;
546
+ padding: 5px;
547
+ border-radius: 50%;
548
+ width: 30px;
549
+ height: 30px;
550
+ display: flex;
551
+ align-items: center;
552
+ justify-content: center;
553
+ float: left;
554
+ }
555
+
556
+ .calendar-nav button:hover {
557
+ background-color: #f5f7fa;
558
+ color: #409eff;
559
+ }
560
+
561
+ /* 日历网格 */
562
+ .calendar-grid {
563
+ display: grid;
564
+ grid-template-columns: repeat(7, 1fr);
565
+ gap: 2px;
566
+ margin-bottom: 15px;
567
+ }
568
+
569
+ .week-header {
570
+ text-align: center;
571
+ font-size: 12px;
572
+ color: #909399;
573
+ padding: 8px 0;
574
+ font-weight: 500;
575
+ }
576
+
577
+ .calendar-cell {
578
+ width: 30px;
579
+ height: 30px;
580
+ line-height: 30px;
581
+ display: flex;
582
+ align-items: center;
583
+ justify-content: center;
584
+ cursor: pointer;
585
+ border-radius: 4px;
586
+ font-size: 12px;
587
+ position: relative;
588
+ }
589
+
590
+ .calendar-cell:hover {
591
+ background-color: #f2f6fc;
592
+ }
593
+
594
+ .calendar-cell.selected {
595
+ background-color: #409eff;
596
+ color: white;
597
+ border-radius: 50%;
598
+ }
599
+
600
+ .calendar-cell.disabled {
601
+ color: #c0c4cc;
602
+ cursor: not-allowed;
603
+ }
604
+
605
+ .calendar-cell.today:not(.selected) {
606
+ color: #409eff;
607
+ font-weight: 500;
608
+ }
609
+
610
+ /* 时间选择器 */
611
+ .time-selectors {
612
+ display: flex;
613
+ gap: 5px;
614
+ flex-wrap: nowrap;
615
+ justify-content: center;
616
+ }
617
+
618
+ .time-column {
619
+ display: flex;
620
+ flex-direction: column;
621
+ align-items: center;
622
+ flex: 1;
623
+ }
624
+
625
+ .time-label {
626
+ font-size: 13px;
627
+ color: #606266;
628
+ margin-bottom: 8px;
629
+ font-weight: 500;
630
+ }
631
+
632
+ .time-selector {
633
+ width: 55px;
634
+ height: 260px;
635
+ overflow-y: auto;
636
+ border: 1px solid #e4e7ed;
637
+ border-radius: 8px;
638
+ scrollbar-width: thin;
639
+ scrollbar-color: #c0c4cc #f5f7fa;
640
+ /* 隐藏滚动条,但保持可滚动 */
641
+ scrollbar-width: none;
642
+ /* Firefox */
643
+ -ms-overflow-style: none;
644
+ /* IE 10+ */
645
+ }
646
+
647
+ .time-selector::-webkit-scrollbar {
648
+ display: none;
649
+ }
650
+
651
+ .time-selector::-webkit-scrollbar-track {
652
+ background: #f5f7fa;
653
+ border-radius: 3px;
654
+ }
655
+
656
+ .time-selector::-webkit-scrollbar-thumb {
657
+ background: #c0c4cc;
658
+ border-radius: 3px;
659
+ }
660
+
661
+ .time-selector::-webkit-scrollbar-thumb:hover {
662
+ background: #909399;
663
+ }
664
+
665
+ .time-option {
666
+ padding: 5px 0;
667
+ text-align: center;
668
+ cursor: pointer;
669
+ transition: all 0.2s;
670
+ font-size: 12px;
671
+ border-radius: 4px;
672
+ /* margin: 2px 4px; */
673
+ }
674
+
675
+ .time-option:hover {
676
+ background-color: #f5f7fa;
677
+ }
678
+
679
+ .time-option.selected {
680
+ color: #3498db;
681
+ font-weight: 500;
682
+ background-color: #ecf5ff;
683
+ }
684
+
685
+ /* 底部操作按钮 */
686
+ .panel-footer {
687
+ display: flex;
688
+ justify-content: flex-end;
689
+ gap: 10px;
690
+ border-top: 1px solid #e4e7ed;
691
+ padding-top: 12px;
692
+ margin-top: 10px;
693
+ }
694
+
695
+ .el-button {
696
+ padding: 9px 15px;
697
+ border-radius: 4px;
698
+ border: none;
699
+ cursor: pointer;
700
+ font-size: 14px;
701
+ font-weight: 500;
702
+ transition: all 0.2s;
703
+ }
704
+
705
+ .el-button--default {
706
+ background-color: transparent;
707
+ color: #606266;
708
+ }
709
+
710
+ .el-button--default:hover {
711
+ color: #409eff;
712
+ }
713
+
714
+ .el-button--primary {
715
+ background-color: #409eff;
716
+ color: white;
717
+ }
718
+
719
+ .el-button--primary:hover {
720
+ background-color: #66b1ff;
721
+ }
722
+
723
+ /* 蓝色文字 */
724
+ .now-btn {
725
+ color: #409eff;
726
+ /* Element 主蓝 */
727
+ border: none;
728
+ padding: 0 8px;
729
+ background-color: transparent;
730
+ }
731
+
732
+ .now-btn:hover {
733
+ color: #66b1ff;
734
+ /* 悬停更浅一点 */
735
+ }
736
+
737
+ .el-form-item.is-error .el-input__inner {
738
+ border-color: #f56c6c;
739
+ }
740
+ </style>
741
+
742
+ <style>
743
+ /* 全局样式,用于自定义el-popover */
744
+ .datetime-popover {
745
+ padding: 0 !important;
746
+ border: 1px solid #e4e7ed !important;
747
+ }
748
+ </style>