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