v1.9.0-rc.5 - SIT

This commit is contained in:
LINING-PC\lining
2026-02-03 11:23:12 +08:00
parent aacd7457fe
commit 9d5cdef835
10 changed files with 182 additions and 218 deletions
+4 -4
View File
@@ -101,7 +101,7 @@ const applyDataSource = (source: RawDataSource) => {
description: resData.description, description: resData.description,
department: resData.department, department: resData.department,
skills: resData.skills, skills: resData.skills,
utilization: resData.utilization, capacity: resData.capacity,
color: resData.color, color: resData.color,
tasks: resData.tasks || [] tasks: resData.tasks || []
}) })
@@ -293,13 +293,13 @@ const resourceListConfig = computed<ResourceListConfig>(() => ({
formatter: (resource: Resource) => resource.name || '-', formatter: (resource: Resource) => resource.name || '-',
}, },
{ {
key: 'utilization', key: 'capacity',
label: '利用率', label: '利用率',
visible: true, visible: true,
width: 100, width: 100,
formatter: (resource: Resource) => { formatter: (resource: Resource) => {
if (resource.utilization != null) { if (resource.capacity != null) {
return `${resource.utilization}%` return `${resource.capacity}%`
} }
// 简单计算:基于任务数量的利用率 // 简单计算:基于任务数量的利用率
const taskCount = resource.tasks?.length || 0 const taskCount = resource.tasks?.length || 0
+100 -100
View File
@@ -11,7 +11,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 66, "capacity": 66,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -477,7 +477,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 73, "capacity": 73,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -928,7 +928,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 79, "capacity": 79,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -1394,7 +1394,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 64, "capacity": 64,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -1815,7 +1815,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 82, "capacity": 82,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -2266,7 +2266,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 75, "capacity": 75,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -2657,7 +2657,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 93, "capacity": 93,
"color": "#a8a8a8", "color": "#a8a8a8",
"tasks": [ "tasks": [
{ {
@@ -3108,7 +3108,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 73, "capacity": 73,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -3454,7 +3454,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 80, "capacity": 80,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -3920,7 +3920,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 63, "capacity": 63,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -4251,7 +4251,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 86, "capacity": 86,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -4672,7 +4672,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 62, "capacity": 62,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -5018,7 +5018,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 95, "capacity": 95,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -5394,7 +5394,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 87, "capacity": 87,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -5785,7 +5785,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 73, "capacity": 73,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -6221,7 +6221,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 67, "capacity": 67,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -6537,7 +6537,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 95, "capacity": 95,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -6988,7 +6988,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 77, "capacity": 77,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -7364,7 +7364,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 63, "capacity": 63,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -7725,7 +7725,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 61, "capacity": 61,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -8161,7 +8161,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 69, "capacity": 69,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -8522,7 +8522,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 83, "capacity": 83,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -8928,7 +8928,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 99, "capacity": 99,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -9304,7 +9304,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 66, "capacity": 66,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -9725,7 +9725,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 97, "capacity": 97,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -10071,7 +10071,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 95, "capacity": 95,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -10462,7 +10462,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 80, "capacity": 80,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -10778,7 +10778,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 82, "capacity": 82,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -11199,7 +11199,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 86, "capacity": 86,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -11605,7 +11605,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 74, "capacity": 74,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -11951,7 +11951,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 73, "capacity": 73,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -12417,7 +12417,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 64, "capacity": 64,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -12823,7 +12823,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 93, "capacity": 93,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -13229,7 +13229,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 62, "capacity": 62,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -13695,7 +13695,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 93, "capacity": 93,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -14146,7 +14146,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 69, "capacity": 69,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -14597,7 +14597,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 88, "capacity": 88,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -14958,7 +14958,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 84, "capacity": 84,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -15364,7 +15364,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 60, "capacity": 60,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -15680,7 +15680,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 81, "capacity": 81,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -16116,7 +16116,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 97, "capacity": 97,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -16492,7 +16492,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 88, "capacity": 88,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -16913,7 +16913,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 90, "capacity": 90,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -17259,7 +17259,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 63, "capacity": 63,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -17590,7 +17590,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 69, "capacity": 69,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -17951,7 +17951,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 85, "capacity": 85,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -18387,7 +18387,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 67, "capacity": 67,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -18733,7 +18733,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 63, "capacity": 63,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -19154,7 +19154,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 78, "capacity": 78,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -19500,7 +19500,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 87, "capacity": 87,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -19921,7 +19921,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 91, "capacity": 91,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -20327,7 +20327,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 65, "capacity": 65,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -20658,7 +20658,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 68, "capacity": 68,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -21034,7 +21034,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 67, "capacity": 67,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -21350,7 +21350,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 72, "capacity": 72,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -21786,7 +21786,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 97, "capacity": 97,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -22252,7 +22252,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 60, "capacity": 60,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -22568,7 +22568,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 96, "capacity": 96,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -22944,7 +22944,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 87, "capacity": 87,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -23380,7 +23380,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 82, "capacity": 82,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -23801,7 +23801,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 74, "capacity": 74,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -24117,7 +24117,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 99, "capacity": 99,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -24538,7 +24538,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 70, "capacity": 70,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -24989,7 +24989,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 91, "capacity": 91,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -25320,7 +25320,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 93, "capacity": 93,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -25726,7 +25726,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 99, "capacity": 99,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -26132,7 +26132,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 68, "capacity": 68,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -26538,7 +26538,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 99, "capacity": 99,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -26944,7 +26944,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 81, "capacity": 81,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -27410,7 +27410,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 95, "capacity": 95,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -27801,7 +27801,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 98, "capacity": 98,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -28147,7 +28147,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 86, "capacity": 86,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -28568,7 +28568,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 61, "capacity": 61,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -28989,7 +28989,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 86, "capacity": 86,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -29305,7 +29305,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 97, "capacity": 97,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -29696,7 +29696,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 97, "capacity": 97,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -30117,7 +30117,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 78, "capacity": 78,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -30553,7 +30553,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 66, "capacity": 66,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -30974,7 +30974,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 63, "capacity": 63,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -31335,7 +31335,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 79, "capacity": 79,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -31711,7 +31711,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 72, "capacity": 72,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -32027,7 +32027,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 82, "capacity": 82,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -32373,7 +32373,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 61, "capacity": 61,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -32719,7 +32719,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 89, "capacity": 89,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -33065,7 +33065,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 78, "capacity": 78,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -33381,7 +33381,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 64, "capacity": 64,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -33832,7 +33832,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 68, "capacity": 68,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -34193,7 +34193,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 73, "capacity": 73,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -34524,7 +34524,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 91, "capacity": 91,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -34960,7 +34960,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 96, "capacity": 96,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -35411,7 +35411,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 96, "capacity": 96,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -35802,7 +35802,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 77, "capacity": 77,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -36193,7 +36193,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 85, "capacity": 85,
"color": "#f44336", "color": "#f44336",
"tasks": [ "tasks": [
{ {
@@ -36524,7 +36524,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 89, "capacity": 89,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -36870,7 +36870,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 79, "capacity": 79,
"color": "#00bcd4", "color": "#00bcd4",
"tasks": [ "tasks": [
{ {
@@ -37201,7 +37201,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 91, "capacity": 91,
"color": "#8bc34a", "color": "#8bc34a",
"tasks": [ "tasks": [
{ {
@@ -37622,7 +37622,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 91, "capacity": 91,
"color": "#ffc107", "color": "#ffc107",
"tasks": [ "tasks": [
{ {
@@ -38073,7 +38073,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 67, "capacity": 67,
"color": "#e91e63", "color": "#e91e63",
"tasks": [ "tasks": [
{ {
@@ -38524,7 +38524,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 77, "capacity": 77,
"color": "#3f51b5", "color": "#3f51b5",
"tasks": [ "tasks": [
{ {
@@ -38870,7 +38870,7 @@
"技能B", "技能B",
"技能C" "技能C"
], ],
"utilization": 65, "capacity": 65,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
+13 -13
View File
@@ -7,7 +7,7 @@
"avatar": "/50-11.jpg", "avatar": "/50-11.jpg",
"department": "临床研究部", "department": "临床研究部",
"skills": ["临床试验管理", "项目规划", "团队领导"], "skills": ["临床试验管理", "项目规划", "团队领导"],
"utilization": 85, "capacity": 85,
"color": "#2196f3", "color": "#2196f3",
"tasks": [ "tasks": [
{ {
@@ -52,7 +52,7 @@
"avatar": "/50-12.jpg", "avatar": "/50-12.jpg",
"department": "I期临床研究组", "department": "I期临床研究组",
"skills": ["I期试验设计", "药代动力学", "安全性评估"], "skills": ["I期试验设计", "药代动力学", "安全性评估"],
"utilization": 92, "capacity": 92,
"color": "#4caf50", "color": "#4caf50",
"tasks": [ "tasks": [
{ {
@@ -97,7 +97,7 @@
"avatar": "/50-13.jpg", "avatar": "/50-13.jpg",
"department": "临床研究部", "department": "临床研究部",
"skills": ["方案设计", "伦理审查", "监管申报"], "skills": ["方案设计", "伦理审查", "监管申报"],
"utilization": 75, "capacity": 75,
"tasks": [ "tasks": [
{ {
"id": 1101, "id": 1101,
@@ -131,7 +131,7 @@
"avatar": "/50-14.jpg", "avatar": "/50-14.jpg",
"department": "数据管理与统计部", "department": "数据管理与统计部",
"skills": ["统计分析", "数据可视化", "报告撰写"], "skills": ["统计分析", "数据可视化", "报告撰写"],
"utilization": 68, "capacity": 68,
"color": "#ff9800", "color": "#ff9800",
"tasks": [ "tasks": [
{ {
@@ -176,7 +176,7 @@
"avatar": "/50-15.jpg", "avatar": "/50-15.jpg",
"department": "质量保证部", "department": "质量保证部",
"skills": ["GCP审计", "质量控制", "SOP管理"], "skills": ["GCP审计", "质量控制", "SOP管理"],
"utilization": 88, "capacity": 88,
"tasks": [ "tasks": [
{ {
"id": 1104, "id": 1104,
@@ -211,7 +211,7 @@
"avatar": "/50-16.jpg", "avatar": "/50-16.jpg",
"department": "II期临床研究组", "department": "II期临床研究组",
"skills": ["试验协调", "中心管理", "受试者跟踪"], "skills": ["试验协调", "中心管理", "受试者跟踪"],
"utilization": 55, "capacity": 55,
"tasks": [ "tasks": [
{ {
"id": 1200, "id": 1200,
@@ -238,7 +238,7 @@
"avatar": "/50-17.jpg", "avatar": "/50-17.jpg",
"department": "设备与后勤部", "department": "设备与后勤部",
"skills": ["设备维护", "库存管理", "供应链协调"], "skills": ["设备维护", "库存管理", "供应链协调"],
"utilization": 42, "capacity": 42,
"tasks": [ "tasks": [
{ {
"id": 5011, "id": 5011,
@@ -265,7 +265,7 @@
"avatar": "/50-18.jpg", "avatar": "/50-18.jpg",
"department": "III期临床研究组", "department": "III期临床研究组",
"skills": ["大型试验管理", "多中心协调", "里程碑管理"], "skills": ["大型试验管理", "多中心协调", "里程碑管理"],
"utilization": 35, "capacity": 35,
"color": "#9c27b0", "color": "#9c27b0",
"tasks": [ "tasks": [
{ {
@@ -285,7 +285,7 @@
"avatar": "/50-19.jpg", "avatar": "/50-19.jpg",
"department": "数据管理与统计部", "department": "数据管理与统计部",
"skills": ["统计建模", "样本量计算", "数据分析"], "skills": ["统计建模", "样本量计算", "数据分析"],
"utilization": 95, "capacity": 95,
"color": "#ff5733", "color": "#ff5733",
"tasks": [ "tasks": [
{ {
@@ -341,7 +341,7 @@
"avatar": "/50-20.jpg", "avatar": "/50-20.jpg",
"department": "临床监查部", "department": "临床监查部",
"skills": ["现场监查", "数据核查", "问题解决"], "skills": ["现场监查", "数据核查", "问题解决"],
"utilization": 110, "capacity": 110,
"color": "#3498db", "color": "#3498db",
"tasks": [ "tasks": [
{ {
@@ -397,7 +397,7 @@
"avatar": "/50-21.jpg", "avatar": "/50-21.jpg",
"department": "医学事务部", "department": "医学事务部",
"skills": ["研究报告撰写", "文献检索", "医学翻译"], "skills": ["研究报告撰写", "文献检索", "医学翻译"],
"utilization": 72, "capacity": 72,
"color": "#e74c3c", "color": "#e74c3c",
"tasks": [ "tasks": [
{ {
@@ -442,7 +442,7 @@
"avatar": "/50-22.jpg", "avatar": "/50-22.jpg",
"department": "药物警戒部", "department": "药物警戒部",
"skills": ["不良事件管理", "安全性报告", "信号检测"], "skills": ["不良事件管理", "安全性报告", "信号检测"],
"utilization": 88, "capacity": 88,
"color": "#f39c12", "color": "#f39c12",
"tasks": [ "tasks": [
{ {
@@ -478,7 +478,7 @@
"avatar": "/50-23.jpg", "avatar": "/50-23.jpg",
"department": "注册事务部", "department": "注册事务部",
"skills": ["IND申报", "监管沟通", "文件管理"], "skills": ["IND申报", "监管沟通", "文件管理"],
"utilization": 65, "capacity": 65,
"tasks": [ "tasks": [
{ {
"id": 6015, "id": 6015,
+1 -1
View File
@@ -56,7 +56,7 @@ function generateLargeResourceData() {
avatar: `https://i.pravatar.cc/150?img=${i}`, avatar: `https://i.pravatar.cc/150?img=${i}`,
department: departments[i % departments.length], department: departments[i % departments.length],
skills: ['技能A', '技能B', '技能C'], skills: ['技能A', '技能B', '技能C'],
utilization: 60 + Math.floor(Math.random() * 40), capacity: 60 + Math.floor(Math.random() * 40),
color: colors[i % colors.length], color: colors[i % colors.length],
tasks, tasks,
}) })
+14 -43
View File
@@ -28,6 +28,7 @@ import {
} from '../models/configs/TaskListConfig' } from '../models/configs/TaskListConfig'
import type { TaskBarConfig } from '../models/configs/TaskBarConfig' import type { TaskBarConfig } from '../models/configs/TaskBarConfig'
import { TimelineScale, SCALE_CONFIGS } from '../models/types/TimelineScale' import { TimelineScale, SCALE_CONFIGS } from '../models/types/TimelineScale'
import { detectConflicts } from '../utils/conflictUtils'
import { useMessage } from '../composables/useMessage' import { useMessage } from '../composables/useMessage'
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -189,6 +190,7 @@ const resourceRowPositions = computed(() => {
}) })
// updateTaskTrigger便 // updateTaskTrigger便
// v1.9.9 使 detectConflicts
const resourceConflicts = computed(() => { const resourceConflicts = computed(() => {
if (currentViewMode.value !== 'resource') return new Map() if (currentViewMode.value !== 'resource') return new Map()
@@ -198,38 +200,22 @@ const resourceConflicts = computed(() => {
// updateTaskTrigger 便 // updateTaskTrigger 便
if (updateTaskTrigger.value >= 0) { if (updateTaskTrigger.value >= 0) {
resources.forEach(resource => { resources.forEach(resource => {
// v1.9.2 使 Resource.isOverloaded()
// > 100%
if (typeof resource.isOverloaded === 'function' && resource.isOverloaded()) {
const conflicts = new Set<number>()
const tasks = resource.tasks || [] const tasks = resource.tasks || []
const validTasks = tasks.filter(task => task.startDate && task.endDate) if (tasks.length < 2) return
// // 使 conflictUtils detectConflicts
for (let i = 0; i < validTasks.length; i++) { // A:40% + B:40% + C:30% = 110%
for (let j = i + 1; j < validTasks.length; j++) { const conflictZones = detectConflicts(tasks, resource.id)
const task1 = validTasks[i]
const task2 = validTasks[j]
// if (conflictZones.length > 0) {
const start1 = new Date(task1.startDate!).getTime() const conflicts = new Set<number>()
const end1 = new Date(task1.endDate!).getTime()
const start2 = new Date(task2.startDate!).getTime()
const end2 = new Date(task2.endDate!).getTime()
if (start1 < end2 && start2 < end1) { // ID
// 100% conflictZones.forEach(zone => {
const percent1 = getTaskAllocationPercent(resource, task1) zone.tasks.forEach(taskInfo => {
const percent2 = getTaskAllocationPercent(resource, task2) conflicts.add(taskInfo.id)
})
if (percent1 + percent2 > 100) { })
//
conflicts.add(task1.id)
conflicts.add(task2.id)
}
}
}
}
if (conflicts.size > 0) { if (conflicts.size > 0) {
conflictsMap.set(String(resource.id), conflicts) conflictsMap.set(String(resource.id), conflicts)
@@ -241,21 +227,6 @@ const resourceConflicts = computed(() => {
return conflictsMap return conflictsMap
}) })
//
function getTaskAllocationPercent(resource: Resource, task: Task): number {
if (!task.resources || !Array.isArray(task.resources)) {
return 100 // resources100%
}
const allocation = task.resources.find((r: any) => String(r.id) === String(resource.id))
if (!allocation) {
return 100 // 100%
}
const percent = allocation.percent ?? 100
return Math.max(20, Math.min(100, percent)) // 20-100
}
// //
provide('resourceTaskLayouts', resourceTaskLayouts) provide('resourceTaskLayouts', resourceTaskLayouts)
provide('resourceRowPositions', resourceRowPositions) provide('resourceRowPositions', resourceRowPositions)
+4 -4
View File
@@ -47,7 +47,7 @@ defineSlots<{
// GanttChart enableLinkAnchor // GanttChart enableLinkAnchor
const enableLinkAnchor = inject<ComputedRef<boolean>>('enable-link-anchor', computed(() => true)) const enableLinkAnchor = inject<ComputedRef<boolean>>('enable-link-anchor', computed(() => true))
// v1.9.0 // v1.9.0
const resourcePercent = computed(() => { const resourcePercent = computed(() => {
// 使 // 使
if (props.resourceAllocationPercent !== undefined) { if (props.resourceAllocationPercent !== undefined) {
@@ -156,7 +156,7 @@ interface Props {
hasResourceConflict?: boolean hasResourceConflict?: boolean
// v1.9.2 // v1.9.2
conflictTasks?: Task[] conflictTasks?: Task[]
// v1.9.0 (20-100) // v1.9.0 (20-100)
resourceAllocationPercent?: number resourceAllocationPercent?: number
// v1.9.0 ID // v1.9.0 ID
currentResourceId?: string | number currentResourceId?: string | number
@@ -2694,7 +2694,7 @@ const handleTaskBarMouseEnter = (event: MouseEvent) => {
const rowHeight = 24 // const rowHeight = 24 //
let contentRows = 4 // 4 let contentRows = 4 // 4
// <100%1 // <100%1
if (viewMode.value === 'resource' && resourcePercent.value < 100) { if (viewMode.value === 'resource' && resourcePercent.value < 100) {
contentRows += 1 contentRows += 1
} }
@@ -3636,7 +3636,7 @@ const handleAnchorDragEnd = (anchorEvent: { taskId: number; type: 'predecessor'
<div class="tooltip-arrow"></div> <div class="tooltip-arrow"></div>
<div class="tooltip-title">{{ task.name }}</div> <div class="tooltip-title">{{ task.name }}</div>
<div class="tooltip-content"> <div class="tooltip-content">
<!-- v1.9.0 资源视图显示投入占比 --> <!-- v1.9.0 资源视图显示利用率 -->
<div v-if="viewMode === 'resource' && resourcePercent < 100" class="tooltip-row"> <div v-if="viewMode === 'resource' && resourcePercent < 100" class="tooltip-row">
<span class="tooltip-label">{{ t('investment') || '投入' }}:</span> <span class="tooltip-label">{{ t('investment') || '投入' }}:</span>
<span class="tooltip-value">{{ resourcePercent }}%</span> <span class="tooltip-value">{{ resourcePercent }}%</span>
+7 -2
View File
@@ -130,6 +130,9 @@ const getConflictTasksForTask = (resourceId: string | number, taskId: string | n
// v1.9.7 // v1.9.7
// 100%resourceConflictsID // 100%resourceConflictsID
// 375% // 375%
// v1.9.9 endDate +1 conflictUtils
const currentEndPlus = currentEnd + 24 * 60 * 60 * 1000
const conflictTasks = resource.tasks.filter(task => { const conflictTasks = resource.tasks.filter(task => {
if (task.id === taskId) return false if (task.id === taskId) return false
if (!task.startDate || !task.endDate) return false if (!task.startDate || !task.endDate) return false
@@ -138,9 +141,11 @@ const getConflictTasksForTask = (resourceId: string | number, taskId: string | n
const taskStart = new Date(task.startDate).getTime() const taskStart = new Date(task.startDate).getTime()
const taskEnd = new Date(task.endDate).getTime() const taskEnd = new Date(task.endDate).getTime()
const taskEndPlus = taskEnd + 24 * 60 * 60 * 1000
// // endDate +1
return currentStart < taskEnd && taskStart < currentEnd // A endDate=12-24, B startDate=12-2412-24
return currentStart < taskEndPlus && taskStart < currentEndPlus
}) })
return conflictTasks return conflictTasks
+22 -11
View File
@@ -197,7 +197,7 @@ const conflictInfoList = computed(() => {
// //
const currentPercent = props.resourcePercent || 100 const currentPercent = props.resourcePercent || 100
// v1.9.8 // v1.9.8
return props.conflictTasks.map(conflictTask => { return props.conflictTasks.map(conflictTask => {
if (!conflictTask.startDate || !conflictTask.endDate) return null if (!conflictTask.startDate || !conflictTask.endDate) return null
@@ -228,7 +228,7 @@ const conflictInfoList = computed(() => {
taskName: conflictTask.name, taskName: conflictTask.name,
overlapStart: formatDate(overlapStart), overlapStart: formatDate(overlapStart),
overlapEnd: formatDate(overlapEnd), overlapEnd: formatDate(overlapEnd),
conflictPercent, // conflictPercent, //
} }
}).filter(Boolean) }).filter(Boolean)
}) })
@@ -246,6 +246,9 @@ const totalOverloadPercent = computed(() => {
const currentEnd = new Date(currentTask.endDate).getTime() const currentEnd = new Date(currentTask.endDate).getTime()
const currentPercent = props.resourcePercent || 100 const currentPercent = props.resourcePercent || 100
// v1.9.9 endDate +1
const DAY_MS = 24 * 60 * 60 * 1000
// //
let maxTotalPercent = currentPercent let maxTotalPercent = currentPercent
@@ -257,26 +260,31 @@ const totalOverloadPercent = computed(() => {
if (!task1.startDate || !task1.endDate) return if (!task1.startDate || !task1.endDate) return
const start1 = new Date(task1.startDate).getTime() const start1 = new Date(task1.startDate).getTime()
const end1 = new Date(task1.endDate).getTime() const end1 = new Date(task1.endDate).getTime()
const end1Plus = end1 + DAY_MS // endDate +1
allTasks.forEach((task2, j) => { allTasks.forEach((task2, j) => {
if (i >= j || !task2.startDate || !task2.endDate) return if (i >= j || !task2.startDate || !task2.endDate) return
const start2 = new Date(task2.startDate).getTime() const start2 = new Date(task2.startDate).getTime()
const end2 = new Date(task2.endDate).getTime() const end2 = new Date(task2.endDate).getTime()
const end2Plus = end2 + DAY_MS // endDate +1
// // 使 +1 endDate
if (start1 < end2 && start2 < end1) { // A endDate=12-24, B startDate=12-24
if (start1 < end2Plus && start2 < end1Plus) {
// //
const overlapStart = Math.max(start1, start2) const overlapStart = Math.max(start1, start2)
const overlapEnd = Math.min(end1, end2) const overlapEnd = Math.min(end1, end2)
const overlapEndPlus = overlapEnd + DAY_MS
let intervalTotal = 0 let intervalTotal = 0
allTasks.forEach(task => { allTasks.forEach(task => {
if (!task.startDate || !task.endDate) return if (!task.startDate || !task.endDate) return
const tStart = new Date(task.startDate).getTime() const tStart = new Date(task.startDate).getTime()
const tEnd = new Date(task.endDate).getTime() const tEnd = new Date(task.endDate).getTime()
const tEndPlus = tEnd + DAY_MS
// // 使 +1 endDate
if (tStart < overlapEnd && tEnd > overlapStart) { if (tStart < overlapEndPlus && tEndPlus > overlapStart) {
let taskPercent = 100 let taskPercent = 100
if (task.resources && Array.isArray(task.resources)) { if (task.resources && Array.isArray(task.resources)) {
const allocation = task.resources.find( const allocation = task.resources.find(
@@ -422,9 +430,9 @@ onUnmounted(() => {
<!-- 内容区域 --> <!-- 内容区域 -->
<div class="expanded-body"> <div class="expanded-body">
<!-- 投入占比 --> <!-- 利用率 -->
<div class="expanded-row"> <div class="expanded-row">
<span class="info-label">投入占比</span> <span class="info-label">利用率</span>
<span class="info-value">{{ percentText }}</span> <span class="info-value">{{ percentText }}</span>
</div> </div>
<!-- 日期范围 --> <!-- 日期范围 -->
@@ -440,7 +448,7 @@ onUnmounted(() => {
<path fill="currentColor" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/> <path fill="currentColor" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
</svg> </svg>
<span class="conflict-title">资源超载警告</span> <span class="conflict-title">资源超载警告</span>
<span class="total-overload">+{{ totalOverloadPercent }}%</span> <span class="total-overload"> {{ totalOverloadPercent }}%</span>
</div> </div>
<!-- 可滚动的冲突列表 --> <!-- 可滚动的冲突列表 -->
<div class="conflict-list-container"> <div class="conflict-list-container">
@@ -451,7 +459,7 @@ onUnmounted(() => {
<span class="conflict-value">{{ info.overlapStart }} ~ {{ info.overlapEnd }}</span> <span class="conflict-value">{{ info.overlapStart }} ~ {{ info.overlapEnd }}</span>
</div> </div>
<div class="conflict-detail"> <div class="conflict-detail">
<span class="conflict-label">任务投入占比</span> <span class="conflict-label">任务利用率</span>
<span class="conflict-value">{{ info.conflictPercent }}%</span> <span class="conflict-value">{{ info.conflictPercent }}%</span>
</div> </div>
</div> </div>
@@ -598,10 +606,13 @@ onUnmounted(() => {
} }
.total-overload { .total-overload {
font-size: 13px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: #ff5252; color: #ff5252;
margin-left: auto; margin-left: auto;
background: #FFC107;
padding: 2px 4px;
border-radius: 4px;
} }
.conflict-item { .conflict-item {
+11 -34
View File
@@ -1,4 +1,5 @@
import type { Task } from './Task' import type { Task } from './Task'
import { detectConflicts } from '../../utils/conflictUtils'
/** /**
* (Resource Class) * (Resource Class)
@@ -15,7 +16,7 @@ export class Resource {
description?: string description?: string
department?: string department?: string
skills?: string[] skills?: string[]
utilization?: number capacity?: number
color?: string // 自定义资源行左边框颜色,如 '#ff5733',若不设置则使用默认颜色方案 color?: string // 自定义资源行左边框颜色,如 '#ff5733',若不设置则使用默认颜色方案
tasks: Task[] tasks: Task[]
[key: string]: unknown [key: string]: unknown
@@ -28,7 +29,7 @@ export class Resource {
description?: string description?: string
department?: string department?: string
skills?: string[] skills?: string[]
utilization?: number capacity?: number
color?: string color?: string
tasks?: Task[] tasks?: Task[]
[key: string]: unknown [key: string]: unknown
@@ -40,7 +41,7 @@ export class Resource {
this.description = data.description this.description = data.description
this.department = data.department this.department = data.department
this.skills = data.skills this.skills = data.skills
this.utilization = data.utilization this.capacity = data.capacity
this.color = data.color this.color = data.color
this.tasks = data.tasks || [] this.tasks = data.tasks || []
@@ -89,7 +90,7 @@ export class Resource {
* *
*/ */
updateUtilization(): void { updateUtilization(): void {
this.utilization = this.calculateUtilization() this.capacity = this.calculateUtilization()
} }
/** /**
@@ -130,45 +131,21 @@ export class Resource {
/** /**
* v1.9.0 * v1.9.0
* > 100% * > 100%
* v1.9.9 使 detectConflicts
* @returns * @returns
*/ */
isOverloaded(): boolean { isOverloaded(): boolean {
if (this.tasks.length < 2) return false if (this.tasks.length < 2) return false
// 过滤掉没有开始日期和结束日期的任务 // 使用 conflictUtils 的 detectConflicts 函数来检测冲突
const validTasks = this.tasks.filter(task => task.startDate && task.endDate) // 这个函数能正确处理多任务叠加的超载情况(如 A:40% + B:40% + C:30% = 110%
if (validTasks.length < 2) return false const conflictZones = detectConflicts(this.tasks, this.id)
// 检测任意时间点的总占比是否超过100% return conflictZones.length > 0
for (let i = 0; i < validTasks.length; i++) {
for (let j = i + 1; j < validTasks.length; j++) {
const task1 = validTasks[i]
const task2 = validTasks[j]
// 检查两个任务是否有时间交集
const start1 = new Date(task1.startDate!).getTime()
const end1 = new Date(task1.endDate!).getTime()
const start2 = new Date(task2.startDate!).getTime()
const end2 = new Date(task2.endDate!).getTime()
// 判断时间是否重叠
if (start1 < end2 && start2 < end1) {
// 有重叠,计算总占比
const percent1 = this.getTaskAllocationPercent(task1)
const percent2 = this.getTaskAllocationPercent(task2)
if (percent1 + percent2 > 100) {
return true // 超负荷
}
}
}
}
return false
} }
/** /**
* v1.9.0 * v1.9.0
* @param task * @param task
* @returns (20-100)100 * @returns (20-100)100
*/ */
+4 -4
View File
@@ -7,7 +7,7 @@ export type ResourceListColumnType =
| 'name' | 'name'
| 'type' | 'type'
| 'department' | 'department'
| 'utilization' | 'capacity'
| 'taskCount' | 'taskCount'
/** /**
@@ -71,10 +71,10 @@ export const DEFAULT_RESOURCE_LIST_COLUMNS: ResourceListColumnConfig[] = [
visible: true, visible: true,
}, },
{ {
type: 'utilization', type: 'capacity',
key: 'utilization', key: 'capacity',
label: '利用率', label: '利用率',
cssClass: 'col-utilization', cssClass: 'col-capacity',
visible: true, visible: true,
}, },
] ]