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
+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 // 未配置resources时,默认100%
}
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,
},
]