|
|
@@ -1,15 +1,14 @@
|
|
1
|
1
|
<script setup>
|
|
2
|
|
-import { ref, computed, watch } from 'vue';
|
|
|
2
|
+import { ref, computed, watch, defineEmits } from 'vue';
|
|
3
|
3
|
import { ElCalendar, ElTooltip } from 'element-plus';
|
|
4
|
|
-
|
|
|
4
|
+import dayjs from 'dayjs';
|
|
5
|
5
|
// Props from parent component
|
|
6
|
|
-const props = defineProps({
|
|
7
|
|
- month: {
|
|
8
|
|
- type: Number,
|
|
9
|
|
- required: true,
|
|
10
|
|
- default: '',
|
|
11
|
|
- },
|
|
12
|
|
-});
|
|
|
6
|
+const props = defineProps(['month']);
|
|
|
7
|
+// const emit = defineEmits(['update:month']);
|
|
|
8
|
+// 控制展开状态
|
|
|
9
|
+const expandedDates = ref({});
|
|
|
10
|
+// 最大显示任务数
|
|
|
11
|
+const MAX_DISPLAY_TASKS = 4;
|
|
13
|
12
|
// 模拟任务数据
|
|
14
|
13
|
const tasks = ref([
|
|
15
|
14
|
{
|
|
|
@@ -252,8 +251,47 @@ const tasks = ref([
|
|
252
|
251
|
status: 'cancelled',
|
|
253
|
252
|
department: '人力资源部',
|
|
254
|
253
|
},
|
|
|
254
|
+ {
|
|
|
255
|
+ id: 31,
|
|
|
256
|
+ name: '龙飞街量缸试水检查',
|
|
|
257
|
+ date: '2025-12-17',
|
|
|
258
|
+ start: '07:59',
|
|
|
259
|
+ status: 'processable',
|
|
|
260
|
+ department: '管理部',
|
|
|
261
|
+ },
|
|
|
262
|
+ {
|
|
|
263
|
+ id: 32,
|
|
|
264
|
+ name: '龙飞街量缸试水检查',
|
|
|
265
|
+ date: '2025-12-17',
|
|
|
266
|
+ start: '07:59',
|
|
|
267
|
+ status: 'processable',
|
|
|
268
|
+ department: '管理部',
|
|
|
269
|
+ },
|
|
|
270
|
+ {
|
|
|
271
|
+ id: 33,
|
|
|
272
|
+ name: '龙飞街量缸试水检查',
|
|
|
273
|
+ date: '2025-12-17',
|
|
|
274
|
+ start: '07:59',
|
|
|
275
|
+ status: 'processable',
|
|
|
276
|
+ department: '管理部',
|
|
|
277
|
+ },
|
|
|
278
|
+ {
|
|
|
279
|
+ id: 34,
|
|
|
280
|
+ name: '龙飞街量缸试水检查',
|
|
|
281
|
+ date: '2025-12-17',
|
|
|
282
|
+ start: '07:59',
|
|
|
283
|
+ status: 'processable',
|
|
|
284
|
+ department: '管理部',
|
|
|
285
|
+ },
|
|
|
286
|
+ {
|
|
|
287
|
+ id: 35,
|
|
|
288
|
+ name: '龙飞街量缸试水检查',
|
|
|
289
|
+ date: '2025-12-17',
|
|
|
290
|
+ start: '07:59',
|
|
|
291
|
+ status: 'processable',
|
|
|
292
|
+ department: '管理部',
|
|
|
293
|
+ },
|
|
255
|
294
|
]);
|
|
256
|
|
-
|
|
257
|
295
|
// Get task status class based on task properties
|
|
258
|
296
|
const getTaskStatusClass = (task) => {
|
|
259
|
297
|
switch (task.status) {
|
|
|
@@ -275,7 +313,6 @@ const getTaskStatusClass = (task) => {
|
|
275
|
313
|
}
|
|
276
|
314
|
}
|
|
277
|
315
|
};
|
|
278
|
|
-
|
|
279
|
316
|
// Get tasks for a specific date
|
|
280
|
317
|
const getTasksForDate = (date) => {
|
|
281
|
318
|
const dateStr = date.toISOString().split('T')[0];
|
|
|
@@ -286,7 +323,28 @@ const getTasksForDate = (date) => {
|
|
286
|
323
|
return false;
|
|
287
|
324
|
});
|
|
288
|
325
|
};
|
|
|
326
|
+// Toggle expand/collapse for a date
|
|
|
327
|
+const toggleExpand = (date) => {
|
|
|
328
|
+ const dateStr = date.toISOString().split('T')[0];
|
|
|
329
|
+ expandedDates.value[dateStr] = !expandedDates.value[dateStr];
|
|
|
330
|
+};
|
|
289
|
331
|
|
|
|
332
|
+// Get filtered tasks based on expand state
|
|
|
333
|
+const getFilteredTasks = (date) => {
|
|
|
334
|
+ const allTasks = getTasksForDate(date);
|
|
|
335
|
+ const dateStr = date.toISOString().split('T')[0];
|
|
|
336
|
+ const isExpanded = expandedDates.value[dateStr] || false;
|
|
|
337
|
+
|
|
|
338
|
+ if (isExpanded || allTasks.length <= MAX_DISPLAY_TASKS) {
|
|
|
339
|
+ return allTasks;
|
|
|
340
|
+ }
|
|
|
341
|
+ return allTasks.slice(0, MAX_DISPLAY_TASKS);
|
|
|
342
|
+};
|
|
|
343
|
+// Check if a date is expanded
|
|
|
344
|
+const isDateExpanded = (date) => {
|
|
|
345
|
+ const dateStr = date.toISOString().split('T')[0];
|
|
|
346
|
+ return expandedDates.value[dateStr] || false;
|
|
|
347
|
+};
|
|
290
|
348
|
// 状态对应颜色(与其他视图保持一致)
|
|
291
|
349
|
const statusColor = (status) => {
|
|
292
|
350
|
switch (status) {
|
|
|
@@ -302,27 +360,31 @@ const statusColor = (status) => {
|
|
302
|
360
|
return '#CCCCCC';
|
|
303
|
361
|
}
|
|
304
|
362
|
};
|
|
305
|
|
-
|
|
306
|
|
-// Set initial calendar value based on props
|
|
307
|
|
-const initialCalendarValue = computed(() => {
|
|
308
|
|
- if (props.month) {
|
|
309
|
|
- // If month prop is provided, use it to set the calendar month
|
|
310
|
|
- return new Date(`2025-${props.month.toString().padStart(2, '0')}-01`);
|
|
311
|
|
- }
|
|
312
|
|
- // Default to December 2025 where there are tasks
|
|
313
|
|
- return new Date('2025-12-01');
|
|
314
|
|
-});
|
|
|
363
|
+const todayWeek = computed(() => dayjs().day() - 1);
|
|
|
364
|
+// const handleDateClick = (data)=>{
|
|
|
365
|
+// console.log(new Date(data.day));
|
|
|
366
|
+
|
|
|
367
|
+// emit('update:month', new Date(data.day));
|
|
|
368
|
+// }
|
|
|
369
|
+// const isSelectedDate = (dayStr) => {
|
|
|
370
|
+// return dayjs(dayStr).isSame(dayjs(props.month), 'day')
|
|
|
371
|
+// }
|
|
315
|
372
|
</script>
|
|
316
|
373
|
|
|
317
|
374
|
<template>
|
|
318
|
375
|
<div class="month-schedule">
|
|
319
|
|
- <ElCalendar :model-value="initialCalendarValue">
|
|
|
376
|
+ <ElCalendar v-model="props.month">
|
|
320
|
377
|
<template #date-cell="{ data }">
|
|
321
|
|
- <div class="calendar-cell">
|
|
322
|
|
- <div class="cell-date-number">{{ data.date.getDate() }}</div>
|
|
|
378
|
+ <div
|
|
|
379
|
+ class="calendar-cell"
|
|
|
380
|
+ :class="{ expanded: isDateExpanded(data.date) }"
|
|
|
381
|
+ >
|
|
|
382
|
+ <div class="cell-date-number">
|
|
|
383
|
+ {{ data.date.getDate() }}
|
|
|
384
|
+ </div>
|
|
323
|
385
|
<div class="tasks-container">
|
|
324
|
386
|
<div
|
|
325
|
|
- v-for="task in getTasksForDate(data.date)"
|
|
|
387
|
+ v-for="task in getFilteredTasks(data.date)"
|
|
326
|
388
|
:key="task.id"
|
|
327
|
389
|
class="task-item"
|
|
328
|
390
|
:class="getTaskStatusClass(task)"
|
|
|
@@ -336,13 +398,25 @@ const initialCalendarValue = computed(() => {
|
|
336
|
398
|
class="task-dot"
|
|
337
|
399
|
:style="{ backgroundColor: statusColor(task.status) }"
|
|
338
|
400
|
></span>
|
|
339
|
|
- <span v-if="task.start || task.time" class="task-time">{{
|
|
340
|
|
- task.start || task.time
|
|
341
|
|
- }}</span>
|
|
|
401
|
+ <span v-if="task.start || task.time" class="task-time">
|
|
|
402
|
+ {{ task.start || task.time }}
|
|
|
403
|
+ </span>
|
|
342
|
404
|
<span class="task-name">{{ task.name }}</span>
|
|
343
|
405
|
</div>
|
|
344
|
406
|
</ElTooltip>
|
|
345
|
407
|
</div>
|
|
|
408
|
+ <!-- Expand/Collapse button -->
|
|
|
409
|
+ <div
|
|
|
410
|
+ v-if="getTasksForDate(data.date).length > MAX_DISPLAY_TASKS"
|
|
|
411
|
+ class="expand-button"
|
|
|
412
|
+ @click="toggleExpand(data.date)"
|
|
|
413
|
+ >
|
|
|
414
|
+ {{
|
|
|
415
|
+ expandedDates[data.date.toISOString().split('T')[0]]
|
|
|
416
|
+ ? '收起'
|
|
|
417
|
+ : `还有 ${getTasksForDate(data.date).length - MAX_DISPLAY_TASKS} 个日程...`
|
|
|
418
|
+ }}
|
|
|
419
|
+ </div>
|
|
346
|
420
|
</div>
|
|
347
|
421
|
</div>
|
|
348
|
422
|
</template>
|
|
|
@@ -354,6 +428,71 @@ const initialCalendarValue = computed(() => {
|
|
354
|
428
|
:deep(.el-calendar__button-group) {
|
|
355
|
429
|
display: none;
|
|
356
|
430
|
}
|
|
|
431
|
+:deep(.el-calendar-table thead th) {
|
|
|
432
|
+ text-align: left;
|
|
|
433
|
+ padding-left: 10px;
|
|
|
434
|
+ color: #d61111;
|
|
|
435
|
+ font-size: 12px;
|
|
|
436
|
+ font-weight: 400;
|
|
|
437
|
+}
|
|
|
438
|
+
|
|
|
439
|
+/* 今天对应的星期标题高亮 */
|
|
|
440
|
+:deep(.el-calendar-table thead th:nth-child(1)) {
|
|
|
441
|
+ /* background-color: v-bind("todayWeek === 0 ? '#409eff' : 'transparent'"); */
|
|
|
442
|
+ color: v-bind("todayWeek === 0 ? '#409eff' : '#959DA8'");
|
|
|
443
|
+}
|
|
|
444
|
+:deep(.el-calendar-table thead th:nth-child(2)) {
|
|
|
445
|
+ /* background-color: v-bind("todayWeek === 1 ? '#409eff' : 'transparent'"); */
|
|
|
446
|
+ color: v-bind("todayWeek === 1 ? '#409eff' : '#959DA8'");
|
|
|
447
|
+}
|
|
|
448
|
+:deep(.el-calendar-table thead th:nth-child(3)) {
|
|
|
449
|
+ /* background-color: v-bind("todayWeek === 2 ? '#409eff' : 'transparent'"); */
|
|
|
450
|
+ color: v-bind("todayWeek === 2 ? '#409eff' : '#959DA8'");
|
|
|
451
|
+}
|
|
|
452
|
+:deep(.el-calendar-table thead th:nth-child(4)) {
|
|
|
453
|
+ /* background-color: v-bind("todayWeek === 3 ? '#409eff' : 'transparent'"); */
|
|
|
454
|
+ color: v-bind("todayWeek === 3 ? '#409eff' : '#959DA8'");
|
|
|
455
|
+}
|
|
|
456
|
+:deep(.el-calendar-table thead th:nth-child(5)) {
|
|
|
457
|
+ /* background-color: v-bind("todayWeek === 4 ? '#409eff' : 'transparent'"); */
|
|
|
458
|
+ color: v-bind("todayWeek === 4 ? '#409eff' : '#959DA8'");
|
|
|
459
|
+}
|
|
|
460
|
+:deep(.el-calendar-table thead th:nth-child(6)) {
|
|
|
461
|
+ /* background-color: v-bind("todayWeek === 5 ? '#409eff' : 'transparent'"); */
|
|
|
462
|
+ color: v-bind("todayWeek === 5 ? '#409eff' : '#959DA8'");
|
|
|
463
|
+}
|
|
|
464
|
+:deep(.el-calendar-table thead th:nth-child(7)) {
|
|
|
465
|
+ /* background-color: v-bind("todayWeek === 6 ? '#409eff' : 'transparent'"); */
|
|
|
466
|
+ color: v-bind("todayWeek === 6 ? '#409eff' : '#959DA8'");
|
|
|
467
|
+}
|
|
|
468
|
+
|
|
|
469
|
+:deep(.el-calendar-table thead th:nth-child(1)):before {
|
|
|
470
|
+ content: '周';
|
|
|
471
|
+}
|
|
|
472
|
+
|
|
|
473
|
+:deep(.el-calendar-table thead th:nth-child(2)):before {
|
|
|
474
|
+ content: '周';
|
|
|
475
|
+}
|
|
|
476
|
+
|
|
|
477
|
+:deep(.el-calendar-table thead th:nth-child(3)):before {
|
|
|
478
|
+ content: '周';
|
|
|
479
|
+}
|
|
|
480
|
+
|
|
|
481
|
+:deep(.el-calendar-table thead th:nth-child(4)):before {
|
|
|
482
|
+ content: '周';
|
|
|
483
|
+}
|
|
|
484
|
+
|
|
|
485
|
+:deep(.el-calendar-table thead th:nth-child(5)):before {
|
|
|
486
|
+ content: '周';
|
|
|
487
|
+}
|
|
|
488
|
+
|
|
|
489
|
+:deep(.el-calendar-table thead th:nth-child(6)):before {
|
|
|
490
|
+ content: '周';
|
|
|
491
|
+}
|
|
|
492
|
+
|
|
|
493
|
+:deep(.el-calendar-table thead th:nth-child(7)):before {
|
|
|
494
|
+ content: '周';
|
|
|
495
|
+}
|
|
357
|
496
|
.month-schedule {
|
|
358
|
497
|
width: 100%;
|
|
359
|
498
|
overflow-x: auto;
|
|
|
@@ -368,16 +507,39 @@ const initialCalendarValue = computed(() => {
|
|
368
|
507
|
flex-direction: column;
|
|
369
|
508
|
}
|
|
370
|
509
|
|
|
|
510
|
+.calendar-cell.expanded {
|
|
|
511
|
+ position: absolute;
|
|
|
512
|
+ top: 0;
|
|
|
513
|
+ left: 0;
|
|
|
514
|
+ right: 0;
|
|
|
515
|
+ z-index: 10;
|
|
|
516
|
+ background-color: #f9f9f9;
|
|
|
517
|
+ border: 1px solid #e5e5e5;
|
|
|
518
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
519
|
+ padding: 6px 6px 4px 6px;
|
|
|
520
|
+ box-sizing: border-box;
|
|
|
521
|
+ display: flex;
|
|
|
522
|
+ flex-direction: column;
|
|
|
523
|
+ height: auto !important;
|
|
|
524
|
+ min-height: 150px;
|
|
|
525
|
+}
|
|
|
526
|
+
|
|
|
527
|
+::v-deep .el-calendar-day {
|
|
|
528
|
+ position: relative;
|
|
|
529
|
+}
|
|
|
530
|
+
|
|
371
|
531
|
.cell-date-number {
|
|
372
|
532
|
position: absolute;
|
|
373
|
533
|
top: 6px;
|
|
374
|
534
|
left: 8px;
|
|
375
|
535
|
font-size: 12px;
|
|
376
|
|
- color: #666;
|
|
|
536
|
+ /* color: #666; */
|
|
377
|
537
|
font-weight: 600;
|
|
378
|
538
|
z-index: 3;
|
|
379
|
539
|
}
|
|
380
|
|
-
|
|
|
540
|
+.selected {
|
|
|
541
|
+ color: #409eff;
|
|
|
542
|
+}
|
|
381
|
543
|
.tasks-container {
|
|
382
|
544
|
margin-top: 22px;
|
|
383
|
545
|
}
|
|
|
@@ -409,7 +571,7 @@ const initialCalendarValue = computed(() => {
|
|
409
|
571
|
}
|
|
410
|
572
|
|
|
411
|
573
|
.status-indicator.processable {
|
|
412
|
|
- background-color: #000;
|
|
|
574
|
+ background-color: #67c23a !important;
|
|
413
|
575
|
}
|
|
414
|
576
|
|
|
415
|
577
|
.status-indicator.overdue {
|
|
|
@@ -439,14 +601,27 @@ const initialCalendarValue = computed(() => {
|
|
439
|
601
|
flex-direction: column;
|
|
440
|
602
|
} */
|
|
441
|
603
|
|
|
|
604
|
+::v-deep .el-calendar-table__row {
|
|
|
605
|
+ height: 150px !important;
|
|
|
606
|
+}
|
|
|
607
|
+
|
|
442
|
608
|
::v-deep .el-calendar-table__row .current {
|
|
443
|
609
|
height: 150px;
|
|
444
|
610
|
}
|
|
445
|
611
|
|
|
446
|
612
|
::v-deep .el-calendar-table__row .el-calendar-day {
|
|
|
613
|
+ height: 150px !important;
|
|
|
614
|
+ min-height: 150px;
|
|
|
615
|
+}
|
|
|
616
|
+
|
|
|
617
|
+::v-deep .el-calendar-table__row .el-calendar-day.expanded {
|
|
447
|
618
|
height: 150px;
|
|
448
|
619
|
}
|
|
449
|
620
|
|
|
|
621
|
+::v-deep .el-calendar-day {
|
|
|
622
|
+ position: relative;
|
|
|
623
|
+}
|
|
|
624
|
+
|
|
450
|
625
|
.date-text {
|
|
451
|
626
|
font-weight: bold;
|
|
452
|
627
|
margin-bottom: 4px;
|
|
|
@@ -454,6 +629,10 @@ const initialCalendarValue = computed(() => {
|
|
454
|
629
|
|
|
455
|
630
|
.tasks-container {
|
|
456
|
631
|
flex: 1;
|
|
|
632
|
+ overflow-y: visible;
|
|
|
633
|
+}
|
|
|
634
|
+
|
|
|
635
|
+.calendar-cell:not(.expanded) .tasks-container {
|
|
457
|
636
|
overflow-y: auto;
|
|
458
|
637
|
}
|
|
459
|
638
|
|
|
|
@@ -494,6 +673,20 @@ const initialCalendarValue = computed(() => {
|
|
494
|
673
|
flex: 0 0 40px;
|
|
495
|
674
|
}
|
|
496
|
675
|
|
|
|
676
|
+/* Expand button styles */
|
|
|
677
|
+.expand-button {
|
|
|
678
|
+ font-size: 11px;
|
|
|
679
|
+ color: #409eff;
|
|
|
680
|
+ cursor: pointer;
|
|
|
681
|
+ margin-top: 4px;
|
|
|
682
|
+ padding: 2px 0;
|
|
|
683
|
+}
|
|
|
684
|
+
|
|
|
685
|
+.expand-button:hover {
|
|
|
686
|
+ /* color: #409eff; */
|
|
|
687
|
+ text-decoration: underline;
|
|
|
688
|
+}
|
|
|
689
|
+
|
|
497
|
690
|
/* Task status styles */
|
|
498
|
691
|
.task-item.not-started {
|
|
499
|
692
|
color: #999;
|