Переглянути джерело

feat: enhance project delivery stage with improved approval message display and image handling

- Updated the approval history section to a more compact message flow, improving readability and user experience.
- Enhanced image handling by integrating high-quality default interior images based on space type, ensuring better visual representation in project cases.
- Refactored the image selection logic to dynamically choose images based on the specified space name.
- Improved styling for approval status indicators, making them more visually distinct and user-friendly.
- Added console logging for better debugging and tracking of component initialization and data loading processes.
徐福静0235668 2 днів тому
батько
коміт
316825aee2

+ 50 - 9
src/app/pages/admin/services/project-auto-case.service.ts

@@ -293,14 +293,37 @@ export class ProjectAutoCaseService {
   }
   
   /**
-   * 生成模拟图片URL
+   * 生成模拟图片URL(使用真实的家装设计图片)
    */
   private generateMockImages(spaceName: string): string[] {
-    const baseUrl = 'https://placehold.co/800x600';
+    // 使用Unsplash的高质量家装设计图片
+    const defaultInteriorImages = [
+      'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=800&h=600&fit=crop', // 现代客厅
+      'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop', // 简约卧室
+      'https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=800&h=600&fit=crop', // 厨房设计
+      'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop', // 餐厅空间
+      'https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?w=800&h=600&fit=crop', // 书房设计
+      'https://images.unsplash.com/photo-1600573472550-8090b5e0745e?w=800&h=600&fit=crop', // 浴室设计
+      'https://images.unsplash.com/photo-1600585154526-990dced4db0d?w=800&h=600&fit=crop', // 儿童房
+      'https://images.unsplash.com/photo-1600566752355-35792bedcfea?w=800&h=600&fit=crop'  // 阳台设计
+    ];
+    
+    // 根据空间名称选择合适的图片
+    const spaceImageMap: Record<string, number[]> = {
+      '客厅': [0, 1],
+      '主卧': [1, 2],
+      '书房': [4, 0],
+      '儿童房': [6, 1],
+      '卫生间': [5, 3],
+      '厨房': [2, 3],
+      '餐厅': [3, 0]
+    };
+    
+    const indices = spaceImageMap[spaceName] || [0, 1];
     return [
-      `${baseUrl}/png?text=${encodeURIComponent(spaceName)}-效果图1`,
-      `${baseUrl}/png?text=${encodeURIComponent(spaceName)}-效果图2`,
-      `${baseUrl}/png?text=${encodeURIComponent(spaceName)}-效果图3`
+      defaultInteriorImages[indices[0]],
+      defaultInteriorImages[indices[1]],
+      defaultInteriorImages[(indices[0] + 2) % defaultInteriorImages.length]
     ];
   }
   
@@ -320,6 +343,18 @@ export class ProjectAutoCaseService {
       let totalPrice = 0;
       let totalArea = 0;
       
+      // 默认家装图片(如果没有上传图片则使用)
+      const defaultInteriorImages = [
+        'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=800&h=600&fit=crop', // 现代客厅
+        'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop', // 简约卧室
+        'https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=800&h=600&fit=crop', // 厨房设计
+        'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop', // 餐厅空间
+        'https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?w=800&h=600&fit=crop', // 书房设计
+        'https://images.unsplash.com/photo-1600573472550-8090b5e0745e?w=800&h=600&fit=crop', // 浴室设计
+        'https://images.unsplash.com/photo-1600585154526-990dced4db0d?w=800&h=600&fit=crop', // 儿童房
+        'https://images.unsplash.com/photo-1600566752355-35792bedcfea?w=800&h=600&fit=crop'  // 阳台设计
+      ];
+      
       // 从Product(空间)收集数据
       const productsDetail: any[] = [];
       for (const product of products) {
@@ -422,14 +457,20 @@ export class ProjectAutoCaseService {
       const defaultTags = ['全屋设计', '专业团队'];
       const finalTags = [...new Set([...styleTags, ...defaultTags])].slice(0, 5);
       
+      // 如果没有上传图片,使用默认家装图片
+      const finalImages = allImages.length > 0 ? allImages : defaultInteriorImages;
+      const finalCoverImage = finalImages[0] || defaultInteriorImages[0];
+      
+      console.log(`📸 案例图片: ${allImages.length > 0 ? '使用上传的图片' : '使用默认家装图片'}, 共 ${finalImages.length} 张`);
+      
       // 构建案例数据
       const caseData = {
         name: project.get('title') || '未命名案例',
         projectId: project.id,
         designerId: assignee?.id || '',
         teamId: department?.id || '',
-        coverImage: allImages[0] || 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600"><rect width="800" height="600" fill="%23f0f0f0"/><text x="50%" y="50%" text-anchor="middle" font-size="24" fill="%23999">暂无封面图</text></svg>',
-        images: allImages.slice(0, 20), // 最多20张图片
+        coverImage: finalCoverImage,
+        images: finalImages.slice(0, 20), // 最多20张图片
         totalPrice: totalPrice || projectData.quotation?.total || 0,
         completionDate: projectData.deliveryCompletedAt || projectData.completedAt || new Date(),
         tag: finalTags,
@@ -516,10 +557,10 @@ export class ProjectAutoCaseService {
           // 图片详细信息
           imagesDetail: projectData.imagesDetail || {
             beforeRenovation: [],
-            afterRenovation: allImages.map((url, idx) => ({
+            afterRenovation: finalImages.map((url, idx) => ({
               attachmentId: `img-${idx}`,
               url,
-              description: `效果图${idx + 1}`,
+              description: allImages.length > 0 ? `效果图${idx + 1}` : `家装设计参考${idx + 1}`,
               uploadDate: new Date().toISOString(),
               spaceArea: products[idx % products.length]?.get('productName') || ''
             })),

+ 56 - 161
src/modules/project/pages/project-detail/stages/stage-delivery.component.html

@@ -8,146 +8,56 @@
   }
 
   @if (!loading) {
-    <!-- 审批历史记录展示 -->
+    <!-- 审批消息流(紧凑显示) -->
     @if (project && approvalHistory.length > 0) {
-      <div class="approval-history-card">
-        <div class="card-header">
-          <h3 class="card-title">
-            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-              <path fill="currentColor" d="M336 64h32a48 48 0 0148 48v320a48 48 0 01-48 48H144a48 48 0 01-48-48V112a48 48 0 0148-48h32" opacity=".3"/>
-              <path fill="currentColor" d="M336 64h-80a48 48 0 00-96 0h-80a48 48 0 00-48 48v320a48 48 0 0048 48h224a48 48 0 0048-48V112a48 48 0 00-48-48zM256 32a16 16 0 11-16 16 16 16 0 0116-16zm112 400H144V112h224z"/>
-              <path fill="currentColor" d="M208 192h96v16h-96zm0 64h96v16h-96z"/>
-            </svg>
-            审批流程记录
-          </h3>
-          <p class="card-subtitle">查看项目的完整审批历史</p>
+      <div class="approval-messages-container">
+        <div class="messages-header">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
+            <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-38 312.38L137.4 280.8a24 24 0 0133.94-33.94l50.2 50.2 95.74-95.74a24 24 0 0133.94 33.94z"/>
+          </svg>
+          <span>审批记录 ({{ approvalHistory.length }})</span>
         </div>
-        <div class="card-content">
-          <div class="approval-timeline">
-            @for (record of approvalHistory; track $index; let isFirst = $first; let isLast = $last) {
-              <div class="timeline-item" [class.first]="isFirst" [class.last]="isLast">
-                <!-- 时间线节点 -->
-                <div class="timeline-node" [attr.data-status]="record.status">
+        <div class="approval-messages">
+          @for (record of approvalHistory; track $index) {
+            <div class="approval-message" [attr.data-status]="record.status">
+              <div class="message-icon">
+                @if (record.status === 'approved') {
+                  <span class="icon-emoji">✅</span>
+                } @else if (record.status === 'rejected') {
+                  <span class="icon-emoji">❌</span>
+                } @else {
+                  <span class="icon-emoji">⏳</span>
+                }
+              </div>
+              <div class="message-content">
+                <div class="message-header">
+                  <span class="message-stage">{{ record.stage }}</span>
+                  <span class="message-time">{{ record.submitTime | date: 'MM-dd HH:mm' }}</span>
+                </div>
+                <div class="message-body">
+                  <span class="message-user">{{ record.submitter?.name || '未知' }}</span>
                   @if (record.status === 'approved') {
-                    <svg class="icon status-icon" viewBox="0 0 512 512">
-                      <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-38 312.38L137.4 280.8a24 24 0 0133.94-33.94l50.2 50.2 95.74-95.74a24 24 0 0133.94 33.94z"/>
-                    </svg>
+                    <span class="message-text">提交审批,已通过</span>
                   } @else if (record.status === 'rejected') {
-                    <svg class="icon status-icon" viewBox="0 0 512 512">
-                      <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm52.697 283.697L256 279l-52.697 52.697-22.626-22.626L233.373 256l-52.696-52.697 22.626-22.626L256 233.373l52.697-52.696 22.626 22.626L278.627 256l52.696 52.697-22.626 22.626z"/>
-                    </svg>
+                    <span class="message-text">提交审批,已驳回</span>
                   } @else {
-                    <svg class="icon status-icon" viewBox="0 0 512 512">
-                      <path fill="currentColor" d="M256 464c114.87 0 208-93.13 208-208S370.87 48 256 48 48 141.13 48 256s93.13 208 208 208zm0-384c97 0 176 79 176 176s-79 176-176 176S80 353 80 256 159 80 256 80z"/>
-                      <path fill="currentColor" d="M256 176a32 32 0 11-32 32 32 32 0 0132-32m0 160a32 32 0 11-32 32 32 32 0 0132-32"/>
-                    </svg>
+                    <span class="message-text">提交审批,待处理</span>
+                  }
+                  @if (record.approver) {
+                    <span class="message-approver">· 审批人: {{ record.approver.name }}</span>
                   }
                 </div>
-                
-                <!-- 时间线连接线 -->
-                @if (!isLast) {
-                  <div class="timeline-connector"></div>
+                @if (record.comment) {
+                  <div class="message-comment">{{ record.comment }}</div>
                 }
-                
-                <!-- 审批内容 -->
-                <div class="timeline-content">
-                  <div class="approval-header">
-                    <div class="approval-stage-badge" [attr.data-status]="record.status">
-                      {{ record.stage }}
-                    </div>
-                    <div class="approval-status-badge" [attr.data-status]="record.status">
-                      @if (record.status === 'approved') {
-                        <span>✅ 已通过</span>
-                      } @else if (record.status === 'rejected') {
-                        <span>❌ 已驳回</span>
-                      } @else {
-                        <span>⏳ 待审批</span>
-                      }
-                    </div>
-                  </div>
-                  
-                  <div class="approval-details">
-                    <!-- 提交信息 -->
-                    <div class="approval-section">
-                      <div class="section-label">提交人</div>
-                      <div class="section-content">
-                        <div class="user-info">
-                          <span class="user-name">{{ record.submitter?.name || '未知' }}</span>
-                          <span class="user-role">{{ record.submitter?.role || '-' }}</span>
-                        </div>
-                        <div class="time-info">
-                          {{ record.submitTime | date: 'yyyy-MM-dd HH:mm' }}
-                        </div>
-                      </div>
-                    </div>
-                    
-                    <!-- 审批人信息(如果已审批) -->
-                    @if (record.approver) {
-                      <div class="approval-section">
-                        <div class="section-label">审批人</div>
-                        <div class="section-content">
-                          <div class="user-info">
-                            <span class="user-name">{{ record.approver?.name || '未知' }}</span>
-                            <span class="user-role">{{ record.approver?.role || '-' }}</span>
-                          </div>
-                          <div class="time-info">
-                            {{ record.approvalTime | date: 'yyyy-MM-dd HH:mm' }}
-                          </div>
-                        </div>
-                      </div>
-                    }
-                    
-                    <!-- 报价信息 -->
-                    @if (record.quotationTotal) {
-                      <div class="approval-section">
-                        <div class="section-label">报价总额</div>
-                        <div class="section-content">
-                          <span class="quotation-amount">¥{{ record.quotationTotal | number: '1.2-2' }}</span>
-                        </div>
-                      </div>
-                    }
-                    
-                    <!-- 团队信息 -->
-                    @if (record.teams && record.teams.length > 0) {
-                      <div class="approval-section">
-                        <div class="section-label">分配团队 ({{ record.teams.length }}人)</div>
-                        <div class="section-content">
-                          <div class="team-members">
-                            @for (member of record.teams; track member.id) {
-                              <div class="team-member-chip">
-                                <span class="member-name">{{ member.name }}</span>
-                                @if (member.spaces && member.spaces.length > 0) {
-                                  <span class="member-spaces">{{ member.spaces.join(', ') }}</span>
-                                }
-                              </div>
-                            }
-                          </div>
-                        </div>
-                      </div>
-                    }
-                    
-                    <!-- 审批意见/驳回原因 -->
-                    @if (record.comment || record.reason) {
-                      <div class="approval-section">
-                        <div class="section-label">
-                          {{ record.status === 'rejected' ? '驳回原因' : '审批意见' }}
-                        </div>
-                        <div class="section-content">
-                          <div class="approval-comment">
-                            {{ record.comment || record.reason || '-' }}
-                          </div>
-                        </div>
-                      </div>
-                    }
-                  </div>
-                </div>
               </div>
-            }
-          </div>
+            </div>
+          }
         </div>
       </div>
     }
 
+
     <!-- 场景Product选择标签 (第一层) -->
     @if (isMultiProductProject) {
       <div class="product-tabs-section">
@@ -331,42 +241,27 @@
                       <div class="file-uploader">上传: {{ file.uploadedBy }}</div>
                     }
                     
-                    <!-- ✨ 审批状态显示(参考售后归档红框样式) -->
-                    <div class="file-approval-status" [ngClass]="'has-' + file.approvalStatus">
-                      <div class="status-row">
-                        <span class="status-badge" [ngClass]="getApprovalStatusClass(file.approvalStatus)">
-                          {{ getApprovalStatusText(file.approvalStatus) }}
+                    <!-- ✨ 审批状态显示(紧凑单行样式) -->
+                    @if (file.approvalStatus && file.approvalStatus !== 'unverified') {
+                      <div class="file-approval-status" [ngClass]="'status-' + file.approvalStatus">
+                        <span class="status-icon">
+                          @if (file.approvalStatus === 'approved') {
+                            ✅
+                          } @else if (file.approvalStatus === 'rejected') {
+                            ❌
+                          } @else if (file.approvalStatus === 'pending') {
+                            🔍
+                          }
                         </span>
+                        <span class="status-text">{{ getApprovalStatusText(file.approvalStatus) }}</span>
+                        @if (file.approvedBy) {
+                          <span class="status-by">· {{ file.approvedBy }}</span>
+                        }
+                        @if (file.rejectionReason) {
+                          <span class="status-reason">: {{ file.rejectionReason }}</span>
+                        }
                       </div>
-                      
-                      @if (file.approvalStatus === 'approved') {
-                        <div class="approval-details">
-                          @if (file.approvedBy) {
-                            <div class="approval-info">
-                              <svg class="icon-small" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-                                <path fill="currentColor" d="M258.9 48C141.92 46.42 46.42 141.92 48 258.9c1.56 112.19 92.91 203.54 205.1 205.1 117 1.6 212.48-93.9 210.88-210.88C462.44 140.91 371.09 49.56 258.9 48zm-16.79 192.47l51.55 51.55a12 12 0 010 17l-5.66 5.66a12 12 0 01-17 0l-51.55-51.55-51.55 51.55a12 12 0 01-17 0l-5.66-5.66a12 12 0 010-17l51.55-51.55-51.55-51.55a12 12 0 010-17l5.66-5.66a12 12 0 0117 0l51.55 51.55 51.55-51.55a12 12 0 0117 0l5.66 5.66a12 12 0 010 17z"/>
-                              </svg>
-                              <span>审批: {{ file.approvedBy }}</span>
-                            </div>
-                          }
-                          @if (file.approvedAt) {
-                            <div class="approval-time">{{ file.approvedAt | date: 'yyyy-MM-dd HH:mm' }}</div>
-                          }
-                        </div>
-                      }
-                      
-                      @if (file.approvalStatus === 'rejected' && file.rejectionReason) {
-                        <div class="rejection-reason">
-                          <div class="rejection-header">
-                            <svg class="icon-small" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-                              <path fill="currentColor" d="M85.57 446.25h340.86a32 32 0 0028.17-47.17L284.18 82.58c-12.09-22.44-44.27-22.44-56.36 0L57.4 399.08a32 32 0 0028.17 47.17z"/>
-                            </svg>
-                            <span class="label">驳回原因</span>
-                          </div>
-                          <p class="reason">{{ file.rejectionReason }}</p>
-                        </div>
-                      }
-                    </div>
+                    }
                   </div>
                 </div>
               }

+ 125 - 330
src/modules/project/pages/project-detail/stages/stage-delivery.component.scss

@@ -3,275 +3,137 @@
   background-color: #f8f9fa;
   min-height: 100vh;
 
-  // 审批历史记录卡片
-  .approval-history-card {
+  // 审批消息流(紧凑样式)
+  .approval-messages-container {
     background: white;
-    border-radius: 16px;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
-    margin-bottom: 24px;
-    overflow: hidden;
+    border-radius: 12px;
+    padding: 12px 16px;
+    margin-bottom: 16px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
 
-    .card-header {
-      padding: 20px 24px;
+    .messages-header {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 13px;
+      font-weight: 600;
+      color: #6c757d;
+      margin-bottom: 12px;
+      padding-bottom: 8px;
       border-bottom: 1px solid #e9ecef;
-      
-      .card-title {
-        display: flex;
-        align-items: center;
-        gap: 12px;
-        font-size: 18px;
-        font-weight: 700;
-        color: #212529;
-        margin: 0 0 8px 0;
 
-        .icon {
-          width: 24px;
-          height: 24px;
-          color: #6366f1;
-        }
-      }
-
-      .card-subtitle {
-        color: #6c757d;
-        font-size: 14px;
-        margin: 0;
+      .icon {
+        width: 16px;
+        height: 16px;
+        color: #6366f1;
       }
     }
 
-    .card-content {
-      padding: 24px;
-    }
-
-    // 时间线样式
-    .approval-timeline {
-      position: relative;
+    .approval-messages {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
 
-      .timeline-item {
-        position: relative;
+      .approval-message {
         display: flex;
-        gap: 20px;
-        padding-bottom: 32px;
+        gap: 10px;
+        padding: 8px 12px;
+        border-radius: 8px;
+        background: #f8f9fa;
+        transition: all 0.2s ease;
 
-        &:last-child {
-          padding-bottom: 0;
+        &:hover {
+          background: #e9ecef;
         }
 
-        // 时间线节点
-        .timeline-node {
+        &[data-status="approved"] {
+          background: #d1fae5;
+          border-left: 3px solid #10b981;
+        }
+
+        &[data-status="rejected"] {
+          background: #fee2e2;
+          border-left: 3px solid #ef4444;
+        }
+
+        &[data-status="pending"] {
+          background: #fef3c7;
+          border-left: 3px solid #f59e0b;
+        }
+
+        .message-icon {
           flex-shrink: 0;
-          width: 48px;
-          height: 48px;
-          border-radius: 50%;
+          width: 24px;
+          height: 24px;
           display: flex;
           align-items: center;
           justify-content: center;
-          background: white;
-          border: 3px solid #e9ecef;
-          z-index: 2;
-          transition: all 0.3s ease;
-
-          .status-icon {
-            width: 24px;
-            height: 24px;
-          }
-
-          &[data-status="approved"] {
-            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
-            border-color: #10b981;
-            
-            .status-icon {
-              color: white;
-            }
-          }
 
-          &[data-status="rejected"] {
-            background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
-            border-color: #ef4444;
-            
-            .status-icon {
-              color: white;
-            }
+          .icon-emoji {
+            font-size: 18px;
+            line-height: 1;
           }
-
-          &[data-status="pending"] {
-            background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
-            border-color: #f59e0b;
-            animation: pulse-pending 2s ease-in-out infinite;
-            
-            .status-icon {
-              color: white;
-            }
-          }
-        }
-
-        // 时间线连接线
-        .timeline-connector {
-          position: absolute;
-          left: 23px;
-          top: 48px;
-          bottom: -32px;
-          width: 2px;
-          background: linear-gradient(180deg, #e9ecef 0%, #dee2e6 100%);
-          z-index: 1;
         }
 
-        // 审批内容
-        .timeline-content {
+        .message-content {
           flex: 1;
-          background: #f8f9fa;
-          border-radius: 12px;
-          padding: 20px;
-          border: 1px solid #e9ecef;
-          transition: all 0.3s ease;
+          min-width: 0;
 
-          &:hover {
-            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-            border-color: #6366f1;
-          }
-
-          .approval-header {
+          .message-header {
             display: flex;
             align-items: center;
-            gap: 12px;
-            margin-bottom: 16px;
+            justify-content: space-between;
+            margin-bottom: 4px;
 
-            .approval-stage-badge {
-              background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
-              color: white;
-              padding: 6px 14px;
-              border-radius: 20px;
-              font-size: 13px;
+            .message-stage {
+              font-size: 12px;
               font-weight: 600;
-              letter-spacing: 0.3px;
+              color: #374151;
             }
 
-            .approval-status-badge {
-              padding: 6px 14px;
-              border-radius: 20px;
-              font-size: 13px;
-              font-weight: 600;
-
-              &[data-status="approved"] {
-                background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
-                color: #065f46;
-              }
-
-              &[data-status="rejected"] {
-                background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
-                color: #991b1b;
-              }
-
-              &[data-status="pending"] {
-                background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
-                color: #92400e;
-              }
+            .message-time {
+              font-size: 11px;
+              color: #9ca3af;
             }
           }
 
-          .approval-details {
+          .message-body {
+            font-size: 13px;
+            color: #4b5563;
             display: flex;
-            flex-direction: column;
-            gap: 16px;
-
-            .approval-section {
-              .section-label {
-                font-size: 12px;
-                font-weight: 600;
-                color: #6c757d;
-                text-transform: uppercase;
-                letter-spacing: 0.5px;
-                margin-bottom: 8px;
-              }
-
-              .section-content {
-                display: flex;
-                flex-direction: column;
-                gap: 8px;
-
-                .user-info {
-                  display: flex;
-                  align-items: center;
-                  gap: 8px;
-
-                  .user-name {
-                    font-size: 14px;
-                    font-weight: 600;
-                    color: #212529;
-                  }
-
-                  .user-role {
-                    font-size: 12px;
-                    color: #6c757d;
-                    background: white;
-                    padding: 2px 8px;
-                    border-radius: 4px;
-                  }
-                }
-
-                .time-info {
-                  font-size: 13px;
-                  color: #6c757d;
-                }
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 4px;
 
-                .quotation-amount {
-                  font-size: 18px;
-                  font-weight: 700;
-                  color: #6366f1;
-                }
+            .message-user {
+              font-weight: 600;
+              color: #1f2937;
+            }
 
-                .team-members {
-                  display: flex;
-                  flex-wrap: wrap;
-                  gap: 8px;
-
-                  .team-member-chip {
-                    display: flex;
-                    flex-direction: column;
-                    gap: 4px;
-                    background: white;
-                    padding: 8px 12px;
-                    border-radius: 8px;
-                    border: 1px solid #e9ecef;
-
-                    .member-name {
-                      font-size: 13px;
-                      font-weight: 600;
-                      color: #212529;
-                    }
-
-                    .member-spaces {
-                      font-size: 11px;
-                      color: #6c757d;
-                    }
-                  }
-                }
+            .message-text {
+              color: #6b7280;
+            }
 
-                .approval-comment {
-                  background: white;
-                  padding: 12px;
-                  border-radius: 8px;
-                  font-size: 14px;
-                  color: #495057;
-                  line-height: 1.6;
-                  border-left: 3px solid #6366f1;
-                }
-              }
+            .message-approver {
+              color: #9ca3af;
+              font-size: 12px;
             }
           }
+
+          .message-comment {
+            margin-top: 6px;
+            padding: 6px 10px;
+            background: rgba(255, 255, 255, 0.6);
+            border-radius: 6px;
+            font-size: 12px;
+            color: #6b7280;
+            font-style: italic;
+          }
         }
       }
     }
   }
 
-  @keyframes pulse-pending {
-    0%, 100% {
-      box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
-    }
-    50% {
-      box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
-    }
-  }
-
   // 加载状态
   .loading-state {
     display: flex;
@@ -1345,119 +1207,52 @@
   }
 }
 
-// ✨ 审批状态样式(参考售后归档样式优化 - 红框样式)
+// ✨ 审批状态样式(紧凑单行样式)
 .file-approval-status {
-  margin-top: 12px;
-  padding: 12px;
-  background: #f5f5f5;
-  border-radius: 8px;
+  margin-top: 8px;
+  padding: 6px 10px;
+  background: #f8f9fa;
+  border-radius: 6px;
   font-size: 12px;
-  line-height: 1.5;
-  border-left: 4px solid #e0e0e0;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  border-left: 3px solid #e0e0e0;
 
-  .status-row { 
-    margin-bottom: 8px; 
-    display: flex;
-    align-items: center;
-    gap: 8px;
+  .status-icon {
+    font-size: 14px;
+    line-height: 1;
   }
 
-  .status-badge {
-    display: inline-block;
-    padding: 4px 12px;
-    border-radius: 12px;
-    font-size: 12px;
+  .status-text {
     font-weight: 600;
-    flex-shrink: 0;
-    
-    &.status-unverified {
-      background: rgba(255, 196, 9, 0.1);
-      color: #ffc409;
-      border: 1px solid #ffc409;
-    }
-    
-    &.status-pending {
-      background: rgba(56, 128, 255, 0.1);
-      color: #3880ff;
-      border: 1px solid #3880ff;
-    }
-    
-    &.status-approved {
-      background: rgba(45, 211, 111, 0.1);
-      color: #2dd36f;
-      border: 1px solid #2dd36f;
-    }
-    
-    &.status-rejected {
-      background: rgba(235, 68, 90, 0.1);
-      color: #eb445a;
-      border: 1px solid #eb445a;
-    }
-  }
-  
-  // 根据状态改变整个区域的边框颜色
-  &.has-unverified { border-left-color: #ffc409; }
-  &.has-pending { border-left-color: #3880ff; }
-  &.has-approved { border-left-color: #2dd36f; background: rgba(45, 211, 111, 0.05); }
-  &.has-rejected { border-left-color: #eb445a; background: rgba(235, 68, 90, 0.05); }
-
-  .approval-details {
-    margin-top: 6px;
-    
-    .approval-info {
-      display: flex;
-      align-items: center;
-      gap: 6px;
-      font-size: 11px;
-      color: #155724;
-      margin-bottom: 4px;
-      
-      .icon-small {
-        width: 14px;
-        height: 14px;
-      }
-    }
-    
-    .approval-time {
-      font-size: 10px;
-      color: #6c757d;
-      margin-left: 20px;
-    }
+    color: #495057;
   }
 
-  .rejection-reason {
-    margin-top: 8px;
-    padding: 10px;
-    background: #fff5f5;
-    border-left: 3px solid #e74c3c;
-    border-radius: 4px;
+  .status-by {
+    color: #6c757d;
+    font-size: 11px;
+  }
 
-    .rejection-header {
-      display: flex;
-      align-items: center;
-      gap: 6px;
-      margin-bottom: 6px;
-      
-      .icon-small {
-        width: 16px;
-        height: 16px;
-        color: #eb445a;
-      }
-      
-      .label {
-        font-weight: 600;
-        color: #721c24;
-        font-size: 12px;
-      }
-    }
+  .status-reason {
+    color: #6c757d;
+    font-size: 11px;
+    font-style: italic;
+  }
 
-    .reason {
-      margin: 0;
-      color: #333;
-      font-size: 12px;
-      line-height: 1.5;
-      padding-left: 22px;
-    }
+  &.status-pending {
+    background: rgba(56, 128, 255, 0.1);
+    border-left-color: #3880ff;
+  }
+  
+  &.status-approved {
+    background: rgba(45, 211, 111, 0.1);
+    border-left-color: #2dd36f;
+  }
+  
+  &.status-rejected {
+    background: rgba(235, 68, 90, 0.1);
+    border-left-color: #eb445a;
   }
 }
 

+ 83 - 3
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -148,6 +148,8 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
   ) {}
 
   async ngOnInit() {
+    console.log('🚀 StageDeliveryComponent 初始化...');
+    
     // 从路由或Input获取参数
     this.cid = this.route.parent?.snapshot.paramMap.get('cid') || '';
     // 判断是否为管理员后台视图(/admin 路由)
@@ -157,6 +159,14 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
     } catch {}
     this.projectId = this.route.parent?.snapshot.paramMap.get('projectId') || '';
 
+    console.log('📋 初始化参数:', {
+      cid: this.cid,
+      projectId: this.projectId,
+      isAdminView: this.isAdminView,
+      canEdit: this.canEdit,
+      hasProject: !!this.project
+    });
+
     await this.loadData();
 
     // 周期性刷新交付文件以同步组长端审批状态
@@ -179,24 +189,33 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
    */
   async loadData() {
     try {
+      console.log('📥 开始加载数据...');
       this.loading = true;
 
       // 如果没有project对象,尝试加载
       if (!this.project && this.projectId) {
+        console.log('📡 加载项目数据:', this.projectId);
         const query = new Parse.Query('Project');
         query.include('contact', 'assignee');
         this.project = await query.get(this.projectId);
         this.customer = this.project.get('contact');
+        console.log('✅ 项目加载成功:', this.project.get('title'));
       }
 
       // 如果没有currentUser,尝试获取
       if (!this.currentUser && this.cid) {
+        console.log('👤 获取当前用户...');
         // @ts-ignore - WxworkAuth type issue with fmode-ng
         const wxwork = new WxworkAuth({ cid: this.cid, appId: 'crm' });
         this.currentUser = await wxwork.currentProfile();
 
         const role = this.currentUser?.get('roleName') || '';
         this.canEdit = ['客服', '组长', '管理员', '组员'].includes(role);
+        console.log('✅ 用户信息:', {
+          name: this.currentUser?.get('name'),
+          role,
+          canEdit: this.canEdit
+        });
       }
 
       // 加载项目场景Product数据
@@ -326,6 +345,7 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
    * 选择场景Product
    */
   selectProduct(productId: string): void {
+    console.log('🏠 选择场景Product:', productId);
     this.activeProductId = productId;
     this.cdr.markForCheck();
   }
@@ -334,7 +354,13 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
    * 选择交付类型
    */
   selectDeliveryType(typeId: string): void {
+    console.log('📦 选择交付类型:', typeId);
     this.activeDeliveryType = typeId;
+    console.log('当前状态:', {
+      activeProductId: this.activeProductId,
+      activeDeliveryType: this.activeDeliveryType,
+      canEdit: this.canEdit
+    });
     this.cdr.markForCheck();
   }
 
@@ -342,16 +368,31 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
    * 上传交付文件
    */
   async uploadDeliveryFile(event: any, productId: string, deliveryType: string): Promise<void> {
+    console.log('🔥 uploadDeliveryFile 被调用', { productId, deliveryType, event });
+    
     const files = event.target.files;
-    if (!files || files.length === 0) return;
+    console.log('📁 选中的文件:', files);
+    
+    if (!files || files.length === 0) {
+      console.warn('⚠️ 没有选中文件');
+      return;
+    }
 
     try {
       this.uploadingDeliveryFiles = true;
       this.uploadProgress = 0;
       const targetProjectId = this.projectId || this.project?.id;
 
+      console.log('📋 项目信息:', {
+        targetProjectId,
+        productId,
+        deliveryType,
+        fileCount: files.length
+      });
+
       if (!targetProjectId) {
-        console.error('未找到项目ID,无法上传文件');
+        console.error('❌ 未找到项目ID,无法上传文件');
+        window?.fmode?.alert('未找到项目ID,无法上传文件');
         return;
       }
 
@@ -370,13 +411,16 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
 
       for (let i = 0; i < files.length; i++) {
         const file = files[i];
+        console.log(`📤 开始上传文件 ${i + 1}/${files.length}:`, file.name, `(${(file.size / 1024 / 1024).toFixed(2)}MB)`);
 
         // 文件大小验证 (50MB)
         if (file.size > 50 * 1024 * 1024) {
-          console.warn(`文件 ${file.name} 超过50MB限制,跳过`);
+          console.warn(`⚠️ 文件 ${file.name} 超过50MB限制,跳过`);
           continue;
         }
 
+        console.log('📡 调用 ProjectFileService.uploadProjectFileWithRecord...');
+        
         // 使用ProjectFileService上传到服务器并创建ProjectFile记录
         const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
           file,
@@ -400,6 +444,8 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
             this.cdr.markForCheck();
           }
         );
+        
+        console.log('✅ ProjectFile 创建成功:', projectFile.id);
 
         // 创建交付文件记录
         const deliveryFile: DeliveryFile = {
@@ -432,6 +478,8 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
       // ✨ 上传成功后通知组长审批
       if (uploadedFiles > 0) {
         await this.notifyTeamLeaderForApproval(uploadedFiles, deliveryType);
+        // 二次校验:从服务器重新查询,确认已写入 ProjectFile 表
+        await this.verifyProjectFilesOnServer(targetProjectId, productId, deliveryType);
       }
 
     } catch (error) {
@@ -444,6 +492,38 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
     }
   }
 
+  /**
+   * 从服务器校验 ProjectFile 是否写入成功(仅日志输出)
+   */
+  private async verifyProjectFilesOnServer(
+    projectId: string,
+    productId: string,
+    deliveryType: string
+  ): Promise<void> {
+    try {
+      const results = await this.projectFileService.getProjectFiles(projectId, {
+        fileType: `delivery_${deliveryType}`,
+        stage: 'delivery',
+        spaceId: productId
+      } as any);
+
+      const simplified = results.map((pf: any) => ({
+        id: pf.id,
+        name: pf.get('fileName'),
+        url: pf.get('fileUrl'),
+        size: pf.get('fileSize'),
+        createdAt: pf.createdAt
+      }));
+
+      console.log(
+        `✅ 校验成功:已在 ProjectFile 表找到 ${simplified.length} 条记录(projectId=${projectId}, spaceId=${productId}, type=delivery_${deliveryType})`,
+        simplified.slice(0, 5)
+      );
+    } catch (err) {
+      console.error('❌ 校验 ProjectFile 失败:', err);
+    }
+  }
+
   /**
    * 通知组长审批交付文件
    */