建筑项目指挥台里,"裙楼机电 3 个冲突点"和"地下车库今日验收"同时出现。若验收任务只看自己的状态,用户可以把它标为完成;但关联的机电冲突还没处理,进度看板就提前把这部分工程算进了已完成。
我没有把依赖关系写在说明文字里,而是让每个任务明确列出它依赖哪些问题或前置任务。任务状态变化前,控制器先判断阻塞条件。
问题和任务各自有稳定编号
ts
export type WorkState = 'todo' | 'working' | 'waitingReview' | 'done' | 'blocked'
export interface SiteIssue {
id: string
title: string
severity: 'minor' | 'major' | 'critical'
state: 'open' | 'resolved' | 'verified'
updatedAt: number
}
export interface SiteTask {
id: string
title: string
state: WorkState
dependsOnTaskIds: string[]
blocksOnIssueIds: string[]
progress: number
}
"结构完成""协调中""今日验收"是展示标签,真正的判断使用状态字段和 ID。页面过滤楼层或切换平板布局时,任务引用不会因为数组排序而断开。
完成前先收集阻塞项
ts
export interface BlockReason {
type: 'task' | 'issue'
id: string
title: string
}
export function blockersOf(
task: SiteTask,
tasks: SiteTask[],
issues: SiteIssue[]
): BlockReason[] {
const reasons: BlockReason[] = []
task.dependsOnTaskIds.forEach((id: string) => {
const dependency = tasks.find((item: SiteTask) => item.id === id)
if (dependency && dependency.state !== 'done') {
reasons.push({ type: 'task', id, title: dependency.title })
}
})
task.blocksOnIssueIds.forEach((id: string) => {
const issue = issues.find((item: SiteIssue) => item.id === id)
if (issue && issue.state !== 'verified') {
reasons.push({ type: 'issue', id, title: issue.title })
}
})
return reasons
}
问题标记"已解决"后还不能立即放行验收,必须经过"已验证"。这能区分现场负责人声称修复和验收人员实际确认两个阶段。
状态迁移有明确入口
ts
export function requestDone(
task: SiteTask,
tasks: SiteTask[],
issues: SiteIssue[]
): SiteTask {
const blockers = blockersOf(task, tasks, issues)
if (blockers.length > 0) {
return { ...task, state: 'blocked' }
}
if (task.state !== 'working' && task.state !== 'waitingReview') {
return task
}
return { ...task, state: 'done', progress: 100 }
}
任务卡片点击"完成"时,如果返回 blocked,右侧详情展开阻塞项,而不是简单提示"操作失败"。现场人员能直接看到是"裙楼机电"还是某一项验收问题未闭环。

总体进度使用可验收权重
第一版通常把所有任务的百分比直接平均,尚未验收的大任务也会抬高总进度。我给任务增加权重,且只有状态到 done 才贡献完整权重。
ts
export interface WeightedTask extends SiteTask {
weight: number
}
export function projectProgress(tasks: WeightedTask[]): number {
const weight = tasks.reduce((sum: number, item: WeightedTask) => sum + item.weight, 0)
if (weight === 0) return 0
const completed = tasks.reduce((sum: number, item: WeightedTask) => {
const ratio = item.state === 'done' ? 1 : Math.min(0.95, item.progress / 100)
return sum + item.weight * ratio
}, 0)
return Math.round(completed / weight * 100)
}
未验收任务最多贡献 95%,避免"99%"长期停留却掩盖关键阻塞。这个规则并非施工进度的通用算法,真实项目应由项目管理系统提供权重和验收口径。
实时数据与人工修改并存
页面里的"实时运行"按钮只控制本地演示变化。未来接入现场系统时,每条同步消息携带更新时间,人工刚写入的验收结果不能被更早的远端消息覆盖。
ts
export function acceptRemoteTask(current: SiteTask, remote: SiteTask & { updatedAt: number }, localUpdatedAt: number): SiteTask {
return remote.updatedAt >= localUpdatedAt ? remote : current
}
任务、问题和验收三类状态分别保存后,建筑项目指挥台才能把 72% 总体进度、14 个待处理问题和楼层卡片放在同一套逻辑里。现场问题没有验证完成,依赖它的验收不会被提前关闭。