|
|
@@ -37,50 +37,32 @@
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 视图切换 -->
|
|
|
- <div class="filter-group view-controls">
|
|
|
+ <!-- 时间尺度切换 -->
|
|
|
+ <div class="filter-group time-scale-controls">
|
|
|
+ <label>时间范围:</label>
|
|
|
<button
|
|
|
- class="view-btn"
|
|
|
- [class.active]="viewMode === 'list'"
|
|
|
- (click)="toggleViewMode('list')">
|
|
|
- 📋 列表
|
|
|
+ class="scale-btn"
|
|
|
+ [class.active]="timelineScale === 'week'"
|
|
|
+ (click)="toggleTimelineScale('week')">
|
|
|
+ 📆 7天
|
|
|
</button>
|
|
|
<button
|
|
|
- class="view-btn"
|
|
|
- [class.active]="viewMode === 'timeline'"
|
|
|
- (click)="toggleViewMode('timeline')">
|
|
|
- 📅 时间轴
|
|
|
+ class="scale-btn"
|
|
|
+ [class.active]="timelineScale === 'month'"
|
|
|
+ (click)="toggleTimelineScale('month')">
|
|
|
+ 📅 30天
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 时间尺度切换(仅在时间轴视图显示) -->
|
|
|
- @if (viewMode === 'timeline') {
|
|
|
- <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 refresh-controls">
|
|
|
+ <button
|
|
|
+ class="refresh-btn"
|
|
|
+ (click)="refresh()"
|
|
|
+ title="刷新数据和时间线(自动10分钟刷新一次)">
|
|
|
+ 🔄 刷新
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 排序方式 -->
|
|
|
<div class="filter-group sort-controls">
|
|
|
@@ -132,227 +114,160 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 时间轴主体 -->
|
|
|
- <div class="timeline-body" [class.timeline-view]="viewMode === 'timeline'">
|
|
|
- @if (viewMode === 'timeline') {
|
|
|
- <!-- 时间轴视图 -->
|
|
|
- <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-label">🎨 建模截止</span>
|
|
|
- </div>
|
|
|
- <div class="legend-item legend-phase">
|
|
|
- <span class="legend-label">🪑 软装截止</span>
|
|
|
- </div>
|
|
|
- <div class="legend-item legend-phase">
|
|
|
- <span class="legend-label">🖼️ 渲染截止</span>
|
|
|
- </div>
|
|
|
- <div class="legend-item legend-phase">
|
|
|
- <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 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="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 class="legend-item">
|
|
|
+ <span class="legend-icon review-icon">📋</span>
|
|
|
+ <span class="legend-label">小图对图(灰色=已完成)</span>
|
|
|
</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 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-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"
|
|
|
- [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 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>
|
|
|
- } @else {
|
|
|
- <!-- 列表视图 -->
|
|
|
- <div class="projects-list">
|
|
|
+
|
|
|
+ <!-- 今日标记线(实时移动,精确到分钟) -->
|
|
|
+ <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; let i = $index) {
|
|
|
- <div
|
|
|
- class="project-item"
|
|
|
- [class]="'status-' + project.status"
|
|
|
- (click)="onProjectClick(project.projectId)">
|
|
|
-
|
|
|
- <!-- 优先级指示条 -->
|
|
|
- <div class="priority-bar" [class]="'priority-' + project.priority"></div>
|
|
|
-
|
|
|
- <!-- 项目信息 -->
|
|
|
- <div class="project-content">
|
|
|
- <div class="project-header">
|
|
|
- <h4 class="project-name">{{ project.projectName }}</h4>
|
|
|
- <div class="project-badges">
|
|
|
- @if (project.isStalled) {
|
|
|
- <span class="badge badge-stalled">⏸️ 停滞{{ project.stalledDays }}天</span>
|
|
|
- }
|
|
|
- @if (project.urgentCount > 0) {
|
|
|
- <span class="badge badge-urgent">🔥 催办{{ project.urgentCount }}次</span>
|
|
|
- }
|
|
|
- @if (project.status === 'overdue') {
|
|
|
- <span class="badge badge-overdue">⚠️ 逾期</span>
|
|
|
- } @else if (project.status === 'urgent') {
|
|
|
- <span class="badge badge-warning">⏰ 紧急</span>
|
|
|
- }
|
|
|
- <!-- 🆕 空间与交付物统计徽章 -->
|
|
|
- @if (getSpaceDeliverableSummary(project.projectId); as summary) {
|
|
|
- <span class="badge badge-deliverable"
|
|
|
- [title]="formatSpaceDeliverableTooltip(project.projectId)"
|
|
|
- [style.background-color]="getProjectDeliveryStatusColor(project.projectId)"
|
|
|
- [style.color]="'white'">
|
|
|
- 📦 空间 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }} | 文件 {{ summary.totalDeliverableFiles }}
|
|
|
- </span>
|
|
|
- }
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="project-meta">
|
|
|
- <span class="meta-item">
|
|
|
- <span class="meta-label">设计师:</span>
|
|
|
- <span class="meta-value">{{ project.designerName }}</span>
|
|
|
- </span>
|
|
|
- <span class="meta-item">
|
|
|
- <span class="meta-label">阶段:</span>
|
|
|
- <span class="meta-value">{{ project.stageName }}</span>
|
|
|
+ @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>
|
|
|
- <span class="meta-item">
|
|
|
- <span class="meta-label">进度:</span>
|
|
|
- <span class="meta-value">{{ project.stageProgress }}%</span>
|
|
|
- </span>
|
|
|
- <span class="meta-item">
|
|
|
- <span class="meta-label">截止:</span>
|
|
|
- <span class="meta-value" [class.text-danger]="project.status === 'overdue'">
|
|
|
- {{ formatDate(project.endDate) }}
|
|
|
- </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>
|