浏览代码

feat: enhance designer team assignment modal with calendar functionality and UI improvements

- Updated the designer team assignment modal to include calendar month navigation and day click handling for improved project visibility.
- Enhanced the display of review dates and designer skills, limiting the number of visible items and adding indicators for additional entries.
- Improved styling for buttons and layout to ensure a more user-friendly interface, including adjustments to spacing and overflow handling.
- Added logic to manage project data when switching months, ensuring a seamless user experience.
徐福静0235668 2 天之前
父节点
当前提交
ffdeb21132

+ 35 - 23
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.html

@@ -105,31 +105,36 @@
                           </div>
                         }
 
-                        @if (designer.reviewDates.length > 0) {
-                          <div class="metric-item review-dates">
-                            <span class="metric-label">对图日期:{{ designer.reviewDates.join(', ') }}</span>
-                          </div>
-                        }
-                      </div>
+                      @if (designer.reviewDates.length > 0) {
+                        <div class="metric-item review-dates">
+                          <span class="metric-label">
+                            对图日期:{{ designer.reviewDates.slice(0, 2).join(', ') }}
+                            @if (designer.reviewDates.length > 2) {
+                              <span class="more-dates">等{{ designer.reviewDates.length }}个</span>
+                            }
+                          </span>
+                        </div>
+                      }
+                    </div>
 
-                      <div class="designer-skills">
-                        @for (skill of designer.skills.slice(0, 2); track skill) {
-                          <span class="skill-tag">{{ skill }}</span>
-                        }
-                        @if (designer.skills.length > 2) {
-                          <span class="skill-more">+{{ designer.skills.length - 2 }}</span>
-                        }
-                      </div>
+                    <div class="designer-skills">
+                      @for (skill of designer.skills.slice(0, 2); track skill) {
+                        <span class="skill-tag">{{ skill }}</span>
+                      }
+                      @if (designer.skills.length > 2) {
+                        <span class="skill-more">+{{ designer.skills.length - 2 }}</span>
+                      }
                     </div>
+                  </div>
 
-                    <div class="designer-actions">
-                      <button 
-                        class="calendar-btn"
-                        (click)="$event.stopPropagation(); showDesignerEmployeeDetail(designer)"
-                        title="查看设计师详情"
-                      >
-                        👤 详情
-                      </button>
+                  <div class="designer-actions">
+                    <button 
+                      class="calendar-btn"
+                      (click)="$event.stopPropagation(); showDesignerEmployeeDetail(designer)"
+                      title="查看设计师详情"
+                    >
+                      👤 详情
+                    </button>
                       @if (enableSpaceAssignment && spaceScenes.length > 0) {
                         <button 
                           class="space-assign-btn"
@@ -205,7 +210,12 @@
 
                       @if (designer.reviewDates.length > 0) {
                         <div class="metric-item review-dates">
-                          <span class="metric-label">对图日期:{{ designer.reviewDates.join(', ') }}</span>
+                          <span class="metric-label">
+                            对图日期:{{ designer.reviewDates.slice(0, 2).join(', ') }}
+                            @if (designer.reviewDates.length > 2) {
+                              <span class="more-dates">等{{ designer.reviewDates.length }}个</span>
+                            }
+                          </span>
                         </div>
                       }
                     </div>
@@ -504,6 +514,8 @@
     [visible]="true"
     [employeeDetail]="employeeDetailData"
     (close)="closeEmployeeDetailPanel()"
+    (calendarMonthChange)="changeEmployeeCalendarMonth($event)"
+    (calendarDayClick)="onCalendarDayClick($event)"
     (projectClick)="onEmployeeDetailProjectClick($event)"
     (refreshSurvey)="refreshEmployeeSurvey()">
   </app-employee-detail-panel>

+ 50 - 33
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.scss

@@ -254,17 +254,20 @@
   display: flex;
   gap: 12px;
   position: relative;
+  overflow: hidden; // ⭐ 修复:防止内部元素溢出
 
   // 🎨 根据工作量状态添加明显的颜色标识
+  // ⭐ 优化:调整长条样式,避免过于明显
   &::before {
     content: '';
     position: absolute;
     left: 0;
     top: 0;
     bottom: 0;
-    width: 6px;
+    width: 4px; // 从6px减少到4px,更加精致
     border-radius: 10px 0 0 10px;
     transition: all 0.3s ease;
+    opacity: 0.8; // 添加透明度,使其不那么突兀
   }
 
   // 🟢 空闲(0个项目)- 明显的绿色
@@ -387,6 +390,11 @@
   .designer-info {
     flex: 1;
     min-width: 0;
+    // ⭐ 修复:限制信息区域最大高度,避免卡片过高
+    max-height: 160px;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
 
     .designer-name {
       font-size: 15px;
@@ -396,6 +404,7 @@
       display: flex;
       align-items: center;
       gap: 8px;
+      flex-shrink: 0; // 名称不收缩
 
       .leader-badge {
         background: #1890ff;
@@ -463,6 +472,8 @@
 
     .designer-metrics {
       margin-bottom: 8px;
+      flex-shrink: 1; // 允许收缩
+      overflow: hidden; // 隐藏溢出内容
 
       .metric-item {
         margin-bottom: 4px;
@@ -470,6 +481,11 @@
         .metric-label {
           font-size: 12px;
           color: #8c8c8c;
+          // ⭐ 修复:限制单行显示,避免日期过长导致卡片变形
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          max-width: 100%;
         }
 
         &.active .metric-label {
@@ -495,6 +511,23 @@
         &.review-dates .metric-label {
           color: #722ed1;
           font-weight: 500;
+          // ⭐ 修复:对图日期特别处理,最多显示2行
+          white-space: normal;
+          display: -webkit-box;
+          -webkit-line-clamp: 2;
+          -webkit-box-orient: vertical;
+          line-height: 1.4;
+          
+          .more-dates {
+            display: inline-block;
+            margin-left: 4px;
+            padding: 2px 6px;
+            background: rgba(114, 46, 209, 0.1);
+            border-radius: 4px;
+            font-size: 11px;
+            color: #722ed1;
+            font-weight: 600;
+          }
         }
       }
     }
@@ -525,19 +558,25 @@
   .designer-actions {
     flex-shrink: 0;
     display: flex;
-    align-items: flex-start;
+    flex-direction: column; // ⭐ 改为垂直布局,避免横向过宽
+    align-items: center;
+    gap: 6px; // 按钮之间的间距
 
-    .calendar-btn {
-      background: none;
-      border: none;
-      padding: 4px;
+    .calendar-btn,
+    .space-assign-btn {
+      background: white;
+      border: 1px solid #e2e8f0;
+      padding: 6px 10px;
       cursor: pointer;
-      border-radius: 4px;
-      font-size: 16px;
-      transition: background 0.2s ease;
+      border-radius: 6px;
+      font-size: 14px; // 从16px减少到14px
+      transition: all 0.2s ease;
+      white-space: nowrap; // 防止文字换行
 
       &:hover {
-        background: #f0f0f0;
+        background: #f8fafc;
+        border-color: #cbd5e1;
+        transform: scale(1.05);
       }
     }
   }
@@ -750,29 +789,7 @@
 }
 
 // ===== 空间分配相关样式 =====
-
-.designer-actions {
-  display: flex;
-  gap: 8px;
-  margin-top: 12px;
-
-  .calendar-btn,
-  .space-assign-btn {
-    padding: 6px 12px;
-    border: 1px solid #e2e8f0;
-    background: white;
-    border-radius: 6px;
-    cursor: pointer;
-    font-size: 16px;
-    transition: all 0.2s ease;
-
-    &:hover {
-      background: #f8fafc;
-      border-color: #cbd5e1;
-      transform: scale(1.05);
-    }
-  }
-}
+// ⭐ 注意:.designer-actions 已在上面的 .designer-card 中定义,这里移除重复定义
 
 .designer-spaces-info {
   margin-top: 12px;

+ 56 - 2
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.ts

@@ -1376,6 +1376,9 @@ export class DesignerTeamAssignmentModalComponent implements OnInit, OnChanges {
         name: p.get('title') || p.get('name') || '未命名项目'
       }));
 
+      // ⭐ 保存项目数据(用于月份切换)
+      this.currentEmployeeProjects = projects;
+      
       // 构建日历数据(当月)
       console.log(`📅 [员工详情] 开始构建 ${designer.name} 的日历数据,项目数: ${projects.length}`);
       const calendarData = this.buildEmployeeCalendarData(projects);
@@ -1535,11 +1538,13 @@ export class DesignerTeamAssignmentModalComponent implements OnInit, OnChanges {
   /**
    * 构建员工日历数据(当月视图)
    * 🔥 完全参考组长端 dashboard.ts 的 generateEmployeeCalendar 方法
+   * @param projects 项目列表
+   * @param targetMonth 目标月份(可选,默认为当前月)
    */
-  private buildEmployeeCalendarData(projects: any[]): EmployeeCalendarData {
+  private buildEmployeeCalendarData(projects: any[], targetMonth?: Date): EmployeeCalendarData {
     console.log(`\n📅 [日历构建] 收到 ${projects.length} 个项目`);
     
-    const currentMonth = new Date();
+    const currentMonth = targetMonth || new Date();
     const year = currentMonth.getFullYear();
     const month = currentMonth.getMonth();
     
@@ -1632,12 +1637,61 @@ export class DesignerTeamAssignmentModalComponent implements OnInit, OnChanges {
     };
   }
 
+  // 保存当前员工的项目数据(用于切换月份)
+  private currentEmployeeProjects: any[] = [];
+
+  /**
+   * 切换员工日历月份(与组长端对齐)
+   * @param direction -1=上月, 1=下月
+   */
+  changeEmployeeCalendarMonth(direction: number): void {
+    if (!this.employeeDetailData?.calendarData) {
+      console.warn('⚠️ [changeEmployeeCalendarMonth] 日历数据不存在');
+      return;
+    }
+    
+    console.log(`📅 [changeEmployeeCalendarMonth] 切换月份: ${direction > 0 ? '下月' : '上月'}`);
+    
+    const currentMonth = this.employeeDetailData.calendarData.currentMonth;
+    const newMonth = new Date(currentMonth);
+    newMonth.setMonth(newMonth.getMonth() + direction);
+    
+    // 重新生成指定月份的日历数据
+    const newCalendarData = this.buildEmployeeCalendarData(this.currentEmployeeProjects, newMonth);
+    
+    console.log(`📅 [changeEmployeeCalendarMonth] 新月份日历生成完成:`, {
+      月份: `${newMonth.getFullYear()}年${newMonth.getMonth() + 1}月`,
+      有项目的天数: newCalendarData.days.filter(d => d.projectCount > 0).length
+    });
+    
+    // 更新员工详情中的日历数据
+    this.employeeDetailData = {
+      ...this.employeeDetailData,
+      calendarData: newCalendarData
+    };
+    
+    this.cdr.markForCheck();
+  }
+
+  /**
+   * 处理日历日期点击事件
+   */
+  onCalendarDayClick(day: EmployeeCalendarDay): void {
+    console.log(`📅 [onCalendarDayClick] 点击日期:`, {
+      日期: day.date,
+      项目数: day.projectCount,
+      项目列表: day.projects
+    });
+    // 可以显示当天的项目详情弹窗
+  }
+
   /**
    * 关闭员工详情面板
    */
   closeEmployeeDetailPanel(): void {
     this.showEmployeeDetailPanel = false;
     this.employeeDetailData = null;
+    this.currentEmployeeProjects = []; // 清空缓存的项目数据
   }
 
   /**

+ 6 - 5
src/app/shared/components/employee-info-panel/employee-info-panel.component.html

@@ -384,19 +384,20 @@
           </div>
         }
 
-        <!-- ========== 项目负载标签页 - ⭐ 真正复用 employee-detail-panel 组件 ========== -->
+        <!-- ========== 项目负载标签页 - ⭐ 复用 employee-detail-panel 组件 ========== -->
         @if (activeTab === 'workload') {
           <div class="tab-content workload-tab">
             @if (employeeDetailForTeamLeader) {
-              <!-- ⭐ 真正的组件复用:使用 <app-employee-detail-panel> -->
+              <!-- ⭐ 完全对齐组长端的复用方式 -->
               <app-employee-detail-panel
                 [visible]="true"
                 [employeeDetail]="employeeDetailForTeamLeader"
                 [embedMode]="true"
-                (projectClick)="onProjectClick($event)"
-                (calendarMonthChange)="onChangeMonth($event)"
+                (close)="closeEmployeeDetailPanel()"
+                (calendarMonthChange)="changeEmployeeCalendarMonth($event)"
                 (calendarDayClick)="onCalendarDayClick($event)"
-                (refreshSurvey)="onRefreshSurvey()">
+                (projectClick)="navigateToProjectFromPanel($event)"
+                (refreshSurvey)="refreshEmployeeSurvey()">
               </app-employee-detail-panel>
             } @else {
               <!-- 数据加载中状态 -->

+ 32 - 130
src/app/shared/components/employee-info-panel/employee-info-panel.component.ts

@@ -2,7 +2,6 @@ import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChange
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { Router } from '@angular/router';
-import { DesignerCalendarComponent, Designer as CalendarDesigner } from '../../../pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
 import { EmployeeDetailPanelComponent, EmployeeDetail as TeamLeaderEmployeeDetail } from '../../../pages/team-leader/employee-detail-panel';
 
 /**
@@ -102,15 +101,8 @@ export class EmployeeInfoPanelComponent implements OnInit, OnChanges {
   editMode: boolean = false; // 是否处于编辑模式
   formModel: Partial<EmployeeFullInfo> = {}; // 编辑表单模型
   
-  // 项目负载相关
-  showFullSurvey: boolean = false;
+  // 项目负载相关(保持最小状态)
   refreshingSurvey: boolean = false;
-  showCalendarProjectList: boolean = false;
-  selectedDate: Date | null = null;
-  selectedDayProjects: Array<{ id: string; name: string; deadline?: Date }> = [];
-  showDesignerCalendar: boolean = false;
-  calendarDesigners: CalendarDesigner[] = [];
-  calendarViewMode: 'week' | 'month' | 'quarter' = 'month';
 
   constructor(private router: Router) {}
 
@@ -262,9 +254,6 @@ export class EmployeeInfoPanelComponent implements OnInit, OnChanges {
   private resetPanel(): void {
     this.activeTab = 'basic';
     this.editMode = false;
-    this.showFullSurvey = false;
-    this.showCalendarProjectList = false;
-    this.showDesignerCalendar = false;
     this.resetFormModel();
   }
 
@@ -285,98 +274,60 @@ export class EmployeeInfoPanelComponent implements OnInit, OnChanges {
     };
   }
 
-  // ========== 项目负载相关方法 ==========
+  // ========== 项目负载相关方法(完全对齐组长端) ==========
 
   /**
-   * 切换月份
+   * 关闭员工详情面板(嵌入模式下可能不需要,但保留接口)
    */
-  onChangeMonth(direction: number): void {
-    this.calendarMonthChange.emit(direction);
+  closeEmployeeDetailPanel(): void {
+    console.log('📋 [closeEmployeeDetailPanel] 嵌入模式,不执行关闭操作');
+    // 嵌入模式下不需要关闭,因为面板是标签页的一部分
   }
 
   /**
-   * 日历日期点击
+   * 切换员工日历月份(完全对齐组长端)
+   * @param direction -1=上月, 1=下月
    */
-  onCalendarDayClick(day: EmployeeCalendarDay): void {
-    if (!day.isCurrentMonth || day.projectCount === 0) {
-      return;
-    }
-    
-    this.selectedDate = day.date;
-    this.selectedDayProjects = day.projects;
-    this.showCalendarProjectList = true;
+  changeEmployeeCalendarMonth(direction: number): void {
+    console.log(`📅 [changeEmployeeCalendarMonth] 切换月份: ${direction > 0 ? '下月' : '上月'}`);
+    this.calendarMonthChange.emit(direction);
   }
 
   /**
-   * 关闭项目列表弹窗
+   * 处理日历日期点击事件(完全对齐组长端)
    */
-  closeCalendarProjectList(): void {
-    this.showCalendarProjectList = false;
-    this.selectedDate = null;
-    this.selectedDayProjects = [];
+  onCalendarDayClick(day: EmployeeCalendarDay): void {
+    console.log(`📅 [onCalendarDayClick] 点击日期:`, {
+      日期: day.date,
+      项目数: day.projectCount,
+      项目列表: day.projects
+    });
+    this.calendarDayClick.emit(day);
+    // 如果需要在内部处理,可以展开:
+    // if (!day.isCurrentMonth || day.projectCount === 0) return;
+    // this.selectedDate = day.date;
+    // this.selectedDayProjects = day.projects;
+    // this.showCalendarProjectList = true;
   }
 
   /**
-   * 项目点击
+   * 项目点击事件(完全对齐组长端)
    */
-  onProjectClick(projectId: string): void {
+  navigateToProjectFromPanel(projectId: string): void {
+    console.log('🔗 [navigateToProjectFromPanel] 点击项目:', projectId);
     this.projectClick.emit(projectId);
-    this.closeCalendarProjectList();
-  }
-
-  /**
-   * 打开详细日历
-   */
-  openDesignerCalendar(): void {
-    if (!this.employee) return;
-
-    const name = this.employee.name || '设计师';
-    const currentProjects = this.employee.currentProjects || 0;
-
-    const upcomingEvents: CalendarDesigner['upcomingEvents'] = [];
-    const days = this.employee.calendarData?.days || [];
-    for (const day of days) {
-      if (day.projectCount > 0) {
-        upcomingEvents.push({
-          id: `${day.date.getTime()}`,
-          date: day.date,
-          title: `${day.projectCount}个项目`,
-          type: 'project',
-          duration: 6
-        });
-      }
-    }
-
-    this.calendarDesigners = [{
-      id: this.employee.profileId || this.employee.id,
-      name,
-      groupId: this.employee.departmentId || '',
-      groupName: this.employee.department || '',
-      isLeader: this.employee.roleName === '组长',
-      status: currentProjects >= 3 ? 'busy' : 'available',
-      currentProjects,
-      upcomingEvents,
-      workload: Math.min(100, currentProjects * 30)
-    }];
-
-    this.showDesignerCalendar = true;
-  }
-
-  /**
-   * 关闭详细日历
-   */
-  closeDesignerCalendar(): void {
-    this.showDesignerCalendar = false;
-    this.calendarDesigners = [];
+    // 可以在这里实现跳转逻辑
+    // this.router.navigate(['/wxwork', cid, 'project', projectId]);
   }
 
   /**
-   * 刷新问卷
+   * 刷新员工问卷(完全对齐组长端)
    */
-  onRefreshSurvey(): void {
+  refreshEmployeeSurvey(): void {
     if (this.refreshingSurvey) return;
     
     this.refreshingSurvey = true;
+    console.log('🔄 [refreshEmployeeSurvey] 刷新问卷状态...');
     this.refreshSurvey.emit();
     
     setTimeout(() => {
@@ -384,55 +335,6 @@ export class EmployeeInfoPanelComponent implements OnInit, OnChanges {
     }, 2000);
   }
 
-  /**
-   * 切换问卷显示模式
-   */
-  toggleSurveyDisplay(): void {
-    this.showFullSurvey = !this.showFullSurvey;
-  }
-
-  /**
-   * 获取能力画像摘要
-   */
-  getCapabilitySummary(answers: any[]): any {
-    const findAnswer = (questionId: string) => {
-      const item = answers.find((a: any) => a.questionId === questionId);
-      return item?.answer;
-    };
-
-    const formatArray = (value: any): string => {
-      if (Array.isArray(value)) {
-        return value.join('、');
-      }
-      return value || '未填写';
-    };
-
-    return {
-      styles: formatArray(findAnswer('q1_expertise_styles')),
-      spaces: formatArray(findAnswer('q2_expertise_spaces')),
-      advantages: formatArray(findAnswer('q3_technical_advantages')),
-      difficulty: findAnswer('q5_project_difficulty') || '未填写',
-      capacity: findAnswer('q7_weekly_capacity') || '未填写',
-      urgent: findAnswer('q8_urgent_willingness') || '未填写',
-      urgentLimit: findAnswer('q8_urgent_limit') || '',
-      feedback: findAnswer('q9_progress_feedback') || '未填写',
-      communication: formatArray(findAnswer('q12_communication_methods'))
-    };
-  }
-
-  /**
-   * 获取请假类型显示文本
-   */
-  getLeaveTypeText(leaveType?: string): string {
-    const typeMap: Record<string, string> = {
-      'sick': '病假',
-      'personal': '事假',
-      'annual': '年假',
-      'other': '其他'
-    };
-    return typeMap[leaveType || ''] || '未知';
-  }
-
   /**
    * 阻止事件冒泡
    */

+ 22 - 1
src/modules/project/pages/project-detail/stages/stage-aftercare.component.ts

@@ -393,11 +393,32 @@ export class StageAftercareComponent implements OnInit, OnChanges {
 
       let wwauth = new WxworkAuth({ cid: this.cid });
       this.currentUser = await wwauth.currentProfile();
+      
+      // ⭐ 修复:添加权限判断逻辑(与其他阶段对齐)
+      if (this.currentUser) {
+        const role = this.currentUser.get('roleName') || '';
+        // 使用模糊匹配判断权限
+        const allowedRoles = ['客服','客服专员','客服主管','组员','组长','管理员','设计师','team-leader','设计组长'];
+        this.canEdit = allowedRoles.some(r => role.includes(r));
+        
+        // 容错:无法识别角色时,默认允许编辑
+        if (!role) {
+          this.canEdit = true;
+          console.warn('⚠️ 未获取到用户角色,默认允许编辑');
+        }
+        
+        console.log('🔍 售后归档阶段权限判断:', {
+          用户角色: role,
+          canEdit: this.canEdit,
+          匹配结果: allowedRoles.filter(r => role.includes(r))
+        });
+      }
 
       console.log('📦 组件初始化:', {
         cid: this.cid,
         projectId: this.projectId,
-        currentUser: this.currentUser?.get('name')
+        currentUser: this.currentUser?.get('name'),
+        canEdit: this.canEdit
       });
 
       await this.loadData();

+ 51 - 4
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -214,7 +214,21 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
         this.currentUser = await wxwork.currentProfile();
 
         const role = this.currentUser?.get('roleName') || '';
-        this.canEdit = ['客服', '组长', '管理员', '组员'].includes(role);
+        // ⭐ 修复:使用模糊匹配判断权限(与订单分配阶段对齐)
+        const allowedRoles = ['客服','客服专员','客服主管','组员','组长','管理员','设计师','team-leader','设计组长'];
+        // 允许模糊匹配(例如:客服-组员、客服(实习)等)
+        this.canEdit = allowedRoles.some(r => role.includes(r));
+        // 容错:无法识别角色时,默认允许编辑(避免误隐藏按钮)
+        if (!role) {
+          this.canEdit = true;
+          console.warn('⚠️ 未获取到用户角色,默认允许编辑');
+        }
+        
+        console.log('🔍 交付执行阶段权限判断:', {
+          用户角色: role,
+          canEdit: this.canEdit,
+          匹配结果: allowedRoles.filter(r => role.includes(r))
+        });
         
         // ========== 🔥 简化逻辑:URL参数是唯一判断标准 ==========
         let isTeamLeaderEntry = false;
@@ -425,15 +439,29 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
    * 上传交付文件
    */
   async uploadDeliveryFile(event: any, productId: string, deliveryType: string): Promise<void> {
-    console.log('🔥 uploadDeliveryFile 被调用', { productId, deliveryType, event });
+    console.log('🔥 [uploadDeliveryFile] 方法被调用', { 
+      productId, 
+      deliveryType, 
+      canEdit: this.canEdit,
+      currentUser: this.currentUser?.get('name'),
+      role: this.currentUser?.get('roleName'),
+      projectId: this.projectId || this.project?.id
+    });
     
     const files = event.target.files;
-    console.log('📁 选中的文件:', files);
+    console.log('📁 选中的文件:', files?.length || 0, '个');
     
     if (!files || files.length === 0) {
       console.warn('⚠️ 没有选中文件');
       return;
     }
+    
+    // ⭐ 关键检查:确认用户有编辑权限
+    if (!this.canEdit) {
+      console.error('❌ 权限不足:canEdit = false,无法上传文件');
+      window?.fmode?.alert('您没有上传文件的权限,请联系管理员');
+      return;
+    }
 
     try {
       this.uploadingDeliveryFiles = true;
@@ -1161,13 +1189,32 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
         data.deliveryApproval.approvedAt = now;
       }
       
+      // 🚀 审批通过后推进阶段到“尾款结算”(售后归档)
+      // 与 completeDelivery 保持一致,记录完成信息
+      this.project.set('currentStage', '尾款结算');
+      data.deliveryCompletedAt = now;
+      data.deliveryCompletedBy = this.currentUser.get('name');
+      data.deliveryFileCount = this.getTotalFileCount();
+
       this.project.set('data', data);
       
       console.log('💾 保存审批结果...');
       await this.project.save();
 
       console.log('✅ 交付执行审批通过!');
-      window?.fmode?.toast?.success?.('✅ 交付执行审批通过!');
+      window?.fmode?.toast?.success?.('✅ 交付执行审批通过,项目已进入“尾款结算”阶段!');
+      
+      // 📡 派发阶段完成事件,通知父组件刷新并前进
+      try {
+        const event = new CustomEvent('stage:completed', {
+          detail: { stage: 'delivery' },
+          bubbles: true,
+          cancelable: true
+        });
+        document.dispatchEvent(event);
+      } catch (e) {
+        console.warn('⚠️ 派发阶段完成事件失败(忽略):', e);
+      }
       
       // 刷新页面数据
       await this.loadApprovalHistory();