| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- <div class="project-timeline-container">
- <!-- 顶部筛选栏 -->
- <div class="timeline-header">
- <div class="filter-section">
- <!-- 设计师选择 -->
- <div class="filter-group">
- <label>设计师:</label>
- <select [(ngModel)]="selectedDesigner" (change)="applyFilters()" class="filter-select">
- <option value="all">全部设计师</option>
- @for (designer of designers; track designer.id) {
- <option [value]="designer.id">
- {{ designer.name }} ({{ designer.projectCount }})
- </option>
- }
- </select>
- </div>
- <!-- 快捷筛选按钮 -->
- <div class="filter-group quick-filters">
- <button
- class="filter-btn"
- [class.active]="selectedStatus === 'overdue'"
- (click)="toggleFilter('status', 'overdue')">
- 🔴 逾期
- </button>
- <button
- class="filter-btn"
- [class.active]="selectedStatus === 'urgent'"
- (click)="toggleFilter('status', 'urgent')">
- 🟠 紧急
- </button>
- <button
- class="filter-btn"
- [class.active]="selectedStatus === 'stalled'"
- (click)="toggleFilter('status', 'stalled')">
- ⏸️ 停滞
- </button>
- </div>
- <!-- 时间尺度切换 -->
- <div class="filter-group time-scale-controls">
- <label>时间范围:</label>
- <button
- class="scale-btn"
- [class.active]="timelineScale === 'week'"
- (click)="toggleTimelineScale('week')">
- 📆 7天
- </button>
- <button
- class="scale-btn"
- [class.active]="timelineScale === 'month'"
- (click)="toggleTimelineScale('month')">
- 📅 30天
- </button>
- </div>
-
- <!-- 🆕 手动刷新按钮 -->
- <div class="filter-group refresh-controls">
- <button
- class="refresh-btn"
- (click)="refresh()"
- title="刷新数据和时间线(自动10分钟刷新一次)">
- 🔄 刷新
- </button>
- </div>
- <!-- 排序方式 -->
- <div class="filter-group sort-controls">
- <button
- class="sort-btn"
- [class.active]="sortBy === 'priority'"
- (click)="toggleSortBy('priority')">
- 按优先级
- </button>
- <button
- class="sort-btn"
- [class.active]="sortBy === 'time'"
- (click)="toggleSortBy('time')">
- 按时间
- </button>
- </div>
- </div>
- <!-- 设计师统计面板(选中时显示) -->
- @if (selectedDesigner !== 'all') {
- <div class="designer-stats-panel">
- @if (getSelectedDesigner(); as designer) {
- <div class="stats-header">
- <h3>{{ designer.name }}</h3>
- <span class="workload-badge" [class]="'level-' + designer.workloadLevel">
- {{ getWorkloadIcon(designer.workloadLevel) }}
- @if (designer.workloadLevel === 'high') { 超负荷 }
- @else if (designer.workloadLevel === 'medium') { 适度忙碌 }
- @else { 空闲 }
- </span>
- </div>
- <div class="stats-body">
- <div class="stat-item">
- <span class="stat-label">总项目数</span>
- <span class="stat-value">{{ designer.projectCount }}</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">紧急项目</span>
- <span class="stat-value urgent">{{ designer.urgentCount }}</span>
- </div>
- <div class="stat-item">
- <span class="stat-label">逾期项目</span>
- <span class="stat-value overdue">{{ designer.overdueCount }}</span>
- </div>
- </div>
- }
- </div>
- }
- </div>
- <!-- 时间轴主体 -->
- <div class="timeline-body timeline-view">
- <!-- 时间轴视图 -->
- <div class="timeline-view-container">
- <!-- 图例说明 -->
- <div class="timeline-legend">
- <div class="legend-item">
- <span class="legend-icon start-icon">▶️</span>
- <span class="legend-label">项目开始</span>
- </div>
- <div class="legend-item">
- <span class="legend-icon review-icon">📋</span>
- <span class="legend-label">小图对图(灰色=已完成)</span>
- </div>
- <div class="legend-item">
- <span class="legend-icon delivery-icon">📦</span>
- <span class="legend-label">交付日期</span>
- </div>
- <div class="legend-separator"></div>
- <div class="legend-item legend-phase">
- <span class="legend-icon phase-icon modeling-icon">建</span>
- <span class="legend-label">建模截止</span>
- </div>
- <div class="legend-item legend-phase">
- <span class="legend-icon phase-icon softDecor-icon">软</span>
- <span class="legend-label">软装截止</span>
- </div>
- <div class="legend-item legend-phase">
- <span class="legend-icon phase-icon rendering-icon">渲</span>
- <span class="legend-label">渲染截止</span>
- </div>
- <div class="legend-item legend-phase">
- <span class="legend-icon phase-icon postProcessing-icon">后</span>
- <span class="legend-label">后期截止</span>
- </div>
- <div class="legend-separator"></div>
- <div class="legend-item">
- <div class="legend-bar-demo legend-bar-green"></div>
- <span class="legend-label">🟢 正常进行(2天+)</span>
- </div>
- <div class="legend-item">
- <div class="legend-bar-demo legend-bar-yellow"></div>
- <span class="legend-label">🟡 前一天(24小时内)</span>
- </div>
- <div class="legend-item">
- <div class="legend-bar-demo legend-bar-orange"></div>
- <span class="legend-label">🟠 事件当天(6小时+)</span>
- </div>
- <div class="legend-item">
- <div class="legend-bar-demo legend-bar-red"></div>
- <span class="legend-label">🔴 紧急(6小时内)</span>
- </div>
- <div class="legend-item legend-note">
- <span class="legend-label">💡 仅显示今日线之后的关键事件和阶段截止时间</span>
- </div>
- </div>
-
- <!-- 时间刻度尺 -->
- <div class="timeline-ruler">
- <div class="ruler-header">
- <span class="project-name-header">项目名称</span>
- </div>
- <div class="ruler-ticks">
- @for (date of timeRange; track date; let i = $index) {
- <div class="ruler-tick" [class.first]="i === 0">
- <div class="tick-date">{{ date.getMonth() + 1 }}/{{ date.getDate() }}</div>
- @if (timelineScale === 'week') {
- <div class="tick-weekday">
- @switch (date.getDay()) {
- @case (0) { 周日 }
- @case (1) { 周一 }
- @case (2) { 周二 }
- @case (3) { 周三 }
- @case (4) { 周四 }
- @case (5) { 周五 }
- @case (6) { 周六 }
- }
- </div>
- }
- </div>
- }
- </div>
- </div>
-
- <!-- 今日标记线(实时移动,精确到分钟) -->
- <div class="today-line"
- [style.left]="getTodayPosition()">
- <div class="today-label">
- {{ getTodayLabel() }}
- </div>
- <div class="today-dot"></div>
- <div class="today-bar"></div>
- </div>
-
- <!-- 项目时间轴 -->
- <div class="timeline-projects">
- @if (filteredProjects.length === 0) {
- <div class="empty-state">
- <p>暂无项目数据</p>
- </div>
- } @else {
- @for (project of filteredProjects; track project.projectId) {
- <div class="timeline-row" (click)="onProjectClick(project.projectId)">
- <!-- 项目名称标签 -->
- <div class="project-label">
- <span class="project-name-label" [title]="project.projectName">
- {{ project.projectName }}
- </span>
- <span class="designer-label">{{ project.designerName }}</span>
- @if (project.priority === 'critical' || project.priority === 'high') {
- <span class="priority-badge" [class]="'badge-' + project.priority">
- @if (project.priority === 'critical') { ‼️ }
- @else { 🔥 }
- </span>
- }
- <!-- 🆕 空间与交付物统计徽章 -->
- @if (getSpaceDeliverableSummary(project.projectId); as summary) {
- <span class="space-deliverable-badge"
- [title]="formatSpaceDeliverableTooltip(project.projectId)"
- [style.background-color]="getProjectDeliveryStatusColor(project.projectId)">
- 📦 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}
- </span>
- }
- </div>
-
- <!-- 时间轴区域 -->
- <div class="timeline-track">
- <!-- 项目条形图 -->
- <div class="project-bar"
- [style.left]="getProjectPosition(project).left"
- [style.width]="getProjectPosition(project).width"
- [style.background]="getProjectPosition(project).background"
- [class.status-overdue]="project.status === 'overdue'"
- [title]="project.projectName + ' | ' + project.stageName + ' ' + project.stageProgress + '%'">
- <!-- 进度填充 -->
- <div class="progress-fill" [style.width]="project.stageProgress + '%'"></div>
- </div>
-
- <!-- 🆕 使用统一的事件标记方法 -->
- @for (event of getProjectEvents(project); track event.date) {
- <div class="event-marker"
- [class]="event.type"
- [class.phase-deadline]="event.type === 'phase_deadline'"
- [style.left]="getEventPosition(event.date)"
- [style.background]="event.color"
- [class.blink]="project.status === 'overdue' && event.type === 'delivery'"
- [title]="event.label + ':' + formatTime(event.date) + (event.phase ? ' (' + getPhaseLabel(event.phase) + ')' : '')">
- {{ event.icon }}
- </div>
- }
- </div>
- </div>
- }
- }
- </div>
- </div>
- </div>
- </div>
|