Browse Source

```
refactor: enhance workload gantt visualization and calendar display

- Updated workload gantt legend labels to show project count ranges (正常 1-2, 超负荷 3+)
- Redesigned gantt chart styling with smaller dots (12px), solid colors for busy/overload states, and striped pattern for leave status
- Improved tooltip design with color-coded headers, project list with stage badges, and better visual hierarchy
- Changed workload calculation to use today's active projects instead of total projects for

0235711 3 days ago
parent
commit
3b26cd8ab5

+ 2 - 2
src/app/pages/team-leader/dashboard/components/workload-gantt/workload-gantt.component.html

@@ -9,8 +9,8 @@
       </div>
       </div>
       <div class="legend">
       <div class="legend">
         <span class="legend-item"><span class="dot idle"></span>空闲</span>
         <span class="legend-item"><span class="dot idle"></span>空闲</span>
-        <span class="legend-item"><span class="dot busy"></span>忙碌</span>
-        <span class="legend-item"><span class="dot overload"></span>超负荷</span>
+        <span class="legend-item"><span class="dot busy"></span>正常 (1-2)</span>
+        <span class="legend-item"><span class="dot overload"></span>超负荷 (3+)</span>
         <span class="legend-item"><span class="dot leave"></span>请假</span>
         <span class="legend-item"><span class="dot leave"></span>请假</span>
       </div>
       </div>
     </div>
     </div>

+ 7 - 8
src/app/pages/team-leader/dashboard/components/workload-gantt/workload-gantt.component.scss

@@ -75,25 +75,24 @@
           color: #6b7280;
           color: #6b7280;
           
           
           .dot {
           .dot {
-            width: 20px;
-            height: 16px;
-            border-radius: 4px;
-            border: 1px solid rgba(0, 0, 0, 0.1);
+            width: 12px;
+            height: 12px;
+            border-radius: 3px;
             
             
             &.idle {
             &.idle {
-              background: #d1fae5; // 空闲-浅绿色(0个项目)
+              background: #f3f4f6; // 空闲
             }
             }
             
             
             &.busy {
             &.busy {
-              background: #bfdbfe; // 忙碌-浅蓝色(1-2个项目)
+              background: #3b82f6; // 忙碌
             }
             }
             
             
             &.overload {
             &.overload {
-              background: #fecaca; // 超负荷-浅红色(≥3个项目)
+              background: #ef4444; // 超负荷
             }
             }
             
             
             &.leave {
             &.leave {
-              background: #e5e7eb; // 请假-灰色
+              background: repeating-linear-gradient(45deg, #e5e7eb, #e5e7eb 2px, #f3f4f6 2px, #f3f4f6 4px); // 请假纹理
             }
             }
           }
           }
         }
         }

+ 135 - 79
src/app/pages/team-leader/dashboard/components/workload-gantt/workload-gantt.component.ts

@@ -153,16 +153,46 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
       workloadByDesigner[name] = [];
       workloadByDesigner[name] = [];
     });
     });
 
 
-    // Calculate total load for sorting
-    const designerTotalLoad: Record<string, number> = {};
+    // Calculate active load for today (for sorting and display)
+    const designerTodayLoad: Record<string, number> = {};
+    
     designers.forEach(name => {
     designers.forEach(name => {
       const projects = this.designerWorkloadMap.get(name) || [];
       const projects = this.designerWorkloadMap.get(name) || [];
-      designerTotalLoad[name] = projects.length;
+      const dayStart = todayTs;
+      const dayEnd = todayTs + DAY - 1;
+
+      const activeProjects = projects.filter(p => {
+        const isCompleted = p.status === '已完成' || p.status === '已交付';
+        
+        if (isCompleted) {
+          return false;
+        }
+        
+        if (!p.deadline) {
+          return true; 
+        }
+        
+        const pEnd = new Date(p.deadline).getTime();
+        
+        if (isNaN(pEnd)) {
+          return true; 
+        }
+        
+        if (dayStart > pEnd) {
+          return false; 
+        }
+        
+        const pStart = p.createdAt ? new Date(p.createdAt).getTime() : todayTs;
+        
+        return !(pEnd < dayStart || pStart > dayEnd);
+      });
+
+      designerTodayLoad[name] = activeProjects.length;
     });
     });
     
     
-    // Sort designers by total load descending
+    // Sort designers by today's load descending
     const sortedDesigners = designers.sort((a, b) => {
     const sortedDesigners = designers.sort((a, b) => {
-      return designerTotalLoad[b] - designerTotalLoad[a];
+      return designerTodayLoad[b] - designerTodayLoad[a];
     });
     });
     
     
     // Generate time slot data for each designer
     // Generate time slot data for each designer
@@ -209,25 +239,30 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
         });
         });
 
 
         let status: 'idle' | 'busy' | 'overload' | 'leave' = 'idle';
         let status: 'idle' | 'busy' | 'overload' | 'leave' = 'idle';
-        let color = '#d1fae5'; // Idle - Light Green
+        let color = 'rgba(243, 244, 246, 0.4)'; // Idle - Very faint gray
+        let borderColor = 'transparent';
         
         
         const projectCount = dayProjects.length;
         const projectCount = dayProjects.length;
         
         
         if (projectCount === 0) {
         if (projectCount === 0) {
           status = 'idle';
           status = 'idle';
-          color = '#d1fae5'; 
+          color = 'rgba(243, 244, 246, 0.4)'; 
         } else if (projectCount >= 3) {
         } else if (projectCount >= 3) {
           status = 'overload';
           status = 'overload';
-          color = '#fecaca'; // Overload - Light Red
+          color = '#ef4444'; // Overload - Red
         } else {
         } else {
           status = 'busy';
           status = 'busy';
-          color = '#bfdbfe'; // Busy - Light Blue
+          color = '#3b82f6'; // Busy - Blue
         }
         }
 
 
         workloadByDesigner[designerName].push({
         workloadByDesigner[designerName].push({
           name: `${designerName}-${i}`,
           name: `${designerName}-${i}`,
-          value: [yIndex, dayStart, dayEnd, designerName, status, projectCount, dayProjects.map(p => p.name)],
-          itemStyle: { color }
+          // value: [index, start, end, name, status, count, projects]
+          value: [yIndex, dayStart, dayEnd, designerName, status, projectCount, dayProjects],
+          itemStyle: { 
+            color: color,
+            borderRadius: 4
+          }
         });
         });
       }
       }
     });
     });
@@ -237,99 +272,112 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
 
 
     const option = {
     const option = {
       backgroundColor: '#fff',
       backgroundColor: '#fff',
-      title: {
-        text: this.workloadGanttScale === 'week' ? '未来7天工作状态' : '未来30天工作状态',
-        subtext: '🟢空闲  🔵忙碌  🔴超负荷',
-        left: 'center',
-        textStyle: { fontSize: 14, color: '#374151', fontWeight: 600 },
-        subtextStyle: { fontSize: 12, color: '#6b7280' }
-      },
       tooltip: {
       tooltip: {
+        trigger: 'item',
+        padding: 0,
+        backgroundColor: 'rgba(255, 255, 255, 0.95)',
+        borderColor: '#e5e7eb',
+        borderWidth: 1,
+        textStyle: { color: '#374151' },
+        extraCssText: 'box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); border-radius: 8px;',
         formatter: (params: any) => {
         formatter: (params: any) => {
-          const [yIndex, start, end, name, status, projectCount, projectNames = []] = params.value;
+          const [yIndex, start, end, name, status, projectCount, projects = []] = params.value;
           const startDate = new Date(start);
           const startDate = new Date(start);
           const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
           const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
           
           
-          let statusText = '';
-          let statusColor = '';
-          let statusBadge = '';
+          // Status Header
+          let headerColor = '#f3f4f6';
+          let headerTextColor = '#374151';
+          let statusLabel = '空闲';
           
           
-          if (status === 'leave') {
-            statusText = '请假';
-            statusColor = '#6b7280';
-            statusBadge = '<span style="background:#e5e7eb;color:#374151;padding:2px 8px;border-radius:4px;font-size:11px;">请假</span>';
-          } else if (projectCount === 0) {
-            statusText = '空闲';
-            statusColor = '#10b981';
-            statusBadge = '<span style="background:#d1fae5;color:#059669;padding:2px 8px;border-radius:4px;font-size:11px;">🟢 空闲</span>';
-          } else if (projectCount >= 3) {
-            statusText = '超负荷';
-            statusColor = '#dc2626';
-            statusBadge = '<span style="background:#fecaca;color:#dc2626;padding:2px 8px;border-radius:4px;font-size:11px;">🔴 超负荷</span>';
-          } else {
-            statusText = '忙碌';
-            statusColor = '#3b82f6';
-            statusBadge = '<span style="background:#bfdbfe;color:#1d4ed8;padding:2px 8px;border-radius:4px;font-size:11px;">🔵 忙碌</span>';
+          if (status === 'overload') {
+            headerColor = '#fee2e2';
+            headerTextColor = '#991b1b';
+            statusLabel = '超负荷';
+          } else if (status === 'busy') {
+            headerColor = '#dbeafe';
+            headerTextColor = '#1e40af';
+            statusLabel = '忙碌';
           }
           }
-          
+
+          // Project List
           let projectListHtml = '';
           let projectListHtml = '';
-          if (projectNames && projectNames.length > 0) {
+          if (projects && projects.length > 0) {
+            const listItems = projects.slice(0, 6).map((p: any, idx: number) => {
+              const isUrgent = p.status === 'urgent' || p.status === 'overdue';
+              const stage = p.currentStage || '进行中';
+              return `
+                <div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; font-size: 12px; border-bottom: 1px dashed #f3f4f6;">
+                  <div style="display: flex; align-items: center; gap: 6px; max-width: 70%;">
+                    <span style="color: #9ca3af; font-size: 10px; width: 15px;">${idx + 1}.</span>
+                    <span style="color: #374151; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">${p.name}</span>
+                  </div>
+                  <div style="display: flex; align-items: center; gap: 4px;">
+                    <span style="background: #f3f4f6; color: #6b7280; padding: 1px 4px; border-radius: 3px; font-size: 10px;">${stage}</span>
+                    ${isUrgent ? '<span style="background: #fee2e2; color: #ef4444; width: 6px; height: 6px; border-radius: 50%; display: inline-block;"></span>' : ''}
+                  </div>
+                </div>
+              `;
+            }).join('');
+            
             projectListHtml = `
             projectListHtml = `
-              <div style="margin-top: 8px; padding-top: 8px; border-top: 1px dashed #e5e7eb;">
-                <div style="font-size: 12px; color: #6b7280; margin-bottom: 4px;">项目列表:</div>
-                ${projectNames.slice(0, 5).map((pName: string, idx: number) => 
-                  `<div style="font-size: 12px; color: #374151; margin-left: 8px;">
-                    ${idx + 1}. ${pName.length > 20 ? pName.substring(0, 20) + '...' : pName}
-                  </div>`
-                ).join('')}
-                ${projectNames.length > 5 ? `<div style="font-size: 12px; color: #6b7280; margin-left: 8px;">...及${projectNames.length - 5}个其他项目</div>` : ''}
+              <div style="padding: 8px 12px;">
+                <div style="font-size: 11px; color: #9ca3af; margin-bottom: 4px; display: flex; justify-content: space-between;">
+                  <span>项目列表</span>
+                  <span>阶段</span>
+                </div>
+                ${listItems}
+                ${projects.length > 6 ? `<div style="text-align: center; font-size: 11px; color: #9ca3af; margin-top: 4px;">...共 ${projects.length} 个项目</div>` : ''}
               </div>
               </div>
             `;
             `;
+          } else {
+            projectListHtml = `<div style="padding: 12px; text-align: center; color: #9ca3af; font-size: 12px;">无项目安排</div>`;
           }
           }
           
           
-          return `<div style="padding: 12px; min-width: 220px;">
-                    <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
-                      <strong style="font-size: 15px; color: #1f2937;">${name}</strong>
-                      ${statusBadge}
-                    </div>
-                    <div style="color: #6b7280; font-size: 13px;">
-                      📅 ${startDate.getMonth() + 1}月${startDate.getDate()}日 ${weekDays[startDate.getDay()]}<br/>
-                      📊 项目数量: <span style="font-weight: 600; color: ${statusColor};">${projectCount}个</span>
-                    </div>
-                    ${projectListHtml}
-                    <div style="margin-top: 10px; padding-top: 8px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 11px; text-align: center;">
-                      💡 点击查看设计师详细信息
-                    </div>
-                  </div>`;
+          return `
+            <div style="width: 260px; overflow: hidden; border-radius: 8px;">
+              <div style="background: ${headerColor}; padding: 8px 12px; border-bottom: 1px solid rgba(0,0,0,0.05);">
+                <div style="display: flex; justify-content: space-between; align-items: center;">
+                  <span style="font-weight: 600; color: ${headerTextColor}; font-size: 14px;">${name}</span>
+                  <span style="font-size: 12px; color: ${headerTextColor}; opacity: 0.8;">${statusLabel} (${projectCount})</span>
+                </div>
+                <div style="font-size: 11px; color: ${headerTextColor}; opacity: 0.7; margin-top: 2px;">
+                  ${startDate.getMonth() + 1}月${startDate.getDate()}日 ${weekDays[startDate.getDay()]}
+                </div>
+              </div>
+              ${projectListHtml}
+              <div style="padding: 6px 12px; background: #f9fafb; border-top: 1px solid #f3f4f6; text-align: center;">
+                <span style="font-size: 10px; color: #9ca3af;">点击查看详情</span>
+              </div>
+            </div>
+          `;
         }
         }
       },
       },
       grid: {
       grid: {
         left: 100,
         left: 100,
-        right: 50,
-        top: 60,
-        bottom: 60
+        right: 30,
+        top: 70,
+        bottom: 30,
+        containLabel: true
       },
       },
       xAxis: {
       xAxis: {
         type: 'time',
         type: 'time',
         min: xMin,
         min: xMin,
         max: xMax,
         max: xMax,
+        position: 'top',
         boundaryGap: false,
         boundaryGap: false,
-        axisLine: { lineStyle: { color: '#e5e7eb' } },
+        axisLine: { show: false },
+        axisTick: { show: false },
         axisLabel: {
         axisLabel: {
           color: '#6b7280',
           color: '#6b7280',
           formatter: xLabelFormatter,
           formatter: xLabelFormatter,
           interval: 0,
           interval: 0,
-          rotate: this.workloadGanttScale === 'week' ? 0 : 45,
-          showMinLabel: true,
-          showMaxLabel: true
-        },
-        axisTick: {
-          alignWithLabel: true,
-          interval: 0
+          margin: 12,
+          fontSize: 11
         },
         },
         splitLine: { 
         splitLine: { 
           show: true,
           show: true,
-          lineStyle: { color: '#f1f5f9' }
+          lineStyle: { color: '#f3f4f6', type: 'dashed' }
         },
         },
         splitNumber: xSplitNumber,
         splitNumber: xSplitNumber,
         minInterval: DAY
         minInterval: DAY
@@ -345,13 +393,13 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
           fontSize: 13,
           fontSize: 13,
           fontWeight: 500,
           fontWeight: 500,
           formatter: (value: string) => {
           formatter: (value: string) => {
-            const totalProjects = designerTotalLoad[value] || 0;
+            const totalProjects = designerTodayLoad[value] || 0;
             const icon = totalProjects >= 3 ? '🔥' : totalProjects >= 2 ? '⚡' : totalProjects >= 1 ? '✓' : '○';
             const icon = totalProjects >= 3 ? '🔥' : totalProjects >= 2 ? '⚡' : totalProjects >= 1 ? '✓' : '○';
             return `${icon} ${value} (${totalProjects})`;
             return `${icon} ${value} (${totalProjects})`;
           }
           }
         },
         },
         axisTick: { show: false },
         axisTick: { show: false },
-        axisLine: { lineStyle: { color: '#e5e7eb' } }
+        axisLine: { show: false }
       },
       },
       series: [
       series: [
         {
         {
@@ -359,13 +407,17 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
           name: '工作负载',
           name: '工作负载',
           renderItem: (params: any, api: any) => {
           renderItem: (params: any, api: any) => {
             const categoryIndex = api.value(0);
             const categoryIndex = api.value(0);
+            // Calculate coordinates
             const start = api.coord([api.value(1), categoryIndex]);
             const start = api.coord([api.value(1), categoryIndex]);
             const end = api.coord([api.value(2), categoryIndex]);
             const end = api.coord([api.value(2), categoryIndex]);
-            const height = api.size([0, 1])[1] * 0.6;
+            
+            // Adjust height and position for a "pill" look
+            const height = api.size([0, 1])[1] * 0.7; // 70% of row height
+            
             const rectShape = echarts.graphic.clipRectByRect({
             const rectShape = echarts.graphic.clipRectByRect({
               x: start[0],
               x: start[0],
               y: start[1] - height / 2,
               y: start[1] - height / 2,
-              width: Math.max(end[0] - start[0], 2),
+              width: Math.max(end[0] - start[0], 2), // Minimum width 2px
               height
               height
             }, {
             }, {
               x: params.coordSys.x,
               x: params.coordSys.x,
@@ -373,9 +425,13 @@ export class WorkloadGanttComponent implements OnDestroy, OnChanges, AfterViewIn
               width: params.coordSys.width,
               width: params.coordSys.width,
               height: params.coordSys.height
               height: params.coordSys.height
             });
             });
+
             return rectShape ? { 
             return rectShape ? { 
               type: 'rect', 
               type: 'rect', 
-              shape: rectShape, 
+              shape: {
+                ...rectShape,
+                r: [4, 4, 4, 4] // Rounded corners
+              }, 
               style: api.style() 
               style: api.style() 
             } : undefined;
             } : undefined;
           },
           },

+ 24 - 19
src/app/pages/team-leader/employee-detail-panel/employee-detail-panel.scss

@@ -352,25 +352,6 @@
               background: transparent;
               background: transparent;
             }
             }
 
 
-            &.today {
-              border-color: #667eea;
-              border-width: 2px;
-              background: #f0f3ff;
-
-              .day-number {
-                color: #667eea;
-                font-weight: 700;
-              }
-            }
-
-            &.other-month {
-              opacity: 0.3;
-
-              .day-number {
-                color: #94a3b8;
-              }
-            }
-
             // 普通项目背景(1个项目)
             // 普通项目背景(1个项目)
             &.has-projects:not(.high-load) {
             &.has-projects:not(.high-load) {
               background: #bfdbfe;
               background: #bfdbfe;
@@ -411,6 +392,30 @@
                 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
                 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
               }
               }
             }
             }
+
+            // Today style - moved to end to override text colors but keep background
+            &.today {
+              border-color: #667eea !important;
+              border-width: 2px;
+              
+              // Only set background if no projects (otherwise keep red/blue)
+              &:not(.has-projects) {
+                background: #f0f3ff;
+              }
+
+              .day-number {
+                background: #667eea;
+                color: white !important;
+                border-radius: 50%;
+                width: 24px;
+                height: 24px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-weight: 700;
+                box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3);
+              }
+            }
           }
           }
         }
         }
 
 

+ 28 - 34
src/app/pages/team-leader/employee-detail-panel/employee-detail-panel.ts

@@ -2,6 +2,7 @@ import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChange
 import { CommonModule } from '@angular/common';
 import { CommonModule } from '@angular/common';
 import { Router } from '@angular/router';
 import { Router } from '@angular/router';
 import { DesignerCalendarComponent, Designer as CalendarDesigner } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
 import { DesignerCalendarComponent, Designer as CalendarDesigner } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
+import { normalizeDateInput, addDays } from '../../../utils/date-utils';
 
 
 // 员工详情面板数据接口
 // 员工详情面板数据接口
 export interface EmployeeDetail {
 export interface EmployeeDetail {
@@ -247,48 +248,41 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
       
       
       // 找出该日期相关的项目(项目进行中且在当天范围内)
       // 找出该日期相关的项目(项目进行中且在当天范围内)
       const dayProjects = employeeProjects.filter(p => {
       const dayProjects = employeeProjects.filter(p => {
-        // 处理 Parse Date 对象:检查是否有 toDate 方法
-        const getDate = (dateValue: any) => {
-          if (!dateValue) return null;
-          if (dateValue.toDate && typeof dateValue.toDate === 'function') {
-            return dateValue.toDate(); // Parse Date对象
-          }
-          if (dateValue instanceof Date) {
-            return dateValue;
-          }
-          return new Date(dateValue); // 字符串或时间戳
-        };
-        
-        const deadlineDate = getDate(p.deadline);
-        const createdDate = p.createdAt ? getDate(p.createdAt) : null;
-        
-        // 如果项目既没有 deadline 也没有 createdAt,则跳过
-        if (!deadlineDate && !createdDate) {
+        // 1. 过滤已完成/已交付的项目
+        if (p.status === '已完成' || p.status === '已交付') {
           return false;
           return false;
         }
         }
+
+        const projectData = p.data || {};
+
+        // 2. 获取真实的项目开始时间 (逻辑与 DesignerWorkloadService 保持一致)
+        const realStartDate = normalizeDateInput(
+          projectData.phaseDeadlines?.modeling?.startDate ||
+            projectData.requirementsConfirmedAt ||
+            p.createdAt,
+          new Date()
+        );
         
         
-        // 智能处理日期范围
-        let startDate: Date;
-        let endDate: Date;
+        // 3. 获取真实的交付日期 (逻辑与 DesignerWorkloadService 保持一致)
+        let proposedEndDate = p.deadline || projectData.phaseDeadlines?.postProcessing?.deadline;
+        let realEndDate: Date;
         
         
-        if (deadlineDate && createdDate) {
-          // 情况1:两个日期都有
-          startDate = createdDate;
-          endDate = deadlineDate;
-        } else if (deadlineDate) {
-          // 情况2:只有deadline,往前推30天
-          startDate = new Date(deadlineDate.getTime() - 30 * 24 * 60 * 60 * 1000);
-          endDate = deadlineDate;
+        if (proposedEndDate) {
+          const proposed = normalizeDateInput(proposedEndDate, realStartDate);
+          // fix: 只要有明确的deadline,就使用它
+          realEndDate = proposed;
         } else {
         } else {
-          // 情况3:只有createdAt,往后推30天
-          startDate = createdDate!;
-          endDate = new Date(createdDate!.getTime() + 30 * 24 * 60 * 60 * 1000);
+          realEndDate = addDays(realStartDate, 30);
         }
         }
         
         
-        startDate.setHours(0, 0, 0, 0);
-        endDate.setHours(0, 0, 0, 0);
+        // 归一化为当天0点,便于比较
+        const rangeStart = new Date(realStartDate);
+        rangeStart.setHours(0, 0, 0, 0);
+        
+        const rangeEnd = new Date(realEndDate);
+        rangeEnd.setHours(0, 0, 0, 0);
         
         
-        const inRange = date >= startDate && date <= endDate;
+        const inRange = date >= rangeStart && date <= rangeEnd;
         
         
         return inRange;
         return inRange;
       }).map(p => {
       }).map(p => {

+ 8 - 4
src/app/pages/team-leader/services/designer-workload.service.ts

@@ -205,10 +205,14 @@ export class DesignerWorkloadService {
       
       
       if (proposedEndDate) {
       if (proposedEndDate) {
         const proposed = normalizeDateInput(proposedEndDate, realStartDate);
         const proposed = normalizeDateInput(proposedEndDate, realStartDate);
-        if (proposed.getTime() > now.getTime()) {
-          realEndDate = proposed;
-        } else {
-          realEndDate = addDays(realStartDate, 30);
+        // fix: 只要有明确的deadline,就使用它,即使它在过去(这样会导致逾期,但不应该强行延长周期)
+        // 之前的逻辑是 if (proposed > now) use proposed else use start + 30
+        // 这会导致过期的项目强行变成 "开始+30天",这对于短期测试项目(如11.22测试)是不对的
+        realEndDate = proposed;
+        
+        // 只有当结束时间早于开始时间这种异常情况,才做修正
+        if (realEndDate.getTime() < realStartDate.getTime()) {
+             realEndDate = addDays(realStartDate, 30);
         }
         }
       } else {
       } else {
         realEndDate = addDays(realStartDate, 30);
         realEndDate = addDays(realStartDate, 30);