Quellcode durchsuchen

feat: enhance customer management and project features with responsive design

- Added mobile-responsive styles for improved user experience on various devices.
- Introduced a customer profile modal with detailed information and follow-up history.
- Enhanced project management interface with improved layout and functionality.
- Implemented activity logging for user actions across the application.
- Updated various components to support new features and ensure consistency in design.
徐福静0235668 vor 10 Monaten
Ursprung
Commit
23bbca9d81
32 geänderte Dateien mit 8685 neuen und 673 gelöschten Zeilen
  1. 265 4
      src/app/pages/admin/customers/customers.html
  2. 909 6
      src/app/pages/admin/customers/customers.scss
  3. 280 2
      src/app/pages/admin/customers/customers.ts
  4. 95 58
      src/app/pages/admin/dashboard/dashboard.html
  5. 885 1
      src/app/pages/admin/dashboard/dashboard.scss
  6. 93 1
      src/app/pages/admin/dashboard/dashboard.ts
  7. 131 16
      src/app/pages/admin/employees/employees.html
  8. 627 16
      src/app/pages/admin/employees/employees.scss
  9. 48 2
      src/app/pages/admin/employees/employees.ts
  10. 175 36
      src/app/pages/admin/groupchats/groupchats.html
  11. 495 0
      src/app/pages/admin/groupchats/groupchats.scss
  12. 14 12
      src/app/pages/admin/project-management/project-management.html
  13. 140 0
      src/app/pages/admin/project-management/project-management.scss
  14. 55 0
      src/app/pages/admin/services/project.service.ts
  15. 9 4
      src/app/pages/customer-service/case-library/case-detail-panel.component.ts
  16. 44 3
      src/app/pages/customer-service/case-library/case-library.html
  17. 417 9
      src/app/pages/customer-service/case-library/case-library.scss
  18. 242 316
      src/app/pages/customer-service/case-library/case-library.ts
  19. 580 0
      src/app/pages/customer-service/case-library/case-management-modal.component.ts
  20. 318 0
      src/app/pages/customer-service/dashboard/dashboard-urgent-tasks-enhanced.scss
  21. 130 35
      src/app/pages/customer-service/dashboard/dashboard.html
  22. 249 1
      src/app/pages/customer-service/dashboard/dashboard.scss
  23. 194 126
      src/app/pages/customer-service/dashboard/dashboard.ts
  24. 6 17
      src/app/pages/customer-service/project-list/project-list.html
  25. 284 0
      src/app/pages/customer-service/project-list/project-list.scss
  26. 32 6
      src/app/pages/customer-service/project-list/project-list.ts
  27. 419 0
      src/app/services/activity-log.service.ts
  28. 665 0
      src/app/services/case.service.ts
  29. 390 0
      src/app/services/urgent-task.service.ts
  30. 29 2
      src/modules/project/pages/project-loader/project-loader.component.ts
  31. 3 0
      src/styles.scss
  32. 462 0
      src/styles/mobile-responsive.scss

+ 265 - 4
src/app/pages/admin/customers/customers.html

@@ -58,12 +58,273 @@
     </div>
   </div>
 
-  <!-- 覆盖层弹出 contact 详情 -->
-  <div class="customer-panel-overlay" *ngIf="showCustomerPanel" (click)="closeCustomerPanel()">
+  <!-- 客户画像弹窗 -->
+  @if (showCustomerPanel && selectedCustomer) {
+    <div class="customer-panel-overlay" (click)="closeCustomerPanel()">
     <div class="customer-panel" (click)="$event.stopPropagation()">
+        <!-- 弹窗头部 -->
+        <div class="panel-header">
+          <div class="header-left">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+              <circle cx="12" cy="7" r="4"></circle>
+            </svg>
+            <h3>客户画像</h3>
+          </div>
+          <button class="close-btn" (click)="closeCustomerPanel()">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <line x1="18" y1="6" x2="6" y2="18"></line>
+              <line x1="6" y1="6" x2="18" y2="18"></line>
+            </svg>
+          </button>
+        </div>
+
+        <!-- 弹窗内容 -->
       <div class="panel-body">
-        <app-contact *ngIf="selectedCustomer" [customer]="selectedCustomer" [currentUser]="currentUserForContact" [embeddedMode]="true" [projectIdFilter]="panelProjectId" (close)="closeCustomerPanel(true)"></app-contact>
+          <!-- 基本信息卡片 -->
+          <div class="profile-card">
+            <div class="profile-header">
+              <img 
+                [src]="getCustomerAvatar(selectedCustomer)" 
+                class="profile-avatar" 
+                alt="客户头像"
+              />
+              <div class="profile-info">
+                <h2 class="profile-name">{{ getCustomerName(selectedCustomer) }}</h2>
+                <div class="profile-meta">
+                  <span class="meta-item">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
+                    </svg>
+                    {{ getCustomerMobile(selectedCustomer) }}
+                  </span>
+                  <span class="meta-item">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
+                      <line x1="16" y1="2" x2="16" y2="6"></line>
+                      <line x1="8" y1="2" x2="8" y2="6"></line>
+                      <line x1="3" y1="10" x2="21" y2="10"></line>
+                    </svg>
+                    {{ getCustomerCreatedAt(selectedCustomer) }}
+                  </span>
+                  <span class="meta-item" *ngIf="getCustomerSource(selectedCustomer) !== '-'">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
+                      <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
+                    </svg>
+                    {{ getCustomerSource(selectedCustomer) }}
+                  </span>
+                </div>
+                <div class="profile-tags" *ngIf="getCustomerTags(selectedCustomer).length > 0">
+                  <span class="tag" *ngFor="let tag of getCustomerTags(selectedCustomer)">{{ tag }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 详细信息网格 -->
+          <div class="info-grid">
+            <!-- 企微信息 -->
+            <div class="info-section">
+              <div class="section-title">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <circle cx="12" cy="12" r="10"></circle>
+                  <line x1="2" y1="12" x2="22" y2="12"></line>
+                  <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
+                </svg>
+                企微信息
+              </div>
+              <div class="info-items">
+                <div class="info-item">
+                  <label>企微ID</label>
+                  <div class="info-value">{{ selectedCustomer.get('external_userid') || '-' }}</div>
+                </div>
+                <div class="info-item">
+                  <label>客户类型</label>
+                  <div class="info-value">
+                    <span class="badge" [class.badge-enterprise]="getCustomerType(selectedCustomer) === '企业成员'">
+                      {{ getCustomerType(selectedCustomer) }}
+                    </span>
+                  </div>
+                </div>
+                <div class="info-item">
+                  <label>来源</label>
+                  <div class="info-value">{{ getCustomerSource(selectedCustomer) }}</div>
+                </div>
+              </div>
+            </div>
+
+            <!-- 联系方式 -->
+            <div class="info-section">
+              <div class="section-title">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+                </svg>
+                联系方式
+              </div>
+              <div class="info-items">
+                <div class="info-item">
+                  <label>手机号</label>
+                  <div class="info-value phone">{{ selectedCustomer.get('mobile') || '-' }}</div>
+                </div>
+                <div class="info-item">
+                  <label>微信号</label>
+                  <div class="info-value">{{ getCustomerWechat(selectedCustomer) || '-' }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 客户画像 -->
+          <div class="profile-section" *ngIf="hasCustomerProfile(selectedCustomer)">
+            <div class="section-title-large">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
+              </svg>
+              客户画像
+            </div>
+            <div class="profile-details">
+              <!-- 风格偏好 -->
+              <div class="profile-item" *ngIf="getCustomerStyles(selectedCustomer).length > 0">
+                <label>风格偏好</label>
+                <div class="tag-list">
+                  <span class="tag tag-style" *ngFor="let style of getCustomerStyles(selectedCustomer)">{{ style }}</span>
+                </div>
+              </div>
+              <!-- 预算范围 -->
+              <div class="profile-item" *ngIf="getCustomerBudget(selectedCustomer)">
+                <label>预算范围</label>
+                <div class="info-value budget">{{ getCustomerBudget(selectedCustomer) }}</div>
+              </div>
+              <!-- 色彩氛围 -->
+              <div class="profile-item" *ngIf="getCustomerColor(selectedCustomer)">
+                <label>色彩氛围</label>
+                <div class="info-value">{{ getCustomerColor(selectedCustomer) }}</div>
+              </div>
+              <!-- 需求类型 -->
+              <div class="profile-item" *ngIf="getCustomerDemand(selectedCustomer)">
+                <label>需求类型</label>
+                <div class="info-value">{{ getCustomerDemand(selectedCustomer) }}</div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 所在群聊 -->
+          <div class="groups-section" *ngIf="customerGroups().length > 0">
+            <div class="section-title-large">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                <circle cx="9" cy="7" r="4"></circle>
+                <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+              </svg>
+              所在群聊 ({{ customerGroups().length }})
+            </div>
+            <p class="section-subtitle">点击可跳转到群聊</p>
+            <div class="groups-grid">
+              <div class="group-card" *ngFor="let group of customerGroups()" (click)="navigateToGroupChat(group)">
+                <div class="group-icon">
+                  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                    <circle cx="9" cy="7" r="4"></circle>
+                    <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                    <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+                  </svg>
+                </div>
+                <div class="group-info">
+                  <h4 class="group-name">{{ getGroupName(group) }}</h4>
+                  <p class="group-project" *ngIf="getGroupProject(group)">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
+                    </svg>
+                    {{ getGroupProject(group) }}
+                  </p>
+                  <p class="group-no-project" *ngIf="!getGroupProject(group)">暂无关联项目</p>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 历史项目 -->
+          <div class="projects-section" *ngIf="customerProjects().length > 0">
+            <div class="section-title-large">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
+                <polyline points="9 22 9 12 15 12 15 22"></polyline>
+              </svg>
+              历史项目 ({{ customerProjects().length }})
+            </div>
+            <div class="projects-list">
+              <div class="project-card" *ngFor="let project of customerProjects()">
+                <div class="project-content">
+                  <div class="project-header">
+                    <h4>{{ getProjectTitle(project) }}</h4>
+                    <span class="project-status" [class]="'status-' + getProjectStatus(project)">
+                      {{ getProjectStatus(project) }}
+                    </span>
+                  </div>
+                  <div class="project-details">
+                    <div class="project-stage">
+                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                        <circle cx="12" cy="12" r="10"></circle>
+                        <polyline points="12 6 12 12 16 14"></polyline>
+                      </svg>
+                      <span>{{ getProjectStage(project) }}</span>
+                    </div>
+                    <div class="project-time">
+                      更新时间: {{ getProjectDate(project) }}
+                    </div>
+                  </div>
+                </div>
+                <button class="project-arrow" (click)="navigateToProject(project)">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <polyline points="9 18 15 12 9 6"></polyline>
+                  </svg>
+                </button>
+              </div>
+            </div>
+          </div>
+
+          <!-- 跟进记录时间线 -->
+          <div class="timeline-section" *ngIf="customerFollowUps().length > 0">
+            <div class="section-title-large">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <circle cx="12" cy="12" r="10"></circle>
+                <polyline points="12 6 12 12 16 14"></polyline>
+              </svg>
+              跟进记录 ({{ customerFollowUps().length }})
+            </div>
+            <div class="timeline">
+              <div class="timeline-item" *ngFor="let record of customerFollowUps(); let isLast = last">
+                <div class="timeline-marker">
+                  <div class="timeline-dot"></div>
+                  <div class="timeline-line" *ngIf="!isLast"></div>
+                </div>
+                <div class="timeline-content">
+                  <div class="timeline-header">
+                    <span class="timeline-operator">{{ getFollowUpOperator(record) }}</span>
+                    <span class="timeline-time">{{ getFollowUpTime(record) }}</span>
+                  </div>
+                  <div class="timeline-body">
+                    <span class="timeline-type">{{ getFollowUpType(record) }}</span>
+                    <p class="timeline-text">{{ getFollowUpContent(record) }}</p>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 无数据提示 -->
+          <div class="empty-state" *ngIf="!hasCustomerProfile(selectedCustomer) && customerProjects().length === 0">
+            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+              <circle cx="12" cy="12" r="10"></circle>
+              <line x1="12" y1="16" x2="12" y2="12"></line>
+              <line x1="12" y1="8" x2="12.01" y2="8"></line>
+            </svg>
+            <p>暂无客户画像数据</p>
+          </div>
+        </div>
       </div>
     </div>
-  </div>
+  }
 </div>

+ 909 - 6
src/app/pages/admin/customers/customers.scss

@@ -7,9 +7,912 @@
 .empty{padding:24px;text-align:center;color:#94a3b8}
 @media (max-width: 992px){.stats-cards{grid-template-columns:repeat(2,1fr)}.search input{width:100%}.toolbar{flex-direction:column;gap:10px;align-items:flex-start}.table{grid-template-columns:1.6fr 1fr 1.4fr .9fr .9fr 1fr}}
 
-/* 客户详情面板样式(覆盖层弹出) */
-.customer-panel-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:2000;animation:fadeIn .2s ease-out}
-.customer-panel{width:90%;max-width:900px;max-height:90vh;background:#fff;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.15);display:flex;flex-direction:column;animation:slideUp .3s ease-out;overflow:hidden}
-.panel-body{flex:1;overflow:auto}
-@keyframes fadeIn{from{opacity:0}to{opacity:1}}
-@keyframes slideUp{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}
+/* 客户画像弹窗样式 */
+.customer-panel-overlay {
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.6);
+  backdrop-filter: blur(4px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 2000;
+  animation: fadeIn 0.3s ease-out;
+  padding: 20px;
+}
+
+.customer-panel {
+  width: 100%;
+  max-width: 1000px;
+  max-height: 90vh;
+  background: #fff;
+  border-radius: 16px;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+  display: flex;
+  flex-direction: column;
+  animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);
+  overflow: hidden;
+}
+
+.panel-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid #e5e7eb;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+
+    svg {
+      flex-shrink: 0;
+    }
+
+    h3 {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+    }
+  }
+
+  .close-btn {
+    width: 32px;
+    height: 32px;
+    border-radius: 8px;
+    border: none;
+    background: rgba(255, 255, 255, 0.2);
+    color: white;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.2s;
+
+    &:hover {
+      background: rgba(255, 255, 255, 0.3);
+      transform: rotate(90deg);
+    }
+  }
+}
+
+.panel-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+  background: #fafafa;
+
+  /* 自定义滚动条 */
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+
+    &:hover {
+      background: #a1a1a1;
+    }
+  }
+}
+
+/* 基本信息卡片 */
+.profile-card {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.profile-header {
+  display: flex;
+  gap: 20px;
+  align-items: flex-start;
+}
+
+.profile-avatar {
+  width: 80px;
+  height: 80px;
+  border-radius: 12px;
+  object-fit: cover;
+  border: 3px solid #f0f0f0;
+  flex-shrink: 0;
+}
+
+.profile-info {
+  flex: 1;
+}
+
+.profile-name {
+  margin: 0 0 12px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+}
+
+.profile-meta {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 12px;
+  flex-wrap: wrap;
+
+  .meta-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 14px;
+    color: #666;
+
+    svg {
+      color: #999;
+      flex-shrink: 0;
+    }
+  }
+}
+
+.profile-tags {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+
+  .tag {
+    padding: 4px 12px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    border-radius: 16px;
+    font-size: 12px;
+    font-weight: 500;
+  }
+}
+
+/* 信息网格 */
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 20px;
+  margin-bottom: 20px;
+
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+  }
+}
+
+.info-section {
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 2px solid #f0f0f0;
+
+  svg {
+    color: #165DFF;
+    flex-shrink: 0;
+  }
+}
+
+.info-items {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.info-item {
+  label {
+    display: block;
+    font-size: 12px;
+    color: #888;
+    margin-bottom: 4px;
+    font-weight: 500;
+  }
+
+  .info-value {
+    font-size: 14px;
+    color: #333;
+    font-weight: 500;
+
+    &.phone {
+      font-family: 'Courier New', monospace;
+      letter-spacing: 0.5px;
+    }
+
+    &.budget {
+      color: #00B42A;
+      font-weight: 600;
+    }
+  }
+
+  .badge {
+    display: inline-block;
+    padding: 4px 10px;
+    background: #eef2ff;
+    color: #4f46e5;
+    border-radius: 12px;
+    font-size: 12px;
+    font-weight: 600;
+
+    &.badge-enterprise {
+      background: #fff7ed;
+      color: #f97316;
+    }
+  }
+}
+
+/* 客户画像区块 */
+.profile-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.section-title-large {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 20px;
+  padding-bottom: 12px;
+  border-bottom: 2px solid #f0f0f0;
+
+  svg {
+    color: #165DFF;
+    flex-shrink: 0;
+  }
+}
+
+.profile-details {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.profile-item {
+  label {
+    display: block;
+    font-size: 13px;
+    color: #666;
+    margin-bottom: 8px;
+    font-weight: 500;
+  }
+
+  .tag-list {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+
+    .tag {
+      padding: 6px 14px;
+      background: #f0f7ff;
+      color: #165DFF;
+      border-radius: 16px;
+      font-size: 13px;
+      font-weight: 500;
+      border: 1px solid #d4e6ff;
+
+      &.tag-style {
+        background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
+        color: #d63031;
+        border: none;
+      }
+    }
+  }
+}
+
+/* 群聊列表区块 */
+.groups-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+  .section-subtitle {
+    font-size: 13px;
+    color: #999;
+    margin: -8px 0 16px;
+  }
+
+  .groups-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+    gap: 12px;
+
+    .group-card {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 14px;
+      background: linear-gradient(135deg, #f8f9ff 0%, #fff 100%);
+      border: 1px solid #e8e9ff;
+      border-radius: 12px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(99, 102, 241, 0.1);
+        border-color: #6366f1;
+      }
+
+      .group-icon {
+        width: 40px;
+        height: 40px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+        border-radius: 10px;
+        flex-shrink: 0;
+
+        svg {
+          stroke: white;
+          width: 20px;
+          height: 20px;
+        }
+      }
+
+      .group-info {
+        flex: 1;
+        min-width: 0;
+
+        .group-name {
+          margin: 0 0 4px;
+          font-size: 14px;
+          font-weight: 500;
+          color: #1a1a1a;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .group-project {
+          display: flex;
+          align-items: center;
+          gap: 4px;
+          font-size: 12px;
+          color: #6366f1;
+          margin: 0;
+
+          svg {
+            width: 14px;
+            height: 14px;
+            stroke: currentColor;
+          }
+        }
+
+        .group-no-project {
+          font-size: 12px;
+          color: #999;
+          margin: 0;
+        }
+      }
+    }
+  }
+}
+
+/* 相关项目区块 */
+.projects-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.projects-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.project-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px;
+  background: linear-gradient(135deg, #fafcff 0%, #fff 100%);
+  border-radius: 10px;
+  border: 1px solid #e8eeff;
+  transition: all 0.2s;
+
+  &:hover {
+    border-color: #3b82f6;
+    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.08);
+    transform: translateX(4px);
+  }
+
+  .project-content {
+    flex: 1;
+    min-width: 0;
+
+    .project-header {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      margin-bottom: 10px;
+
+      h4 {
+        margin: 0;
+        font-size: 15px;
+        font-weight: 600;
+        color: #333;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      .project-status {
+        padding: 4px 10px;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 600;
+        flex-shrink: 0;
+
+        &.status-待分配 {
+          background: linear-gradient(135deg, #e0f2fe 0%, #dbeafe 100%);
+          color: #0284c7;
+        }
+
+        &.status-进行中 {
+          background: linear-gradient(135deg, #fef3c7 0%, #fed7aa 100%);
+          color: #ea580c;
+        }
+
+        &.status-已完成 {
+          background: linear-gradient(135deg, #dcfce7 0%, #d1fae5 100%);
+          color: #16a34a;
+        }
+
+        &.status-已暂停 {
+          background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
+          color: #6b7280;
+        }
+      }
+    }
+
+    .project-details {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      font-size: 13px;
+
+      .project-stage {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        color: #3b82f6;
+
+        svg {
+          width: 14px;
+          height: 14px;
+          stroke: currentColor;
+        }
+      }
+
+      .project-time {
+        color: #888;
+      }
+    }
+  }
+
+  .project-arrow {
+    width: 32px;
+    height: 32px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #f8fafc;
+    border: 1px solid #e2e8f0;
+    border-radius: 8px;
+    flex-shrink: 0;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #3b82f6;
+      border-color: #3b82f6;
+
+      svg {
+        stroke: white;
+      }
+    }
+
+    svg {
+      width: 16px;
+      height: 16px;
+      stroke: #94a3b8;
+      transition: stroke 0.2s ease;
+    }
+  }
+}
+
+/* 跟进记录时间线 */
+.timeline-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+
+  .timeline {
+    margin-top: 16px;
+    position: relative;
+
+    .timeline-item {
+      display: flex;
+      gap: 16px;
+      position: relative;
+
+      &:not(:last-child) {
+        margin-bottom: 20px;
+      }
+
+      .timeline-marker {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        flex-shrink: 0;
+
+        .timeline-dot {
+          width: 12px;
+          height: 12px;
+          border-radius: 50%;
+          background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+          border: 3px solid #ede9fe;
+          flex-shrink: 0;
+          z-index: 2;
+        }
+
+        .timeline-line {
+          width: 2px;
+          flex: 1;
+          background: linear-gradient(180deg, #e9d5ff 0%, #f3f4f6 100%);
+          margin-top: 4px;
+          min-height: 20px;
+        }
+      }
+
+      .timeline-content {
+        flex: 1;
+        min-width: 0;
+        padding: 12px 16px;
+        background: linear-gradient(135deg, #fafafa 0%, #fff 100%);
+        border: 1px solid #f0f0f0;
+        border-radius: 10px;
+        transition: all 0.2s ease;
+
+        &:hover {
+          border-color: #e9d5ff;
+          background: linear-gradient(135deg, #faf5ff 0%, #fff 100%);
+        }
+
+        .timeline-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 8px;
+
+          .timeline-operator {
+            font-weight: 500;
+            font-size: 14px;
+            color: #1a1a1a;
+          }
+
+          .timeline-time {
+            font-size: 12px;
+            color: #999;
+          }
+        }
+
+        .timeline-body {
+          .timeline-type {
+            display: inline-block;
+            padding: 2px 8px;
+            background: linear-gradient(135deg, #ede9fe 0%, #e9d5ff 100%);
+            color: #7c3aed;
+            font-size: 12px;
+            border-radius: 8px;
+            margin-bottom: 6px;
+            font-weight: 500;
+          }
+
+          .timeline-text {
+            margin: 8px 0 0;
+            font-size: 13px;
+            color: #666;
+            line-height: 1.6;
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 空状态 */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60px 20px;
+  text-align: center;
+
+  svg {
+    color: #ccc;
+    margin-bottom: 16px;
+  }
+
+  p {
+    margin: 0;
+    font-size: 14px;
+    color: #999;
+  }
+}
+
+/* 动画 */
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes slideUp {
+  from {
+    transform: translateY(40px) scale(0.95);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0) scale(1);
+    opacity: 1;
+  }
+}
+
+// ========================================
+// 客户管理移动端适配
+// ========================================
+
+@media (max-width: 768px) {
+  // 客户画像面板移动端全屏显示
+  .customer-panel-overlay {
+    padding: 0;
+  }
+
+  .customer-panel {
+    width: 100vw;
+    max-width: 100vw;
+    height: 100vh;
+    max-height: 100vh;
+    border-radius: 0;
+    margin: 0;
+
+    .panel-header {
+      padding: 16px;
+      position: sticky;
+      top: 0;
+      z-index: 10;
+
+      h2 {
+        font-size: 18px;
+      }
+
+      .close-btn {
+        width: 36px;
+        height: 36px;
+      }
+    }
+
+    .panel-body {
+      padding: 16px;
+      height: calc(100vh - 60px);
+      overflow-y: auto;
+    }
+
+    // 个人信息卡片
+    .profile-card {
+      padding: 16px;
+
+      .profile-avatar {
+        width: 60px;
+        height: 60px;
+      }
+
+      .profile-name {
+        font-size: 18px;
+      }
+
+      .profile-meta {
+        flex-direction: column;
+        gap: 8px;
+
+        .meta-item {
+          font-size: 12px;
+        }
+      }
+
+      .profile-tags {
+        flex-wrap: wrap;
+        gap: 6px;
+
+        .tag {
+          font-size: 11px;
+          padding: 3px 8px;
+        }
+      }
+    }
+
+    // 信息网格
+    .info-grid {
+      grid-template-columns: 1fr;
+      gap: 16px;
+    }
+
+    .info-section {
+      padding: 16px;
+
+      .section-title {
+        font-size: 14px;
+      }
+
+      .info-item {
+        padding: 10px 0;
+
+        .label {
+          font-size: 12px;
+        }
+
+        .value {
+          font-size: 13px;
+        }
+      }
+    }
+
+    // 客户画像详情
+    .profile-section {
+      padding: 16px;
+
+      .section-title-large {
+        font-size: 16px;
+      }
+
+      .profile-item {
+        font-size: 13px;
+
+        strong {
+          font-size: 12px;
+        }
+      }
+    }
+
+    // 群聊部分
+    .groups-section {
+      padding: 16px;
+
+      .section-title-large {
+        font-size: 16px;
+      }
+
+      .groups-grid {
+        grid-template-columns: 1fr;
+        gap: 12px;
+      }
+
+      .group-card {
+        padding: 12px;
+
+        .group-name {
+          font-size: 14px;
+        }
+
+        .group-project {
+          font-size: 12px;
+        }
+      }
+    }
+
+    // 项目部分
+    .projects-section {
+      padding: 16px;
+
+      .section-title-large {
+        font-size: 16px;
+      }
+
+      .projects-list {
+        gap: 12px;
+      }
+
+      .project-card {
+        padding: 12px;
+
+        .project-title {
+          font-size: 14px;
+        }
+
+        .project-status {
+          font-size: 11px;
+          padding: 3px 8px;
+        }
+
+        .project-meta {
+          flex-direction: column;
+          gap: 6px;
+
+          .meta-item {
+            font-size: 12px;
+          }
+        }
+      }
+    }
+
+    // 跟进记录时间线
+    .timeline-section {
+      padding: 16px;
+
+      .section-title-large {
+        font-size: 16px;
+      }
+
+      .timeline {
+        padding-left: 24px;
+      }
+
+      .timeline-item {
+        padding-left: 24px;
+
+        .timeline-dot {
+          width: 10px;
+          height: 10px;
+          left: -5px;
+        }
+
+        .timeline-content {
+          padding: 12px;
+
+          .timeline-operator {
+            font-size: 13px;
+          }
+
+          .timeline-time {
+            font-size: 11px;
+          }
+
+          .timeline-type {
+            font-size: 12px;
+            padding: 3px 8px;
+          }
+
+          .timeline-text {
+            font-size: 12px;
+          }
+        }
+      }
+    }
+  }
+}

+ 280 - 2
src/app/pages/admin/customers/customers.ts

@@ -4,12 +4,11 @@ import { FormsModule } from '@angular/forms';
 import { CustomerService } from '../services/customer.service';
 import { FmodeObject } from 'fmode-ng/core';
 import { FmodeParse } from 'fmode-ng/parse';
-import { CustomerProfileComponent } from '../../../../modules/project/pages/contact/contact.component';
 
 @Component({
   selector: 'app-admin-customers',
   standalone: true,
-  imports: [CommonModule, FormsModule, CustomerProfileComponent],
+  imports: [CommonModule, FormsModule],
   templateUrl: './customers.html',
   styleUrl: './customers.scss'
 })
@@ -57,6 +56,22 @@ export class Customers implements OnInit {
   openCustomerDetail(customer: FmodeObject) {
     this.selectedCustomer = customer;
     this.showCustomerPanel = true;
+    
+    // 调试:打印客户对象的所有字段
+    console.log('=== 客户详情 ===');
+    console.log('客户ID:', customer.id);
+    console.log('姓名 (name):', customer.get('name'));
+    console.log('手机号 (mobile):', customer.get('mobile'));
+    console.log('来源 (source):', customer.get('source'));
+    console.log('创建时间 (createdAt):', customer.get('createdAt'));
+    console.log('企微ID (external_userid):', customer.get('external_userid'));
+    console.log('data字段:', customer.get('data'));
+    console.log('=== 结束 ===');
+    
+    // 加载客户相关数据
+    this.loadCustomerProjects(customer);
+    this.loadCustomerGroups(customer);
+    this.loadCustomerFollowUps(customer);
   }
 
   async closeCustomerPanel(refresh?: boolean | Event) {
@@ -113,4 +128,267 @@ export class Customers implements OnInit {
     a.click();
     URL.revokeObjectURL(url);
   }
+
+  // ===== 客户数据获取方法 =====
+
+  getCustomerAvatar(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    return data.avatar || data.external_contact?.avatar || '/assets/images/default-avatar.svg';
+  }
+
+  getCustomerName(customer: FmodeObject): string {
+    return customer.get('name') || customer.get('data')?.name || '未知客户';
+  }
+
+  getCustomerMobile(customer: FmodeObject): string {
+    // 优先从直接字段获取,然后从data中获取
+    const mobile = customer.get('mobile');
+    if (mobile) return mobile;
+    
+    const data = customer.get('data') || {};
+    return data.mobile || data.external_contact?.mobile || '-';
+  }
+
+  getCustomerCreatedAt(customer: FmodeObject): string {
+    const createdAt = customer.get('createdAt');
+    if (createdAt instanceof Date) {
+      const year = createdAt.getFullYear();
+      const month = String(createdAt.getMonth() + 1).padStart(2, '0');
+      const day = String(createdAt.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    }
+    return '-';
+  }
+
+  getCustomerSource(customer: FmodeObject): string {
+    // 优先从直接字段获取,然后从data中获取
+    const source = customer.get('source');
+    if (source) return source;
+    
+    const data = customer.get('data') || {};
+    return data.source || '-';
+  }
+
+  getCustomerTags(customer: FmodeObject): string[] {
+    const data = customer.get('data') || {};
+    return data.tags || data.external_contact?.tags || [];
+  }
+
+  getCustomerType(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    const type = data.external_contact?.type;
+    return type === 2 ? '企业成员' : '外部联系人';
+  }
+
+  getCustomerWechat(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    return data.wechat || data.external_contact?.wechat || '';
+  }
+
+  // 客户画像数据
+  hasCustomerProfile(customer: FmodeObject): boolean {
+    const data = customer.get('data') || {};
+    const profile = data.profile || data.preferences || {};
+    return !!(
+      profile.style?.length ||
+      profile.budget ||
+      profile.colorAtmosphere ||
+      profile.demandType
+    );
+  }
+
+  getCustomerStyles(customer: FmodeObject): string[] {
+    const data = customer.get('data') || {};
+    const profile = data.profile || data.preferences || {};
+    return profile.style || [];
+  }
+
+  getCustomerBudget(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    const profile = data.profile || data.preferences || {};
+    const budget = profile.budget;
+    if (!budget || !budget.min || !budget.max) {
+      return '';
+    }
+    return `¥${budget.min.toLocaleString()} - ¥${budget.max.toLocaleString()}`;
+  }
+
+  getCustomerColor(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    const profile = data.profile || data.preferences || {};
+    return profile.colorAtmosphere || '';
+  }
+
+  getCustomerDemand(customer: FmodeObject): string {
+    const data = customer.get('data') || {};
+    const profile = data.profile || data.preferences || {};
+    return profile.demandType || '';
+  }
+
+  // 相关项目
+  customerProjects = signal<FmodeObject[]>([]);
+  customerGroups = signal<FmodeObject[]>([]);
+  customerFollowUps = signal<FmodeObject[]>([]);
+
+  async loadCustomerProjects(customer: FmodeObject) {
+    try {
+      const Parse = FmodeParse.with('nova');
+      const query = new Parse.Query('Project');
+      query.equalTo('contact', customer);
+      query.notEqualTo('isDeleted', true);
+      query.descending('createdAt');
+      query.limit(10);
+      const projects = await query.find();
+      this.customerProjects.set(projects);
+    } catch (error) {
+      console.error('加载客户项目失败:', error);
+      this.customerProjects.set([]);
+    }
+  }
+
+  async loadCustomerGroups(customer: FmodeObject) {
+    try {
+      const Parse = FmodeParse.with('nova');
+      const externalUserId = customer.get('external_userid');
+      if (!externalUserId) {
+        this.customerGroups.set([]);
+        return;
+      }
+
+      const query = new Parse.Query('GroupChatContact');
+      query.equalTo('userid', externalUserId);
+      query.include('groupChat');
+      query.include('groupChat.project');
+      query.descending('createdAt');
+      query.limit(20);
+      
+      const contacts = await query.find();
+      this.customerGroups.set(contacts);
+    } catch (error) {
+      console.error('加载客户群聊失败:', error);
+      this.customerGroups.set([]);
+    }
+  }
+
+  async loadCustomerFollowUps(customer: FmodeObject) {
+    try {
+      const Parse = FmodeParse.with('nova');
+      
+      const query = new Parse.Query('ActivityLog');
+      query.equalTo('entityId', customer.id);
+      query.equalTo('module', 'customer');
+      query.include('actor');
+      query.descending('createdAt');
+      query.limit(20);
+      
+      const records = await query.find();
+      this.customerFollowUps.set(records);
+    } catch (error) {
+      console.error('加载客户跟进记录失败:', error);
+      this.customerFollowUps.set([]);
+    }
+  }
+
+  getProjectTitle(project: FmodeObject): string {
+    return project.get('title') || '未命名项目';
+  }
+
+  getProjectStatus(project: FmodeObject): string {
+    return project.get('status') || '待分配';
+  }
+
+  getProjectStage(project: FmodeObject): string {
+    return project.get('currentStage') || '订单分配';
+  }
+
+  getProjectDate(project: FmodeObject): string {
+    const updatedAt = project.get('updatedAt');
+    if (updatedAt instanceof Date) {
+      return updatedAt.toLocaleDateString('zh-CN');
+    }
+    return '-';
+  }
+
+  navigateToProject(project: FmodeObject): void {
+    const projectId = project.id;
+    if (projectId) {
+      // 这里可以跳转到项目详情页
+      console.log('导航到项目:', projectId);
+    }
+  }
+
+  // 群聊相关方法
+  getGroupName(groupContact: FmodeObject): string {
+    const groupChat = groupContact.get('groupChat');
+    if (groupChat) {
+      return groupChat.get('name') || '未命名群聊';
+    }
+    return '未命名群聊';
+  }
+
+  getGroupProject(groupContact: FmodeObject): string {
+    const groupChat = groupContact.get('groupChat');
+    if (groupChat) {
+      const project = groupChat.get('project');
+      if (project) {
+        return project.get('title') || '';
+      }
+    }
+    return '';
+  }
+
+  navigateToGroupChat(groupContact: FmodeObject): void {
+    const groupChat = groupContact.get('groupChat');
+    if (groupChat) {
+      const chatId = groupChat.get('chat_id');
+      console.log('导航到群聊:', chatId);
+      // 这里可以实现跳转到企微群聊
+    }
+  }
+
+  // 跟进记录相关方法
+  getFollowUpOperator(record: FmodeObject): string {
+    const actor = record.get('actor');
+    if (actor) {
+      const data = actor.get('data') || {};
+      return data.name || actor.get('name') || '系统';
+    }
+    return '系统';
+  }
+
+  getFollowUpTime(record: FmodeObject): string {
+    const createdAt = record.get('createdAt');
+    if (createdAt instanceof Date) {
+      const now = new Date();
+      const diff = now.getTime() - createdAt.getTime();
+      const minutes = Math.floor(diff / 60000);
+      const hours = Math.floor(diff / 3600000);
+      const days = Math.floor(diff / 86400000);
+      
+      if (minutes < 1) return '刚刚';
+      if (minutes < 60) return `${minutes}分钟前`;
+      if (hours < 24) return `${hours}小时前`;
+      if (days < 7) return `${days}天前`;
+      return createdAt.toLocaleDateString('zh-CN');
+    }
+    return '-';
+  }
+
+  getFollowUpType(record: FmodeObject): string {
+    const actionType = record.get('actionType') || '';
+    const typeMap: { [key: string]: string } = {
+      'create': '创建客户',
+      'update': '更新信息',
+      'contact': '客户沟通',
+      'follow_up': '跟进记录',
+      'assign': '分配客服',
+      'tag': '添加标签',
+      'note': '备注'
+    };
+    return typeMap[actionType] || actionType || '操作';
+  }
+
+  getFollowUpContent(record: FmodeObject): string {
+    return record.get('description') || record.get('metadata')?.content || '暂无描述';
+  }
 }

+ 95 - 58
src/app/pages/admin/dashboard/dashboard.html

@@ -141,72 +141,109 @@
     </div>
   </div>
 
-  <!-- 最近活动 -->
-  <div class="recent-activities">
-    <div class="section-header">
-      <h3>最近活动</h3>
-      <a href="/admin/logs" class="view-all-link">查看全部</a>
-    </div>
-    
-    <div class="activities-list">
-      <div class="activity-item">
-        <div class="activity-icon">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+  <!-- 最近活动 - 重新设计 -->
+  <div class="recent-activities-enhanced">
+    <div class="section-header-enhanced">
+      <div class="header-left">
+        <div class="header-icon">
+          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
           </svg>
         </div>
-        <div class="activity-content">
-          <div class="activity-text">
-            <span class="activity-user">系统</span> 创建了新项目 <span class="activity-project">现代简约风格三居室设计</span>
-          </div>
-          <div class="activity-time">今天 10:30</div>
+        <div class="header-text">
+          <h3>最近活动</h3>
+          <p class="header-subtitle">实时跟踪系统动态 • 共 {{ recentActivities().length }} 条</p>
         </div>
       </div>
-      
-      <div class="activity-item">
-        <div class="activity-icon">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
-            <polyline points="22 4 12 14.01 9 11.01"></polyline>
-          </svg>
-        </div>
-        <div class="activity-content">
-          <div class="activity-text">
-            <span class="activity-user">张设计师</span> 完成了任务 <span class="activity-task">设计初稿</span>
-          </div>
-          <div class="activity-time">今天 09:15</div>
-        </div>
+      <button class="view-all-btn" (click)="viewAllActivities()" *ngIf="!showAllActivities()">
+        <span>查看全部</span>
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="5" y1="12" x2="19" y2="12"></line>
+          <polyline points="12 5 19 12 12 19"></polyline>
+        </svg>
+      </button>
+    </div>
+    
+    <!-- 加载状态 -->
+    @if (loadingActivities()) {
+      <div class="loading-activities">
+        <div class="spinner"></div>
+        <p>加载活动日志...</p>
       </div>
-      
-      <div class="activity-item">
-        <div class="activity-icon">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
-            <circle cx="9" cy="7" r="4"></circle>
-          </svg>
-        </div>
-        <div class="activity-content">
-          <div class="activity-text">
-            <span class="activity-user">客服小李</span> 新增了客户 <span class="activity-customer">王先生</span>
+    }
+    
+    <!-- 活动列表 -->
+    @if (!loadingActivities() && recentActivities().length > 0) {
+      <div class="timeline-container">
+        @for (activity of displayedActivities(); track activity.id; let isLast = $last) {
+          <div class="timeline-item" [ngClass]="getActivityClass(activity)">
+            <div class="timeline-marker">
+              <div class="marker-dot"></div>
+              @if (!isLast) {
+                <div class="marker-line"></div>
+              }
+            </div>
+            <div class="timeline-content">
+              <div class="activity-card">
+                <div class="card-header">
+                  <div class="activity-type-badge" [ngClass]="getActivityClass(activity)">
+                    <span [innerHTML]="activity.icon"></span>
+                    <span>{{ getActionTypeLabel(activity.actionType) }}</span>
+                  </div>
+                  <div class="activity-time">
+                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <circle cx="12" cy="12" r="10"></circle>
+                      <polyline points="12 6 12 12 16 14"></polyline>
+                    </svg>
+                    <span>{{ activity.formattedTime }}</span>
+                  </div>
+                </div>
+                <div class="card-body">
+                  <div class="activity-description">
+                    <span class="actor-name">{{ activity.actorName }}</span> {{ activity.description }}
+                    <div [ngClass]="getEntityTagClass(activity.module)">
+                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                        @if (activity.module === 'project') {
+                          <path d="M3 3h7v7H3z"></path>
+                          <path d="M14 3h7v7h-7z"></path>
+                          <path d="M14 14h7v7h-7z"></path>
+                          <path d="M3 14h7v7H3z"></path>
+                        }
+                        @if (activity.module === 'customer') {
+                          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+                          <circle cx="12" cy="7" r="4"></circle>
+                        }
+                        @if (activity.module === 'task' || activity.module === 'design' || activity.module === 'urgent_task') {
+                          <polyline points="9 11 12 14 22 4"></polyline>
+                          <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
+                        }
+                        @if (activity.module === 'finance') {
+                          <line x1="12" y1="1" x2="12" y2="23"></line>
+                          <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+                        }
+                      </svg>
+                      <span>{{ activity.entityName }}</span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
-          <div class="activity-time">昨天 16:45</div>
-        </div>
+        }
       </div>
-      
-      <div class="activity-item">
-        <div class="activity-icon">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <polyline points="20 6 9 17 4 12"></polyline>
-          </svg>
-        </div>
-        <div class="activity-content">
-          <div class="activity-text">
-            <span class="activity-user">系统</span> 完成了项目 <span class="activity-project">北欧风格两居室设计</span>
-          </div>
-          <div class="activity-time">昨天 15:30</div>
-        </div>
+    }
+    
+    <!-- 空状态 -->
+    @if (!loadingActivities() && recentActivities().length === 0) {
+      <div class="empty-activities">
+        <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <circle cx="12" cy="12" r="10"></circle>
+          <line x1="12" y1="8" x2="12" y2="12"></line>
+          <line x1="12" y1="16" x2="12.01" y2="16"></line>
+        </svg>
+        <p>暂无活动记录</p>
       </div>
-    </div>
+    }
   </div>
 
   <!-- 详情抽屉 -->

+ 885 - 1
src/app/pages/admin/dashboard/dashboard.scss

@@ -530,7 +530,333 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
   to { transform: translateX(0); opacity: 1; }
 }
 
-// 最近活动区域
+// 增强版最近活动区域
+.recent-activities-enhanced {
+  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+  border-radius: 20px;
+  padding: 0;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+  overflow: hidden;
+  
+  &:hover {
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+    transform: translateY(-2px);
+  }
+}
+
+// 增强版区域头部
+.section-header-enhanced {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 28px 32px;
+  background: linear-gradient(135deg, #165DFF 0%, #7c3aed 100%);
+  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+  
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+  
+  .header-icon {
+    width: 48px;
+    height: 48px;
+    background: rgba(255, 255, 255, 0.2);
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    backdrop-filter: blur(10px);
+    
+    svg {
+      stroke-width: 2.5;
+      color: white;
+    }
+  }
+  
+  .header-text {
+    h3 {
+      font-size: 22px;
+      font-weight: 700;
+      color: white;
+      margin: 0 0 4px 0;
+      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    }
+    
+    .header-subtitle {
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.9);
+      margin: 0;
+      font-weight: 400;
+    }
+  }
+  
+  .view-all-btn {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 10px 20px;
+    background: rgba(255, 255, 255, 0.25);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    border-radius: 10px;
+    color: white;
+    text-decoration: none;
+    font-size: 14px;
+    font-weight: 600;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    backdrop-filter: blur(10px);
+    
+    svg {
+      stroke-width: 2.5;
+      transition: transform 0.3s ease;
+    }
+    
+    &:hover {
+      background: rgba(255, 255, 255, 0.35);
+      border-color: rgba(255, 255, 255, 0.5);
+      transform: translateX(4px);
+      
+      svg {
+        transform: translateX(4px);
+      }
+    }
+  }
+}
+
+// 时间线容器
+.timeline-container {
+  padding: 32px;
+  position: relative;
+}
+
+// 时间线项目
+.timeline-item {
+  display: flex;
+  gap: 20px;
+  position: relative;
+  
+  &:not(:last-child) {
+    margin-bottom: 24px;
+  }
+}
+
+// 时间线标记
+.timeline-marker {
+  flex-shrink: 0;
+  width: 40px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+  
+  .marker-dot {
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    border: 3px solid white;
+    box-shadow: 0 0 0 2px currentColor, 0 4px 8px rgba(0, 0, 0, 0.15);
+    background: currentColor;
+    z-index: 2;
+    transition: all 0.3s ease;
+  }
+  
+  .marker-line {
+    width: 2px;
+    flex: 1;
+    background: linear-gradient(180deg, currentColor 0%, rgba(229, 231, 235, 0.5) 100%);
+    margin-top: 4px;
+    min-height: 60px;
+  }
+}
+
+// 不同类型的时间线项目颜色
+.timeline-item.create {
+  .timeline-marker {
+    color: #165DFF;
+  }
+}
+
+.timeline-item.complete {
+  .timeline-marker {
+    color: #00B42A;
+  }
+}
+
+.timeline-item.customer {
+  .timeline-marker {
+    color: #7c3aed;
+  }
+}
+
+.timeline-item.finish {
+  .timeline-marker {
+    color: #FF7D00;
+  }
+}
+
+// 时间线内容
+.timeline-content {
+  flex: 1;
+  padding-bottom: 8px;
+}
+
+// 活动卡片
+.activity-card {
+  background: white;
+  border-radius: 14px;
+  border: 1px solid #e5e7eb;
+  overflow: hidden;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  
+  &:hover {
+    transform: translateX(4px) translateY(-2px);
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+    border-color: #d1d5db;
+    
+    .marker-dot {
+      transform: scale(1.3);
+      box-shadow: 0 0 0 2px currentColor, 0 6px 16px rgba(0, 0, 0, 0.2);
+    }
+  }
+  
+  .card-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 16px 20px;
+    background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
+    border-bottom: 1px solid #e5e7eb;
+  }
+  
+  .card-body {
+    padding: 18px 20px;
+  }
+}
+
+// 活动类型徽章
+.activity-type-badge {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  padding: 6px 14px;
+  border-radius: 8px;
+  font-size: 13px;
+  font-weight: 600;
+  
+  svg {
+    stroke-width: 2.5;
+    flex-shrink: 0;
+  }
+  
+  &.create {
+    background: linear-gradient(135deg, #dbeafe, #bfdbfe);
+    color: #1e40af;
+  }
+  
+  &.complete {
+    background: linear-gradient(135deg, #d1fae5, #a7f3d0);
+    color: #065f46;
+  }
+  
+  &.customer {
+    background: linear-gradient(135deg, #f3e8ff, #e9d5ff);
+    color: #6b21a8;
+  }
+  
+  &.finish {
+    background: linear-gradient(135deg, #fed7aa, #fdba74);
+    color: #9a3412;
+  }
+}
+
+// 活动时间
+.activity-time {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 12px;
+  color: #6b7280;
+  font-weight: 500;
+  
+  svg {
+    stroke-width: 2;
+    flex-shrink: 0;
+  }
+}
+
+// 活动描述
+.activity-description {
+  font-size: 15px;
+  color: #374151;
+  line-height: 1.6;
+  
+  .actor-name {
+    font-weight: 700;
+    color: #1f2937;
+    background: linear-gradient(135deg, #165DFF, #7c3aed);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+  }
+}
+
+// 项目/任务/客户标签
+.project-tag,
+.task-tag,
+.customer-tag {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 12px;
+  padding: 10px 16px;
+  border-radius: 10px;
+  font-weight: 600;
+  font-size: 14px;
+  transition: all 0.3s ease;
+  
+  svg {
+    stroke-width: 2.5;
+    flex-shrink: 0;
+  }
+}
+
+.project-tag {
+  background: linear-gradient(135deg, #eff6ff, #dbeafe);
+  color: #1e40af;
+  border: 1px solid #bfdbfe;
+  
+  &:hover {
+    background: linear-gradient(135deg, #dbeafe, #bfdbfe);
+    transform: scale(1.02);
+  }
+}
+
+.task-tag {
+  background: linear-gradient(135deg, #f0fdf4, #dcfce7);
+  color: #166534;
+  border: 1px solid #bbf7d0;
+  
+  &:hover {
+    background: linear-gradient(135deg, #dcfce7, #bbf7d0);
+    transform: scale(1.02);
+  }
+}
+
+.customer-tag {
+  background: linear-gradient(135deg, #faf5ff, #f3e8ff);
+  color: #6b21a8;
+  border: 1px solid #e9d5ff;
+  
+  &:hover {
+    background: linear-gradient(135deg, #f3e8ff, #e9d5ff);
+    transform: scale(1.02);
+  }
+}
+
+// 旧版最近活动区域(保留但隐藏)
 .recent-activities {
   background: $background-primary;
   border-radius: $border-radius;
@@ -538,6 +864,7 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
   box-shadow: $shadow-sm;
   border: 1px solid rgba(255, 255, 255, 0.8);
   transition: $transition;
+  display: none; // 隐藏旧版
   
   &:hover {
     box-shadow: $shadow-md;
@@ -795,4 +1122,561 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
       }
     }
   }
+}
+
+// ====== 活动日志加载和空状态样式 ======
+.loading-activities {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 80px 20px;
+  gap: 16px;
+  
+  .spinner {
+    width: 40px;
+    height: 40px;
+    border: 3px solid #f3f3f3;
+    border-top: 3px solid #165DFF;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+  }
+  
+  @keyframes spin {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+  }
+  
+  p {
+    color: #86909C;
+    font-size: 14px;
+    margin: 0;
+  }
+}
+
+.empty-activities {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 80px 20px;
+  gap: 16px;
+  
+  svg {
+    stroke: #C9CDD4;
+    opacity: 0.5;
+  }
+  
+  p {
+    color: #86909C;
+    font-size: 14px;
+    margin: 0;
+  }
+}
+
+// 活动类型特定样式
+.timeline-item {
+  &.create .marker-dot {
+    background: linear-gradient(135deg, #3370FF 0%, #165DFF 100%);
+  }
+  
+  &.update .marker-dot {
+    background: linear-gradient(135deg, #00B8D9 0%, #0084C7 100%);
+  }
+  
+  &.complete .marker-dot {
+    background: linear-gradient(135deg, #36CB83 0%, #00B42A 100%);
+  }
+  
+  &.assign .marker-dot {
+    background: linear-gradient(135deg, #9B51E0 0%, #7C3AED 100%);
+  }
+  
+  &.upload .marker-dot {
+    background: linear-gradient(135deg, #FF9500 0%, #FF7A00 100%);
+  }
+  
+  &.delete .marker-dot {
+    background: linear-gradient(135deg, #F76560 0%, #F53F3F 100%);
+  }
+  
+  &.customer .marker-dot {
+    background: linear-gradient(135deg, #F7BA1E 0%, #FAAD14 100%);
+  }
+  
+  &.finish .marker-dot {
+    background: linear-gradient(135deg, #52C41A 0%, #389E0D 100%);
+  }
+}
+
+// 实体标签样式
+.finance-tag {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
+  border: 1px solid #FFD591;
+  border-radius: 6px;
+  color: #D46B08;
+  font-size: 13px;
+  
+  svg {
+    stroke: #D46B08;
+  }
+}
+
+.case-tag {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  background: linear-gradient(135deg, #F0F5FF 0%, #D6E4FF 100%);
+  border: 1px solid #ADC6FF;
+  border-radius: 6px;
+  color: #1D39C4;
+  font-size: 13px;
+  
+  svg {
+    stroke: #1D39C4;
+  }
+}
+
+.chat-tag {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
+  border: 1px solid #91D5FF;
+  border-radius: 6px;
+  color: #0958D9;
+  font-size: 13px;
+  
+  svg {
+    stroke: #0958D9;
+  }
+}
+
+.default-tag {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  background: linear-gradient(135deg, #F5F5F5 0%, #E8E8E8 100%);
+  border: 1px solid #D9D9D9;
+  border-radius: 6px;
+  color: #595959;
+  font-size: 13px;
+  
+  svg {
+    stroke: #595959;
+  }
+}
+
+// 查看全部按钮样式调整(当它是button时)
+.section-header-enhanced {
+  .view-all-btn {
+    cursor: pointer;
+    background: transparent;
+    border: none;
+    padding: 0;
+    
+    &:hover {
+      transform: translateX(4px);
+    }
+  }
+}
+
+// ========================================
+// 移动端响应式适配
+// ========================================
+
+@media (max-width: 768px) {
+  // 主容器适配
+  .admin-dashboard {
+    padding: 16px 0;
+  }
+
+  // 容器内边距
+  .container {
+    padding: 0 12px !important;
+  }
+
+  // 页面头部适配
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start !important;
+    gap: 16px;
+    padding: 16px 12px !important;
+    
+    h1 {
+      font-size: 24px !important;
+    }
+
+    .header-actions {
+      width: 100%;
+      justify-content: stretch;
+      
+      button {
+        flex: 1;
+        min-width: auto;
+      }
+    }
+  }
+
+  // 统计卡片网格适配
+  .stats-grid,
+  .dashboard-grid {
+    grid-template-columns: 1fr !important;
+    gap: 12px !important;
+  }
+
+  // 统计卡片适配
+  .stat-card {
+    padding: 16px !important;
+    
+    .stat-icon {
+      width: 48px !important;
+      height: 48px !important;
+      
+      svg {
+        width: 24px !important;
+        height: 24px !important;
+      }
+    }
+
+    .stat-content {
+      .stat-value {
+        font-size: 24px !important;
+      }
+
+      .stat-label {
+        font-size: 13px !important;
+      }
+    }
+
+    .stat-change {
+      font-size: 12px !important;
+    }
+  }
+
+  // 图表卡片适配
+  .chart-card {
+    padding: 16px !important;
+    
+    .chart-header {
+      h3 {
+        font-size: 16px !important;
+      }
+    }
+
+    .chart-container {
+      height: 250px !important;
+      max-height: 250px !important;
+    }
+  }
+
+  // 活动时间线适配
+  .recent-activities-enhanced {
+    padding: 16px !important;
+
+    .section-header-enhanced {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 12px;
+
+      .header-left {
+        h2 {
+          font-size: 18px !important;
+        }
+      }
+    }
+
+    .timeline-container {
+      padding-left: 24px;
+    }
+
+    .timeline-item {
+      padding-left: 24px;
+      
+      .timeline-marker {
+        left: -12px;
+        
+        .marker-dot {
+          width: 24px;
+          height: 24px;
+        }
+      }
+
+      .activity-card {
+        padding: 12px;
+
+        .card-header {
+          flex-direction: column;
+          align-items: flex-start;
+          gap: 8px;
+
+          .activity-type-badge {
+            font-size: 11px;
+            padding: 4px 8px;
+          }
+
+          .activity-time {
+            font-size: 11px;
+          }
+        }
+
+        .card-body {
+          .activity-description {
+            font-size: 13px;
+          }
+        }
+      }
+    }
+  }
+
+  // 快速操作按钮适配
+  .quick-actions {
+    grid-template-columns: repeat(2, 1fr) !important;
+    gap: 12px !important;
+
+    .action-card {
+      padding: 16px !important;
+
+      .action-icon {
+        width: 40px;
+        height: 40px;
+
+        svg {
+          width: 20px;
+          height: 20px;
+        }
+      }
+
+      .action-label {
+        font-size: 13px;
+      }
+
+      .action-count {
+        font-size: 11px;
+      }
+    }
+  }
+
+  // 写作按钮适配
+  .write-button {
+    width: 56px;
+    height: 56px;
+    bottom: 20px;
+    right: 20px;
+
+    svg {
+      width: 24px;
+      height: 24px;
+    }
+  }
+
+  // 模态框适配
+  .modal-overlay {
+    padding: 0;
+  }
+
+  .modal-content {
+    border-radius: 16px 16px 0 0;
+    max-height: 90vh;
+    width: 100vw;
+    max-width: 100vw;
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    animation: slideUpFromBottom 0.3s ease;
+
+    .modal-header {
+      padding: 20px 16px 0;
+
+      .modal-title {
+        font-size: 20px;
+      }
+
+      .modal-close {
+        width: 32px;
+        height: 32px;
+      }
+    }
+
+    .modal-body {
+      padding: 20px 16px;
+
+      .form-group {
+        margin-bottom: 16px;
+
+        label {
+          font-size: 14px;
+        }
+
+        input,
+        textarea,
+        select {
+          font-size: 16px !important; // 防止iOS自动缩放
+          padding: 12px;
+        }
+      }
+    }
+
+    .modal-footer {
+      padding: 16px;
+      flex-direction: column;
+      gap: 12px;
+
+      button {
+        width: 100%;
+      }
+    }
+  }
+
+  // 项目卡片适配
+  .project-card {
+    .project-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 12px;
+
+      .project-title {
+        font-size: 16px;
+      }
+
+      .project-status {
+        font-size: 12px;
+        padding: 4px 10px;
+      }
+    }
+
+    .project-info {
+      flex-direction: column;
+      gap: 8px;
+
+      .info-item {
+        font-size: 13px;
+      }
+    }
+  }
+
+  // 表格适配(改为卡片式)
+  table {
+    display: block;
+
+    thead {
+      display: none;
+    }
+
+    tbody {
+      display: block;
+    }
+
+    tr {
+      display: flex;
+      flex-direction: column;
+      border: 1px solid $border-color;
+      border-radius: 12px;
+      padding: 16px;
+      margin-bottom: 12px;
+      background: white;
+    }
+
+    td {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border: none;
+
+      &::before {
+        content: attr(data-label);
+        font-weight: 600;
+        color: $text-secondary;
+        flex-shrink: 0;
+        margin-right: 12px;
+      }
+
+      &:not(:last-child) {
+        border-bottom: 1px solid #f5f5f5;
+      }
+    }
+  }
+
+  // 标签样式适配
+  .project-tag,
+  .task-tag,
+  .customer-tag,
+  .finance-tag,
+  .case-tag,
+  .chat-tag,
+  .default-tag {
+    font-size: 11px !important;
+    padding: 3px 8px !important;
+  }
+
+  // 空状态适配
+  .empty-activities,
+  .empty-state {
+    padding: 40px 20px;
+
+    svg {
+      width: 80px;
+      height: 80px;
+    }
+
+    p {
+      font-size: 14px;
+    }
+  }
+
+  // Loading状态适配
+  .loading-activities {
+    padding: 40px 20px;
+
+    .spinner {
+      width: 40px;
+      height: 40px;
+    }
+
+    p {
+      font-size: 14px;
+    }
+  }
+}
+
+// 小屏手机适配 (< 480px)
+@media (max-width: 480px) {
+  .page-header h1 {
+    font-size: 20px !important;
+  }
+
+  .stats-grid .stat-card {
+    .stat-value {
+      font-size: 20px !important;
+    }
+  }
+
+  .quick-actions {
+    grid-template-columns: 1fr !important;
+  }
+
+  .modal-content {
+    .modal-header .modal-title {
+      font-size: 18px;
+    }
+  }
+}
+
+@keyframes slideUpFromBottom {
+  from {
+    transform: translateY(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
 }

+ 93 - 1
src/app/pages/admin/dashboard/dashboard.ts

@@ -5,6 +5,7 @@ import { signal, Component, OnInit, AfterViewInit, OnDestroy, computed } from '@
 import { AdminDashboardService } from './dashboard.service';
 import { AdminDataService } from '../services/admin-data.service';
 import { ProfileService } from '../../../services/profile.service';
+import { ActivityLogService } from '../../../services/activity-log.service';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
 import * as echarts from 'echarts';
 
@@ -129,10 +130,20 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
   private currentUser: any = null;
   private company: any = null;
 
+  // 活动日志
+  recentActivities = signal<any[]>([]);
+  displayedActivities = computed(() => {
+    const limit = this.showAllActivities() ? 50 : 10;
+    return this.recentActivities().slice(0, limit);
+  });
+  showAllActivities = signal(false);
+  loadingActivities = signal(false);
+
   constructor(
     private dashboardService: AdminDashboardService,
     private adminData: AdminDataService,
-    private profileService: ProfileService
+    private profileService: ProfileService,
+    private activityLogService: ActivityLogService
   ) {}
 
   async ngOnInit(): Promise<void> {
@@ -192,6 +203,9 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
       // 加载收入统计数据
       await this.loadRevenueStats();
 
+      // 加载活动日志
+      await this.loadRecentActivities();
+
       console.log('✅ 管理员仪表板数据加载完成');
     } catch (error) {
       console.error('❌ 管理员仪表板数据加载失败:', error);
@@ -270,6 +284,28 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
     }
   }
 
+  // 加载最近活动日志
+  private async loadRecentActivities(forceLimit?: number): Promise<void> {
+    try {
+      this.loadingActivities.set(true);
+      const limit = forceLimit || (this.showAllActivities() ? 50 : 10);
+      const activities = await this.activityLogService.getRecentActivities(limit);
+      this.recentActivities.set(activities);
+      console.log(`✅ 活动日志加载成功: ${activities.length}条`);
+    } catch (error) {
+      console.error('❌ 活动日志加载失败:', error);
+      this.recentActivities.set([]);
+    } finally {
+      this.loadingActivities.set(false);
+    }
+  }
+
+  // 查看全部活动
+  async viewAllActivities(): Promise<void> {
+    this.showAllActivities.set(true);
+    await this.loadRecentActivities(50);
+  }
+
   // 降级到模拟数据
   private loadMockData(): void {
     console.warn('⚠️ 使用模拟数据');
@@ -782,4 +818,60 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
     a.click();
     URL.revokeObjectURL(url);
   }
+
+  // ====== 活动日志相关方法 ======
+  
+  /**
+   * 获取活动的CSS类名
+   */
+  getActivityClass(activity: any): string {
+    const actionMap: Record<string, string> = {
+      'create': 'create',
+      'update': 'update',
+      'delete': 'delete',
+      'complete': 'complete',
+      'assign': 'assign',
+      'upload': 'upload',
+      'approve': 'approve'
+    };
+    return actionMap[activity.actionType] || 'default';
+  }
+
+  /**
+   * 获取操作类型的中文标签
+   */
+  getActionTypeLabel(actionType: string): string {
+    const labelMap: Record<string, string> = {
+      'create': '创建',
+      'update': '更新',
+      'delete': '删除',
+      'complete': '完成',
+      'cancel': '取消',
+      'assign': '分配',
+      'approve': '审批',
+      'reject': '拒绝',
+      'upload': '上传',
+      'download': '下载',
+      'share': '分享',
+      'comment': '评论'
+    };
+    return labelMap[actionType] || actionType;
+  }
+
+  /**
+   * 获取实体标签的CSS类名
+   */
+  getEntityTagClass(module: string): string {
+    const classMap: Record<string, string> = {
+      'project': 'project-tag',
+      'customer': 'customer-tag',
+      'task': 'task-tag',
+      'design': 'task-tag',
+      'urgent_task': 'task-tag',
+      'finance': 'finance-tag',
+      'case_library': 'case-tag',
+      'groupchat': 'chat-tag'
+    };
+    return classMap[module] || 'default-tag';
+  }
 }

+ 131 - 16
src/app/pages/admin/employees/employees.html

@@ -151,24 +151,139 @@
           </div>
         </div>
         <div *ngIf="panelMode === 'edit'" class="form-view">
-          <div class="form-group">
-            <label>身份</label>
-            <select class="form-control" [(ngModel)]="formModel.roleName">
-              <option *ngFor="let role of roles" [value]="role">{{ role }}</option>
-            </select>
+          <!-- 员工头像 -->
+          <div class="form-avatar-section">
+            <img [src]="currentEmployee.avatar || '/assets/images/default-avatar.svg'" class="form-avatar" alt="员工头像"/>
+            <div class="form-avatar-info">
+              <div class="form-avatar-name">{{ currentEmployee.name }}</div>
+              <div class="form-avatar-id">ID: {{ currentEmployee.userid || '-' }}</div>
+            </div>
+          </div>
+
+          <!-- 基本信息 -->
+          <div class="form-section">
+            <div class="form-section-title">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+                <circle cx="12" cy="7" r="4"></circle>
+              </svg>
+              基本信息
+            </div>
+
+            <div class="form-group">
+              <label class="form-label required">姓名</label>
+              <input 
+                type="text" 
+                class="form-input" 
+                [(ngModel)]="formModel.name"
+                placeholder="请输入姓名"
+                required
+              />
+            </div>
+
+            <div class="form-group">
+              <label class="form-label required">手机号</label>
+              <input 
+                type="tel" 
+                class="form-input" 
+                [(ngModel)]="formModel.mobile"
+                placeholder="请输入手机号"
+                maxlength="11"
+                required
+              />
+            </div>
+
+            <div class="form-group">
+              <label class="form-label required">企微ID</label>
+              <input 
+                type="text" 
+                class="form-input" 
+                [(ngModel)]="formModel.userid"
+                placeholder="企业微信用户ID"
+                readonly
+                disabled
+              />
+              <div class="form-hint">企微ID由系统同步,不可修改</div>
+            </div>
+          </div>
+
+          <!-- 职位信息 -->
+          <div class="form-section">
+            <div class="form-section-title">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                <line x1="9" y1="9" x2="15" y2="9"></line>
+              </svg>
+              职位信息
+            </div>
+
+            <div class="form-group">
+              <label class="form-label required">身份</label>
+              <select class="form-select" [(ngModel)]="formModel.roleName" required>
+                <option value="">请选择身份</option>
+                <option *ngFor="let role of roles" [value]="role">{{ role }}</option>
+              </select>
+            </div>
+
+            <div class="form-group">
+              <label class="form-label required">部门</label>
+              <select class="form-select" [(ngModel)]="formModel.departmentId">
+                <option [value]="undefined">未分配</option>
+                <option *ngFor="let dept of departments()" [value]="dept.id">{{ dept.name }}</option>
+              </select>
+              <div class="form-hint">客服和管理员无需分配项目组</div>
+            </div>
           </div>
-          <div class="form-group">
-            <label>部门</label>
-            <select class="form-control" [(ngModel)]="formModel.departmentId">
-              <option [value]="undefined">未分配</option>
-              <option *ngFor="let dept of departments()" [value]="dept.id">{{ dept.name }}</option>
-            </select>
+
+          <!-- 状态管理 -->
+          <div class="form-section">
+            <div class="form-section-title">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M12 2a10 10 0 1 0 0 20 10 10 0 1 0 0-20z"></path>
+                <path d="M12 6v6l4 2"></path>
+              </svg>
+              状态管理
+            </div>
+
+            <div class="form-group">
+              <label class="form-label">员工状态</label>
+              <div class="status-toggle">
+                <label class="status-option" [class.active]="!formModel.isDisabled">
+                  <input 
+                    type="radio" 
+                    name="status" 
+                    [value]="false" 
+                    [(ngModel)]="formModel.isDisabled"
+                  />
+                  <span class="status-dot active"></span>
+                  <span>正常</span>
+                </label>
+                <label class="status-option" [class.active]="formModel.isDisabled">
+                  <input 
+                    type="radio" 
+                    name="status" 
+                    [value]="true" 
+                    [(ngModel)]="formModel.isDisabled"
+                  />
+                  <span class="status-dot disabled"></span>
+                  <span>已禁用</span>
+                </label>
+              </div>
+              <div class="form-hint">禁用后该员工将无法登录系统</div>
+            </div>
           </div>
-          <div class="form-group">
-            <label>
-              <input type="checkbox" [(ngModel)]="formModel.isDisabled" />
-              禁用此员工
-            </label>
+
+          <!-- 提示信息 -->
+          <div class="form-notice">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <circle cx="12" cy="12" r="10"></circle>
+              <line x1="12" y1="16" x2="12" y2="12"></line>
+              <line x1="12" y1="8" x2="12.01" y2="8"></line>
+            </svg>
+            <div>
+              <div class="form-notice-title">修改说明</div>
+              <div class="form-notice-text">员工数据主要从企业微信同步,姓名和手机号可以在此修改,修改后将保存到后端数据库。</div>
+            </div>
           </div>
         </div>
       </div>

+ 627 - 16
src/app/pages/admin/employees/employees.scss

@@ -233,20 +233,631 @@
     }
   }
 
-  .panel-body{padding:20px;overflow:auto}
-  .detail-view{display:flex;flex-direction:column;gap:16px}
-  .detail-row{display:flex;align-items:center;gap:12px;margin-bottom:8px}
-  .avatar{width:64px;height:64px;border-radius:50%;object-fit:cover;border:1px solid #eee}
-  .title-block .name{font-size:18px;font-weight:600}
-  .title-block .position{font-size:12px;color:#888}
-  .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
-  .detail-item{background:#fafafa;border:1px solid #f0f0f0;border-radius:8px;padding:10px}
-  .detail-item label{display:block;color:#666;font-size:12px;margin-bottom:6px}
-  .detail-item div{font-size:14px;color:#333}
-  .skills{margin-top:6px}
-  .tags{display:flex;gap:6px;flex-wrap:wrap}
-  .tag{padding:4px 8px;border-radius:12px;background:#eef2ff;color:#4f46e5;font-size:12px}
-  .workload label{display:block;color:#666;font-size:12px;margin-bottom:6px}
-  .panel-header h2{display:flex;align-items:center;gap:8px}
-  .panel-header h2::before{content:"👤"}
+  .panel-body {
+    padding: 24px;
+    overflow: auto;
+    background: #fafafa;
+  }
+
+  .panel-footer {
+    padding: 16px 24px;
+    border-top: 1px solid #f0f0f0;
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    background: white;
+
+    .btn {
+      min-width: 80px;
+      padding: 10px 20px;
+      font-size: 14px;
+      font-weight: 500;
+      border-radius: 6px;
+      transition: all 0.2s;
+
+      &.btn-default {
+        background: white;
+        border: 1px solid #ddd;
+        color: #666;
+
+        &:hover {
+          border-color: #165DFF;
+          color: #165DFF;
+        }
+      }
+
+      &.btn-primary {
+        background: #165DFF;
+        color: white;
+        border: none;
+
+        &:hover {
+          background: #0E4BD8;
+          box-shadow: 0 2px 8px rgba(22, 93, 255, 0.3);
+        }
+      }
+    }
+  }
+
+  // 详情视图
+  .detail-view {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+
+  .detail-row {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    padding: 16px;
+    background: white;
+    border-radius: 12px;
+  }
+
+  .avatar {
+    width: 72px;
+    height: 72px;
+    border-radius: 50%;
+    object-fit: cover;
+    border: 3px solid #f0f0f0;
+  }
+
+  .title-block {
+    .name {
+      font-size: 20px;
+      font-weight: 600;
+      color: #333;
+      margin-bottom: 4px;
+    }
+
+    .position {
+      font-size: 13px;
+      color: #888;
+    }
+  }
+
+  .grid {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    gap: 12px;
+  }
+
+  .detail-item {
+    background: white;
+    border: 1px solid #f0f0f0;
+    border-radius: 10px;
+    padding: 14px;
+    transition: all 0.2s;
+
+    &:hover {
+      border-color: #165DFF;
+      box-shadow: 0 2px 8px rgba(22, 93, 255, 0.1);
+    }
+
+    label {
+      display: block;
+      color: #888;
+      font-size: 12px;
+      margin-bottom: 8px;
+      font-weight: 500;
+    }
+
+    div {
+      font-size: 14px;
+      color: #333;
+      font-weight: 500;
+    }
+  }
+
+  .skills {
+    margin-top: 8px;
+    padding: 16px;
+    background: white;
+    border-radius: 12px;
+
+    label {
+      display: block;
+      color: #666;
+      font-size: 13px;
+      margin-bottom: 12px;
+      font-weight: 500;
+    }
+  }
+
+  .tags {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+  }
+
+  .tag {
+    padding: 6px 12px;
+    border-radius: 16px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    font-size: 12px;
+    font-weight: 500;
+  }
+
+  .workload {
+    padding: 16px;
+    background: white;
+    border-radius: 12px;
+
+    label {
+      display: block;
+      color: #666;
+      font-size: 13px;
+      margin-bottom: 12px;
+      font-weight: 500;
+    }
+  }
+
+  // 表单视图
+  .form-view {
+    display: flex;
+    flex-direction: column;
+    gap: 24px;
+  }
+
+  .form-avatar-section {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    padding: 20px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 12px;
+    color: white;
+
+    .form-avatar {
+      width: 64px;
+      height: 64px;
+      border-radius: 50%;
+      object-fit: cover;
+      border: 3px solid rgba(255, 255, 255, 0.3);
+    }
+
+    .form-avatar-info {
+      flex: 1;
+
+      .form-avatar-name {
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 4px;
+      }
+
+      .form-avatar-id {
+        font-size: 12px;
+        opacity: 0.9;
+      }
+    }
+  }
+
+  .form-section {
+    padding: 20px;
+    background: white;
+    border-radius: 12px;
+    border: 1px solid #f0f0f0;
+
+    .form-section-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 15px;
+      font-weight: 600;
+      color: #333;
+      margin-bottom: 20px;
+      padding-bottom: 12px;
+      border-bottom: 2px solid #f0f0f0;
+
+      svg {
+        color: #165DFF;
+      }
+    }
+  }
+
+  .form-group {
+    margin-bottom: 20px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    .form-label {
+      display: block;
+      font-size: 13px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 8px;
+
+      &.required::after {
+        content: ' *';
+        color: #F53F3F;
+      }
+    }
+
+    .form-input,
+    .form-select {
+      width: 100%;
+      padding: 10px 14px;
+      border: 1px solid #ddd;
+      border-radius: 8px;
+      font-size: 14px;
+      color: #333;
+      transition: all 0.2s;
+      background: white;
+
+      &:focus {
+        outline: none;
+        border-color: #165DFF;
+        box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+      }
+
+      &:disabled {
+        background: #f5f5f5;
+        color: #999;
+        cursor: not-allowed;
+      }
+
+      &::placeholder {
+        color: #bbb;
+      }
+    }
+
+    .form-hint {
+      margin-top: 6px;
+      font-size: 12px;
+      color: #888;
+    }
+  }
+
+  .status-toggle {
+    display: flex;
+    gap: 12px;
+
+    .status-option {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      padding: 12px 16px;
+      border: 2px solid #e5e7eb;
+      border-radius: 8px;
+      cursor: pointer;
+      transition: all 0.2s;
+      background: white;
+
+      input[type="radio"] {
+        display: none;
+      }
+
+      .status-dot {
+        width: 12px;
+        height: 12px;
+        border-radius: 50%;
+
+        &.active {
+          background: #00B42A;
+          box-shadow: 0 0 0 3px rgba(0, 180, 42, 0.2);
+        }
+
+        &.disabled {
+          background: #F53F3F;
+          box-shadow: 0 0 0 3px rgba(245, 63, 63, 0.2);
+        }
+      }
+
+      span:not(.status-dot) {
+        font-size: 14px;
+        font-weight: 500;
+        color: #666;
+      }
+
+      &.active {
+        border-color: #165DFF;
+        background: #f0f7ff;
+
+        span:not(.status-dot) {
+          color: #165DFF;
+        }
+      }
+
+      &:hover {
+        border-color: #165DFF;
+      }
+    }
+  }
+
+  .form-notice {
+    display: flex;
+    gap: 12px;
+    padding: 16px;
+    background: #fff7e6;
+    border: 1px solid #ffd666;
+    border-radius: 8px;
+
+    svg {
+      flex-shrink: 0;
+      color: #fa8c16;
+      margin-top: 2px;
+    }
+
+    .form-notice-title {
+      font-size: 13px;
+      font-weight: 600;
+      color: #d46b08;
+      margin-bottom: 4px;
+    }
+
+    .form-notice-text {
+      font-size: 12px;
+      color: #ad6800;
+      line-height: 1.5;
+    }
+  }
+
+  .panel-header h2 {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    &::before {
+      content: "👤";
+      font-size: 20px;
+    }
+  }
+}
+
+// ========================================
+// 员工管理移动端适配
+// ========================================
+
+@media (max-width: 768px) {
+  .employees-container {
+    padding: 12px;
+  }
+
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 16px;
+
+    h1 {
+      font-size: 20px;
+    }
+
+    .header-actions {
+      width: 100%;
+
+      button {
+        width: 100%;
+      }
+    }
+  }
+
+  // 统计卡片适配
+  .stats-cards {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+  }
+
+  .stat-card {
+    padding: 16px;
+
+    .stat-value {
+      font-size: 24px;
+    }
+
+    .stat-label {
+      font-size: 12px;
+    }
+  }
+
+  // 搜索筛选区域
+  .search-filter-section {
+    flex-direction: column;
+    gap: 12px;
+
+    .search-input,
+    .department-filter,
+    .status-filter {
+      width: 100%;
+      min-width: 100%;
+    }
+  }
+
+  // 员工列表表格卡片式
+  .employees-table {
+    thead {
+      display: none;
+    }
+
+    tbody {
+      display: block;
+    }
+
+    tr {
+      display: block;
+      border: 1px solid #e5e7eb;
+      border-radius: 12px;
+      padding: 16px;
+      margin-bottom: 12px;
+      background: white;
+    }
+
+    td {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 10px 0;
+      border: none;
+
+      &::before {
+        content: attr(data-label);
+        font-weight: 600;
+        color: #666;
+        font-size: 13px;
+        margin-right: 12px;
+      }
+
+      &:not(:last-child) {
+        border-bottom: 1px solid #f5f5f5;
+      }
+    }
+
+    .employee-info {
+      flex-direction: row;
+      justify-content: flex-start;
+
+      .avatar {
+        width: 40px;
+        height: 40px;
+      }
+
+      .info {
+        .name {
+          font-size: 14px;
+        }
+
+        .mobile,
+        .userid {
+          font-size: 12px;
+        }
+      }
+    }
+
+    .action-buttons {
+      justify-content: center;
+      gap: 8px;
+
+      button {
+        min-width: 32px;
+        padding: 6px;
+      }
+    }
+  }
+
+  // 编辑面板全屏显示
+  .edit-panel-overlay {
+    padding: 0;
+  }
+
+  .edit-panel {
+    width: 100vw;
+    max-width: 100vw;
+    height: 100vh;
+    max-height: 100vh;
+    border-radius: 0;
+
+    .panel-header {
+      padding: 16px;
+      position: sticky;
+      top: 0;
+      z-index: 10;
+
+      h2 {
+        font-size: 18px;
+      }
+
+      .close-btn {
+        width: 36px;
+        height: 36px;
+      }
+    }
+
+    .panel-body {
+      padding: 16px;
+      height: calc(100vh - 140px);
+
+      .form-avatar-section {
+        padding: 20px 16px;
+
+        .avatar-container {
+          width: 80px;
+          height: 80px;
+        }
+
+        .employee-name {
+          font-size: 18px;
+        }
+
+        .employee-id {
+          font-size: 13px;
+        }
+      }
+
+      .form-section {
+        padding: 16px;
+        margin-bottom: 12px;
+
+        .section-title {
+          font-size: 15px;
+        }
+
+        .form-group {
+          margin-bottom: 16px;
+
+          .form-label {
+            font-size: 13px;
+          }
+
+          .form-input,
+          .form-select {
+            font-size: 16px; // 防止iOS自动缩放
+            padding: 12px;
+          }
+        }
+
+        .status-toggle {
+          .toggle-option {
+            padding: 12px;
+
+            .option-label {
+              font-size: 14px;
+            }
+          }
+        }
+      }
+
+      .form-notice {
+        padding: 12px;
+        font-size: 12px;
+      }
+    }
+
+    .panel-footer {
+      padding: 16px;
+      position: sticky;
+      bottom: 0;
+      flex-direction: column;
+      gap: 12px;
+
+      button {
+        width: 100%;
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .employees-container {
+    padding: 8px;
+  }
+
+  .page-header h1 {
+    font-size: 18px;
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr;
+  }
+
+  .stat-card {
+    padding: 12px;
+
+    .stat-value {
+      font-size: 20px;
+    }
+  }
 }

+ 48 - 2
src/app/pages/admin/employees/employees.ts

@@ -187,7 +187,15 @@ export class Employees implements OnInit {
   // 编辑 (员工从企微同步,只能编辑部分字段,不能删除)
   editEmployee(emp: Employee) {
     this.currentEmployee = emp;
-    this.formModel = { ...emp };
+    // 复制所有需要编辑的字段
+    this.formModel = {
+      name: emp.name,
+      mobile: emp.mobile,
+      userid: emp.userid, // 企微ID只读,但需要显示
+      roleName: emp.roleName,
+      departmentId: emp.departmentId,
+      isDisabled: emp.isDisabled || false
+    };
     this.panelMode = 'edit';
     this.showPanel = true;
   }
@@ -204,8 +212,43 @@ export class Employees implements OnInit {
   async updateEmployee() {
     if (!this.currentEmployee) return;
 
+    // 表单验证
+    if (!this.formModel.name?.trim()) {
+      alert('请输入员工姓名');
+      return;
+    }
+
+    if (!this.formModel.mobile?.trim()) {
+      alert('请输入手机号');
+      return;
+    }
+
+    // 手机号格式验证
+    const mobileRegex = /^1[3-9]\d{9}$/;
+    if (!mobileRegex.test(this.formModel.mobile)) {
+      alert('请输入正确的手机号格式');
+      return;
+    }
+
+    if (!this.formModel.roleName) {
+      alert('请选择员工身份');
+      return;
+    }
+
     try {
+      // 保存所有可编辑字段到后端数据库
       await this.employeeService.updateEmployee(this.currentEmployee.id, {
+        name: this.formModel.name.trim(),
+        mobile: this.formModel.mobile.trim(),
+        roleName: this.formModel.roleName,
+        departmentId: this.formModel.departmentId,
+        isDisabled: this.formModel.isDisabled || false
+      });
+
+      console.log('✅ 员工信息已保存到Parse数据库', {
+        id: this.currentEmployee.id,
+        name: this.formModel.name,
+        mobile: this.formModel.mobile,
         roleName: this.formModel.roleName,
         departmentId: this.formModel.departmentId,
         isDisabled: this.formModel.isDisabled
@@ -213,8 +256,11 @@ export class Employees implements OnInit {
 
       await this.loadEmployees();
       this.closePanel();
+      
+      // 显示成功提示
+      alert('员工信息更新成功!');
     } catch (error) {
-      console.error('更新员工失败:', error);
+      console.error('更新员工失败:', error);
       alert('更新员工失败,请重试');
     }
   }

+ 175 - 36
src/app/pages/admin/groupchats/groupchats.html

@@ -72,53 +72,191 @@
     </table>
   </div>
 
-  <!-- 侧边面板 -->
-  <div class="side-panel" [class.open]="showPanel">
+  <!-- 侧边面板(增强版) -->
+  <div class="side-panel-enhanced" [class.open]="showPanel">
     <div class="panel-overlay" (click)="closePanel()"></div>
-    <div class="panel-content">
-      <div class="panel-header">
-        <h2>{{ panelMode === 'edit' ? '编辑群组' : '群组详情' }}</h2>
-        <button class="btn-close" (click)="closePanel()">×</button>
-      </div>
-      <div class="panel-body" *ngIf="currentGroupChat">
-        <div *ngIf="panelMode === 'detail'" class="detail-view">
-          <!-- 基础信息 -->
-          <div class="section-title">基础信息</div>
-          <div class="detail-item"><label>群名称</label><div>{{ currentGroupChat.name }}</div></div>
-          <div class="detail-item"><label>企微群ID</label><div><code>{{ currentGroupChat.chat_id }}</code></div></div>
-          <div class="detail-item"><label>关联项目</label><div>{{ currentGroupChat.project?.get("title") || '未关联' }}</div></div>
-          <div class="detail-item"><label>成员数</label><div>{{ currentGroupChat.memberCount }}</div></div>
-          <div class="detail-item"><label>状态</label><div>{{ currentGroupChat.isDisabled ? '已禁用' : '正常' }}</div></div>
-
-          <!-- 入群二维码 -->
-          <div class="section-title" *ngIf="qrCodeUrl">入群二维码</div>
-          <div class="qr-code-container" *ngIf="qrCodeUrl">
-            <img [src]="qrCodeUrl" alt="入群二维码" class="qr-code-image" />
-            <p class="qr-code-tip">扫描二维码加入群聊</p>
+    <div class="panel-content-enhanced">
+      <!-- 面板头部 -->
+      <div class="panel-header-enhanced">
+        <div class="header-top">
+          <div class="header-icon">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+              <circle cx="9" cy="7" r="4"></circle>
+              <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+              <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+            </svg>
           </div>
+          <div class="header-info">
+            <h2>{{ panelMode === 'edit' ? '编辑群组' : '群组详情' }}</h2>
+            <p class="header-subtitle" *ngIf="currentGroupChat">{{ currentGroupChat.name }}</p>
+          </div>
+          <button class="btn-close-enhanced" (click)="closePanel()">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <line x1="18" y1="6" x2="6" y2="18"></line>
+              <line x1="6" y1="6" x2="18" y2="18"></line>
+            </svg>
+          </button>
+        </div>
+      </div>
 
-          <!-- 群成员列表 -->
-          <div class="section-title">群成员列表</div>
-          <div class="loading-text" *ngIf="loadingDetail()">加载成员信息中...</div>
-          <div class="members-list" *ngIf="!loadingDetail() && groupMembers.length > 0">
-            <div class="member-item" *ngFor="let member of groupMembers">
-              <div class="member-info">
-                <div class="member-name">
-                  {{ member.name || member.userid || '未知成员' }}
-                  <span class="member-type" [class.external]="member.type === 2">
-                    {{ member.type === 1 ? '内部' : '外部' }}
+      <!-- 面板主体 -->
+      <div class="panel-body-enhanced" *ngIf="currentGroupChat">
+        <div *ngIf="panelMode === 'detail'" class="detail-view-enhanced">
+          <!-- 基础信息卡片 -->
+          <div class="info-card">
+            <div class="card-header-mini">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <circle cx="12" cy="12" r="10"></circle>
+                <line x1="12" y1="16" x2="12" y2="12"></line>
+                <line x1="12" y1="8" x2="12.01" y2="8"></line>
+              </svg>
+              <span>基础信息</span>
+            </div>
+            <div class="info-grid">
+              <div class="info-item-enhanced">
+                <div class="info-label">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
+                    <line x1="7" y1="7" x2="7.01" y2="7"></line>
+                  </svg>
+                  群名称
+                </div>
+                <div class="info-value">{{ currentGroupChat.name }}</div>
+              </div>
+              <div class="info-item-enhanced">
+                <div class="info-label">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <polyline points="16 18 22 12 16 6"></polyline>
+                    <polyline points="8 6 2 12 8 18"></polyline>
+                  </svg>
+                  企微群ID
+                </div>
+                <div class="info-value"><code class="code-tag">{{ currentGroupChat.chat_id }}</code></div>
+              </div>
+              <div class="info-item-enhanced">
+                <div class="info-label">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
+                    <polyline points="9 22 9 12 15 12 15 22"></polyline>
+                  </svg>
+                  关联项目
+                </div>
+                <div class="info-value">
+                  <span class="project-badge" *ngIf="currentGroupChat.project">
+                    {{ currentGroupChat.project?.get("title") }}
                   </span>
+                  <span class="no-data" *ngIf="!currentGroupChat.project">未关联</span>
+                </div>
+              </div>
+              <div class="info-item-enhanced">
+                <div class="info-label">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                    <circle cx="9" cy="7" r="4"></circle>
+                    <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                    <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+                  </svg>
+                  成员数
+                </div>
+                <div class="info-value">
+                  <span class="count-badge">{{ currentGroupChat.memberCount }}</span>
+                </div>
+              </div>
+              <div class="info-item-enhanced">
+                <div class="info-label">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <circle cx="12" cy="12" r="10"></circle>
+                    <polyline points="12 6 12 12 16 14"></polyline>
+                  </svg>
+                  状态
                 </div>
-                <div class="member-detail" *ngIf="member.userid">
-                  <code>{{ member.userid }}</code>
+                <div class="info-value">
+                  <span class="status-badge" [class.disabled]="currentGroupChat.isDisabled">
+                    <span class="status-dot"></span>
+                    {{ currentGroupChat.isDisabled ? '已禁用' : '正常' }}
+                  </span>
                 </div>
               </div>
             </div>
           </div>
-          <div class="empty-text" *ngIf="!loadingDetail() && groupMembers.length === 0">
-            暂无成员信息
+
+          <!-- 入群二维码卡片 -->
+          <div class="qr-card" *ngIf="qrCodeUrl">
+            <div class="card-header-mini">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <rect x="3" y="3" width="7" height="7"></rect>
+                <rect x="14" y="3" width="7" height="7"></rect>
+                <rect x="14" y="14" width="7" height="7"></rect>
+                <rect x="3" y="14" width="7" height="7"></rect>
+              </svg>
+              <span>入群二维码</span>
+            </div>
+            <div class="qr-content">
+              <div class="qr-image-wrapper">
+                <img [src]="qrCodeUrl" alt="入群二维码" class="qr-image" />
+              </div>
+              <p class="qr-tip">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+                </svg>
+                扫描二维码即可加入群聊
+              </p>
+            </div>
+          </div>
+
+          <!-- 群成员列表卡片 -->
+          <div class="members-card">
+            <div class="card-header-mini">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                <circle cx="9" cy="7" r="4"></circle>
+                <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+              </svg>
+              <span>群成员列表</span>
+              <span class="member-count" *ngIf="groupMembers.length > 0">({{ groupMembers.length }})</span>
+            </div>
+            
+            <div class="loading-state" *ngIf="loadingDetail()">
+              <div class="spinner"></div>
+              <p>加载成员信息中...</p>
+            </div>
+            
+            <div class="members-grid" *ngIf="!loadingDetail() && groupMembers.length > 0">
+              <div class="member-card" *ngFor="let member of groupMembers">
+                <div class="member-avatar">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+                    <circle cx="12" cy="7" r="4"></circle>
+                  </svg>
+                </div>
+                <div class="member-content">
+                  <div class="member-name">
+                    {{ member.name || member.userid || '未知成员' }}
+                  </div>
+                  <div class="member-meta">
+                    <span class="member-type-badge" [class.external]="member.type === 2">
+                      {{ member.type === 1 ? '内部成员' : '外部联系人' }}
+                    </span>
+                    <code class="member-id" *ngIf="member.userid">{{ member.userid }}</code>
+                  </div>
+                </div>
+              </div>
+            </div>
+            
+            <div class="empty-state-card" *ngIf="!loadingDetail() && groupMembers.length === 0">
+              <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                <circle cx="9" cy="7" r="4"></circle>
+                <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+              </svg>
+              <p>暂无成员信息</p>
+            </div>
           </div>
         </div>
+
         <div *ngIf="panelMode === 'edit'" class="form-view">
           <div class="form-group">
             <label>关联项目</label>
@@ -135,6 +273,7 @@
           </div>
         </div>
       </div>
+
       <div class="panel-footer" *ngIf="panelMode === 'edit'">
         <button class="btn btn-default" (click)="closePanel()">取消</button>
         <button class="btn btn-primary" (click)="updateGroupChat()">更新</button>

+ 495 - 0
src/app/pages/admin/groupchats/groupchats.scss

@@ -9,6 +9,501 @@ code {
   font-size: 12px;
 }
 
+/* ========== 增强版侧边面板样式 ========== */
+.side-panel-enhanced {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  z-index: 9999;
+  pointer-events: none;
+  
+  &.open {
+    pointer-events: all;
+    
+    .panel-overlay {
+      opacity: 1;
+    }
+    
+    .panel-content-enhanced {
+      transform: translateX(0);
+    }
+  }
+  
+  .panel-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    backdrop-filter: blur(4px);
+    opacity: 0;
+    transition: opacity 0.3s ease;
+  }
+  
+  .panel-content-enhanced {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    width: 90%;
+    max-width: 550px;
+    background: #f8f9fa;
+    transform: translateX(100%);
+    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    display: flex;
+    flex-direction: column;
+    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.12);
+  }
+}
+
+/* 面板头部 */
+.panel-header-enhanced {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 24px;
+  flex-shrink: 0;
+  
+  .header-top {
+    display: flex;
+    align-items: flex-start;
+    gap: 16px;
+  }
+  
+  .header-icon {
+    width: 48px;
+    height: 48px;
+    background: rgba(255, 255, 255, 0.2);
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    backdrop-filter: blur(10px);
+    
+    svg {
+      stroke: white;
+    }
+  }
+  
+  .header-info {
+    flex: 1;
+    min-width: 0;
+    
+    h2 {
+      margin: 0 0 4px;
+      font-size: 20px;
+      font-weight: 600;
+      color: white;
+    }
+    
+    .header-subtitle {
+      margin: 0;
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.85);
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+  
+  .btn-close-enhanced {
+    width: 36px;
+    height: 36px;
+    background: rgba(255, 255, 255, 0.15);
+    border: none;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    flex-shrink: 0;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      background: rgba(255, 255, 255, 0.25);
+      transform: scale(1.05);
+    }
+    
+    svg {
+      stroke: white;
+    }
+  }
+}
+
+/* 面板主体 */
+.panel-body-enhanced {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+  
+  /* 自定义滚动条 */
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+    
+    &:hover {
+      background: #a8a8a8;
+    }
+  }
+}
+
+.detail-view-enhanced {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+/* 卡片通用样式 */
+.info-card,
+.qr-card,
+.members-card {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  animation: slideInUp 0.4s ease;
+}
+
+@keyframes slideInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.card-header-mini {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 16px 20px;
+  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+  border-bottom: 2px solid #dee2e6;
+  font-size: 15px;
+  font-weight: 600;
+  color: #495057;
+  
+  svg {
+    flex-shrink: 0;
+    stroke: #667eea;
+  }
+  
+  .member-count {
+    margin-left: auto;
+    font-size: 13px;
+    color: #6c757d;
+    font-weight: 500;
+  }
+}
+
+/* 基础信息网格 */
+.info-grid {
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 0;
+  padding: 20px;
+}
+
+.info-item-enhanced {
+  padding: 12px 0;
+  border-bottom: 1px solid #f0f0f0;
+  
+  &:last-child {
+    border-bottom: none;
+  }
+  
+  .info-label {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 13px;
+    color: #6c757d;
+    margin-bottom: 6px;
+    font-weight: 500;
+    
+    svg {
+      flex-shrink: 0;
+      stroke: #adb5bd;
+    }
+  }
+  
+  .info-value {
+    font-size: 14px;
+    color: #212529;
+    font-weight: 500;
+    padding-left: 20px;
+    
+    .code-tag {
+      background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+      padding: 4px 10px;
+      border-radius: 6px;
+      font-family: 'Courier New', monospace;
+      font-size: 12px;
+      color: #495057;
+      border: 1px solid #dee2e6;
+    }
+    
+    .project-badge {
+      display: inline-block;
+      padding: 4px 12px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: white;
+      border-radius: 16px;
+      font-size: 13px;
+      font-weight: 500;
+    }
+    
+    .no-data {
+      color: #adb5bd;
+      font-style: italic;
+    }
+    
+    .count-badge {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      min-width: 32px;
+      height: 32px;
+      padding: 0 12px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: white;
+      border-radius: 16px;
+      font-size: 14px;
+      font-weight: 600;
+    }
+    
+    .status-badge {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      padding: 4px 12px;
+      background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+      color: #155724;
+      border-radius: 16px;
+      font-size: 13px;
+      font-weight: 500;
+      
+      &.disabled {
+        background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
+        color: #721c24;
+        
+        .status-dot {
+          background: #dc3545;
+        }
+      }
+      
+      .status-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        background: #28a745;
+      }
+    }
+  }
+}
+
+/* 二维码卡片 */
+.qr-content {
+  padding: 24px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.qr-image-wrapper {
+  position: relative;
+  padding: 12px;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  
+  .qr-image {
+    display: block;
+    width: 200px;
+    height: 200px;
+    border-radius: 8px;
+  }
+}
+
+.qr-tip {
+  margin-top: 16px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  color: #6c757d;
+  
+  svg {
+    flex-shrink: 0;
+    stroke: #667eea;
+  }
+}
+
+/* 成员列表网格 */
+.members-grid {
+  padding: 16px;
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 12px;
+  max-height: 500px;
+  overflow-y: auto;
+  
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f8f9fa;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #dee2e6;
+    border-radius: 3px;
+    
+    &:hover {
+      background: #adb5bd;
+    }
+  }
+}
+
+.member-card {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  padding: 12px;
+  background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
+  border: 1px solid #e9ecef;
+  border-radius: 10px;
+  transition: all 0.2s ease;
+  
+  &:hover {
+    transform: translateX(4px);
+    box-shadow: 0 2px 8px rgba(102, 126, 234, 0.15);
+    border-color: #667eea;
+  }
+  
+  .member-avatar {
+    width: 40px;
+    height: 40px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    
+    svg {
+      stroke: white;
+    }
+  }
+  
+  .member-content {
+    flex: 1;
+    min-width: 0;
+  }
+  
+  .member-name {
+    font-size: 14px;
+    font-weight: 600;
+    color: #212529;
+    margin-bottom: 6px;
+  }
+  
+  .member-meta {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    flex-wrap: wrap;
+  }
+  
+  .member-type-badge {
+    display: inline-block;
+    padding: 2px 10px;
+    background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+    color: #155724;
+    font-size: 12px;
+    border-radius: 10px;
+    font-weight: 500;
+    
+    &.external {
+      background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
+      color: #856404;
+    }
+  }
+  
+  .member-id {
+    font-size: 11px;
+    background: #e9ecef;
+    padding: 2px 8px;
+    border-radius: 6px;
+    font-family: 'Courier New', monospace;
+    color: #495057;
+  }
+}
+
+/* 加载状态 */
+.loading-state {
+  padding: 40px 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 16px;
+  
+  .spinner {
+    width: 40px;
+    height: 40px;
+    border: 3px solid #f3f3f3;
+    border-top: 3px solid #667eea;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+  }
+  
+  p {
+    margin: 0;
+    font-size: 14px;
+    color: #6c757d;
+  }
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 空状态卡片 */
+.empty-state-card {
+  padding: 60px 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  
+  svg {
+    stroke: #dee2e6;
+    margin-bottom: 16px;
+  }
+  
+  p {
+    margin: 0;
+    font-size: 14px;
+    color: #adb5bd;
+  }
+}
+
 /* 详情面板增强样式 */
 .section-title {
   font-size: 16px;

+ 14 - 12
src/app/pages/admin/project-management/project-management.html

@@ -34,10 +34,12 @@
         <mat-select [(ngModel)]="statusFilter" (selectionChange)="onStatusFilterChange()"
                    placeholder="项目状态" class="status-filter">
           <mat-option value="">全部状态</mat-option>
-          <mat-option value="pending">待开始</mat-option>
-          <mat-option value="in-progress">进行中</mat-option>
-          <mat-option value="completed">已完成</mat-option>
-          <mat-option value="on-hold">暂停中</mat-option>
+          <mat-option value="待分配">待分配</mat-option>
+          <mat-option value="进行中">进行中</mat-option>
+          <mat-option value="已完成">已完成</mat-option>
+          <mat-option value="已暂停">已暂停</mat-option>
+          <mat-option value="已延期">已延期</mat-option>
+          <mat-option value="已取消">已取消</mat-option>
         </mat-select>
       </div>
       
@@ -87,7 +89,7 @@
             </div>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="项目名称">
           <div class="project-name">
             <a [routerLink]="['/admin/project-detail', project.id]">{{ project.title }}</a>
           </div>
@@ -107,7 +109,7 @@
             </div>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="客户">
           {{ project.customer }}
         </td>
       </ng-container>
@@ -125,7 +127,7 @@
             </div>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="负责人">
           {{ project.assignee }}
         </td>
       </ng-container>
@@ -143,7 +145,7 @@
             </div>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="状态">
           <div class="status-badge" [style.backgroundColor]="statusColors[project.status]">
             {{ statusTexts[project.status] }}
           </div>
@@ -157,7 +159,7 @@
             <span>当前阶段</span>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="当前阶段">
           {{ project.currentStage || '-' }}
         </td>
       </ng-container>
@@ -175,15 +177,15 @@
             </div>
           </div>
         </th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <td mat-cell *matCellDef="let project" class="table-cell" data-label="更新时间">
           {{ project.updatedAt ? (project.updatedAt | date:'yyyy-MM-dd HH:mm') : '-' }}
         </td>
       </ng-container>
 
       <!-- 操作列 -->
       <ng-container matColumnDef="actions">
-        <th mat-header-cell *matHeaderCellDef class="table-header">操作</th>
-        <td mat-cell *matCellDef="let project" class="table-cell">
+        <th mat-header-cell *matHeaderCellDef class="table-header actions-header">操作</th>
+        <td mat-cell *matCellDef="let project" class="table-cell actions-cell" data-label="操作">
           <div class="action-buttons">
             <button mat-icon-button class="action-btn" color="primary"
                     title="查看详情"

+ 140 - 0
src/app/pages/admin/project-management/project-management.scss

@@ -337,6 +337,15 @@ $shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.
       }
     }
 
+    // 让操作列表头与单元格右对齐
+    .actions-header {
+      text-align: right;
+    }
+
+    .actions-cell {
+      text-align: right;
+    }
+
     tr:last-child .table-cell {
       border-bottom: none;
     }
@@ -493,4 +502,135 @@ $shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.
   .stats-cards {
     grid-template-columns: 1fr 1fr;
   }
+
+  .page-header {
+    .page-title {
+      font-size: 20px !important;
+    }
+  }
+
+  .stat-card {
+    padding: 12px;
+
+    .stat-value {
+      font-size: 20px;
+    }
+
+    .stat-label {
+      font-size: 12px;
+    }
+  }
+}
+
+// 移动端表格卡片式布局
+@media (max-width: 768px) {
+  .project-table {
+    thead {
+      display: none;
+    }
+
+    tbody {
+      display: block;
+    }
+
+    tr {
+      display: block;
+      border: 1px solid $border-color;
+      border-radius: 12px;
+      padding: 16px;
+      margin-bottom: 12px;
+      background: white;
+      box-shadow: $shadow-sm;
+
+      &:hover {
+        box-shadow: $shadow-md;
+      }
+    }
+
+    td {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 10px 0;
+      border: none;
+
+      &::before {
+        content: attr(data-label);
+        font-weight: 600;
+        color: $text-secondary;
+        font-size: 13px;
+        flex-shrink: 0;
+        margin-right: 12px;
+        min-width: 80px;
+      }
+
+      &:not(:last-child) {
+        border-bottom: 1px solid #f5f5f5;
+      }
+
+      .action-buttons {
+        justify-content: flex-end;
+        flex-wrap: wrap;
+        gap: 6px;
+
+        .action-btn {
+          width: 32px;
+          height: 32px;
+        }
+      }
+    }
+
+    .table-cell:last-child {
+      flex-direction: column;
+      align-items: stretch;
+
+      &::before {
+        margin-bottom: 8px;
+      }
+
+      .action-buttons {
+        width: 100%;
+        justify-content: center;
+      }
+    }
+  }
+
+  // 搜索筛选区域适配
+  .search-filter-section {
+    .filter-controls {
+      width: 100%;
+      flex-direction: column;
+
+      .status-filter-wrapper,
+      .filter-btn {
+        width: 100%;
+      }
+
+      .status-filter {
+        width: 100%;
+      }
+    }
+  }
+
+  // 分页按钮适配
+  .pagination-controls {
+    .pagination-buttons {
+      button {
+        min-width: 36px;
+        padding: 8px;
+      }
+
+      .page-numbers {
+        button {
+          display: none;
+
+          &.active,
+          &:first-child,
+          &:last-child {
+            display: flex;
+          }
+        }
+      }
+    }
+  }
 }

+ 55 - 0
src/app/pages/admin/services/project.service.ts

@@ -264,4 +264,59 @@ export class ProjectService {
   toJSONArray(projects: FmodeObject[]): any[] {
     return projects.map(p => this.toJSON(p));
   }
+
+  /**
+   * 查询项目列表(带完整关联)
+   */
+  async findProjectsWithRelations(options?: {
+    status?: string;
+    keyword?: string;
+    skip?: number;
+    limit?: number;
+    include?: string[];
+  }): Promise<FmodeObject[]> {
+    return await this.adminData.findAll('Project', {
+      include: options?.include || ['contact', 'assignee', 'department', 'department.leader'],
+      skip: options?.skip || 0,
+      limit: options?.limit || 20,
+      descending: 'updatedAt',
+      additionalQuery: query => {
+        if (options?.status) {
+          query.equalTo('status', options.status);
+        }
+        if (options?.keyword) {
+          const kw = options.keyword.trim();
+          if (kw) {
+            query.matches('title', new RegExp(kw, 'i'));
+          }
+        }
+      }
+    });
+  }
+
+  /**
+   * 查询项目的所有Product
+   */
+  async findProducts(options: {
+    projectId: string;
+    limit?: number;
+  }): Promise<FmodeObject[]> {
+    try {
+      const query = new Parse.Query('Product');
+      query.equalTo('project', {
+        __type: 'Pointer',
+        className: 'Project',
+        objectId: options.projectId
+      });
+      query.notEqualTo('isDeleted', true);
+      query.descending('createdAt');
+      query.limit(options.limit || 100);
+      
+      const products = await query.find();
+      return products;
+    } catch (error) {
+      console.error('查询Product失败:', error);
+      return [];
+    }
+  }
 }

+ 9 - 4
src/app/pages/customer-service/case-library/case-detail-panel.component.ts

@@ -1,15 +1,17 @@
 import { Component, Input, Output, EventEmitter } from '@angular/core';
 import { CommonModule } from '@angular/common';
 
-interface Case {
+export interface Case {
   id: string;
   name: string;
   coverImage: string;
-  projectType: '工装' | '家装';
-  spaceType: '平层' | '复式' | '别墅' | '自建房';
-  renderingLevel: '高端' | '中端' | '低端';
+  projectType: '工装' | '家装' | string;
+  spaceType: '平层' | '复式' | '别墅' | '自建房' | string;
+  renderingLevel: '高端' | '中端' | '低端' | string;
   designer: string;
+  designerId?: string;
   team: string;
+  teamId?: string;
   area: number;
   styleTags: string[];
   customerReview?: string;
@@ -18,7 +20,10 @@ interface Case {
   favoriteCount: number;
   isFavorite: boolean;
   isExcellent: boolean;
+  isPublished?: boolean;
   createdAt: Date;
+  projectId?: string;
+  data?: any;
 }
 
 @Component({

+ 44 - 3
src/app/pages/customer-service/case-library/case-library.html

@@ -3,6 +3,13 @@
   <div class="page-header">
     <h1>案例库</h1>
     <div class="header-actions">
+      <button class="btn btn-success" (click)="openCreateModal()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <line x1="12" y1="5" x2="12" y2="19"></line>
+          <line x1="5" y1="12" x2="19" y2="12"></line>
+        </svg>
+        创建案例
+      </button>
       <button class="btn btn-primary" (click)="showStatistics()">
         <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M3 3v18h18"></path>
@@ -127,8 +134,16 @@
     </div>
   </div>
 
+  <!-- 加载状态 -->
+  @if (loading) {
+    <div class="loading-state">
+      <div class="spinner"></div>
+      <p>加载中...</p>
+    </div>
+  }
+
   <!-- 案例网格 -->
-  <div class="cases-grid">
+  <div class="cases-grid" [class.hidden]="loading">
     @for (caseItem of paginatedCases; track caseItem.id) {
       <div class="case-card">
         <!-- 图片容器 -->
@@ -241,6 +256,24 @@
                 <span>收藏: {{ caseItem.favoriteCount }}</span>
               </div>
             </div>
+            
+            <!-- 管理操作按钮 -->
+            <div class="case-actions">
+              <button class="btn-action btn-edit" (click)="openEditModal(caseItem)" title="编辑案例">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
+                  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
+                </svg>
+                编辑
+              </button>
+              <button class="btn-action btn-delete" (click)="deleteCase(caseItem)" title="删除案例">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polyline points="3 6 5 6 21 6"></polyline>
+                  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
+                </svg>
+                删除
+              </button>
+            </div>
           }
 
           <!-- 操作按钮 -->
@@ -268,7 +301,7 @@
   </div>
 
   <!-- 分页控件 -->
-  @if (filteredCases.length > 0) {
+  @if (totalCount > 0 && !loading) {
     <div class="pagination">
       <button 
         class="pagination-btn" 
@@ -278,7 +311,7 @@
         上一页
       </button>
       
-      <span class="page-info">第 {{ currentPage }} 页 / 共 {{ totalPages }} 页 ({{ filteredCases.length }} 个案例)</span>
+      <span class="page-info">第 {{ currentPage }} 页 / 共 {{ totalPages }} 页 (共 {{ totalCount }} 个案例)</span>
       
       <button 
         class="pagination-btn" 
@@ -342,4 +375,12 @@
       </div>
     </div>
   }
+
+  <!-- 案例管理模态框 -->
+  <app-case-management-modal
+    [isVisible]="showManagementModal"
+    [editingCase]="editingCase"
+    (close)="closeManagementModal()"
+    (success)="handleManagementSuccess($event)"
+  ></app-case-management-modal>
 </div>

+ 417 - 9
src/app/pages/customer-service/case-library/case-library.scss

@@ -46,6 +46,9 @@ $red-500: #ef4444;
     }
     
     .header-actions {
+      display: flex;
+      gap: 12px;
+      
       .btn {
         display: flex;
         align-items: center;
@@ -54,11 +57,31 @@ $red-500: #ef4444;
         border-radius: 8px;
         font-weight: 500;
         transition: all 0.2s ease;
+        border: none;
+        cursor: pointer;
         
         &:hover {
           transform: translateY(-1px);
           box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
         }
+        
+        &.btn-success {
+          background: $success-color;
+          color: white;
+          
+          &:hover {
+            background: darken($success-color, 10%);
+          }
+        }
+        
+        &.btn-primary {
+          background: $primary-color;
+          color: white;
+          
+          &:hover {
+            background: $primary-dark;
+          }
+        }
       }
     }
   }
@@ -243,11 +266,45 @@ $red-500: #ef4444;
     }
   }
   
+  .loading-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    min-height: 400px;
+    
+    .spinner {
+      width: 48px;
+      height: 48px;
+      border: 4px solid #f3f3f3;
+      border-top: 4px solid #3880ff;
+      border-radius: 50%;
+      animation: spin 1s linear infinite;
+    }
+    
+    p {
+      margin-top: 16px;
+      color: #666;
+      font-size: 16px;
+    }
+  }
+
+  @keyframes spin {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+  }
+
+  .hidden {
+    display: none !important;
+  }
+
   .cases-grid {
     display: grid;
     grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
     gap: 24px;
     margin-bottom: 32px;
+    min-height: 500px; // 确保有足够的高度来居中显示空状态
     
     .case-card {
       background: white;
@@ -573,9 +630,15 @@ $red-500: #ef4444;
   }
   
   .empty-state {
+    grid-column: 1 / -1; // 占据整个grid的宽度
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
     text-align: center;
-    padding: 80px 20px;
+    padding: 120px 20px;
     color: $text-secondary;
+    min-height: 400px;
     
     svg {
       color: $gray-300;
@@ -881,31 +944,376 @@ $red-500: #ef4444;
       }
     }
   }
+  
+  // 案例操作按钮样式
+  .case-actions {
+    display: flex;
+    gap: 8px;
+    margin-top: 12px;
+    padding-top: 12px;
+    border-top: 1px solid $border-color;
+    
+    .btn-action {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      padding: 8px 14px;
+      border-radius: 6px;
+      font-size: 13px;
+      font-weight: 500;
+      cursor: pointer;
+      border: none;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        transform: translateY(-1px);
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+      
+      &.btn-edit {
+        background: #f0f9ff;
+        color: #0284c7;
+        
+        &:hover {
+          background: #e0f2fe;
+        }
+      }
+      
+      &.btn-delete {
+        background: #fef2f2;
+        color: #dc2626;
+        
+        &:hover {
+          background: #fee2e2;
+        }
+      }
+      
+      svg {
+        flex-shrink: 0;
+      }
+    }
+  }
 }
 
-@media (max-width: 480px) {
+@media (max-width: 768px) {
   .case-library-container {
     padding: 12px;
-    
+
+    // 页面头部适配
+    .page-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 16px;
+      padding: 16px 12px;
+
+      h1 {
+        font-size: 24px;
+      }
+
+      .header-actions {
+        width: 100%;
+
+        .btn {
+          width: 100%;
+          justify-content: center;
+        }
+      }
+    }
+
+    // 筛选栏适配
     .filter-bar {
       padding: 16px;
-      
+      flex-direction: column;
+      gap: 12px;
+
       .filter-group {
-        .filter-select {
-          min-width: 100px;
+        width: 100%;
+        flex-direction: column;
+        gap: 12px;
+
+        label {
           font-size: 13px;
         }
+
+        .filter-select {
+          width: 100%;
+          min-width: 100%;
+          font-size: 14px;
+          padding: 10px 12px;
+        }
+      }
+
+      .search-box {
+        width: 100%;
+
+        input {
+          width: 100%;
+          font-size: 16px; // 防止iOS自动缩放
+        }
       }
     }
-    
+
+    // 案例网格适配
+    .cases-grid {
+      grid-template-columns: 1fr;
+      gap: 16px;
+      padding: 12px;
+    }
+
+    // 案例卡片适配
     .case-card {
       .case-image-container {
-        height: 200px;
+        height: 220px;
+
+        .case-badges {
+          .badge {
+            font-size: 11px;
+            padding: 4px 10px;
+          }
+        }
+
+        .favorite-btn {
+          width: 36px;
+          height: 36px;
+
+          svg {
+            width: 18px;
+            height: 18px;
+          }
+        }
       }
-      
+
       .case-info {
         padding: 16px;
+
+        .case-title {
+          font-size: 16px;
+        }
+
+        .case-meta {
+          flex-wrap: wrap;
+          gap: 8px;
+
+          .meta-item {
+            font-size: 12px;
+
+            svg {
+              width: 14px;
+              height: 14px;
+            }
+          }
+        }
+
+        .case-tags {
+          .tag {
+            font-size: 11px;
+            padding: 4px 8px;
+          }
+        }
+
+        .case-stats {
+          .stat-item {
+            font-size: 12px;
+
+            svg {
+              width: 14px;
+              height: 14px;
+            }
+          }
+        }
+      }
+
+      // 案例操作按钮移动端适配
+      .case-actions {
+        flex-direction: row;
+        gap: 8px;
+
+        .btn-action {
+          flex: 1;
+          font-size: 12px;
+          padding: 8px 12px;
+        }
+      }
+    }
+
+    // 详情面板适配
+    .case-detail-panel {
+      width: 100vw;
+      max-width: 100vw;
+      height: 100vh;
+      max-height: 100vh;
+      border-radius: 0;
+
+      .panel-header {
+        padding: 16px;
+
+        h2 {
+          font-size: 18px;
+        }
+
+        .close-btn {
+          width: 32px;
+          height: 32px;
+        }
+      }
+
+      .panel-body {
+        padding: 16px;
+
+        .detail-section {
+          padding: 16px;
+
+          h3 {
+            font-size: 16px;
+          }
+        }
+
+        .detail-images {
+          gap: 12px;
+
+          .image-item {
+            height: 200px;
+          }
+        }
+      }
+    }
+
+    // 分享模态框适配
+    .share-modal {
+      width: 90vw;
+      max-width: 90vw;
+      border-radius: 16px;
+      padding: 20px;
+
+      h3 {
+        font-size: 18px;
+      }
+
+      .share-options {
+        flex-direction: column;
+        gap: 12px;
+
+        .share-btn {
+          width: 100%;
+          font-size: 14px;
+        }
       }
     }
+
+    // Loading状态适配
+    .loading-state {
+      .spinner {
+        width: 40px;
+        height: 40px;
+      }
+
+      p {
+        font-size: 14px;
+      }
+    }
+
+    // 空状态适配
+    .empty-state {
+      padding: 60px 20px;
+
+      svg {
+        width: 100px;
+        height: 100px;
+      }
+
+      h3 {
+        font-size: 18px;
+      }
+
+      p {
+        font-size: 14px;
+      }
+    }
+
+    // 分页适配
+    .pagination {
+      flex-wrap: wrap;
+      gap: 8px;
+      padding: 16px 12px;
+
+      .page-info {
+        width: 100%;
+        text-align: center;
+        font-size: 13px;
+        order: -1;
+        margin-bottom: 8px;
+      }
+
+      button {
+        min-width: 40px;
+        padding: 8px;
+        font-size: 13px;
+      }
+
+      .page-numbers {
+        button {
+          &:not(.active):not(:first-child):not(:last-child) {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .case-library-container {
+    padding: 8px;
+
+    .page-header h1 {
+      font-size: 20px;
+    }
+
+    .filter-bar {
+      padding: 12px;
+    }
+
+    .cases-grid {
+      padding: 8px;
+      gap: 12px;
+    }
+
+    .case-card {
+      .case-image-container {
+        height: 200px;
+      }
+
+      .case-info {
+        padding: 12px;
+
+        .case-title {
+          font-size: 15px;
+        }
+      }
+
+      .case-actions {
+        .btn-action {
+          font-size: 11px;
+          padding: 6px 10px;
+        }
+      }
+    }
+
+    .pagination {
+      padding: 12px 8px;
+
+      button {
+        min-width: 36px;
+        padding: 6px;
+        font-size: 12px;
+      }
+    }
+  }
+}
+
+// 平板横屏适配
+@media (min-width: 768px) and (max-width: 1024px) {
+  .cases-grid {
+    grid-template-columns: repeat(2, 1fr) !important;
   }
 }

+ 242 - 316
src/app/pages/customer-service/case-library/case-library.ts

@@ -1,30 +1,12 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { FormControl } from '@angular/forms';
 import { Router } from '@angular/router';
 import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
-import { CaseDetailPanelComponent } from './case-detail-panel.component';
-
-interface Case {
-  id: string;
-  name: string;
-  coverImage: string;
-  projectType: '工装' | '家装';
-  spaceType: '平层' | '复式' | '别墅' | '自建房';
-  renderingLevel: '高端' | '中端' | '低端';
-  designer: string;
-  team: string;
-  area: number;
-  styleTags: string[];
-  customerReview?: string;
-  viewCount: number;
-  shareCount: number;
-  favoriteCount: number;
-  isFavorite: boolean;
-  isExcellent: boolean;
-  createdAt: Date;
-}
+import { CaseDetailPanelComponent, Case } from './case-detail-panel.component';
+import { CaseManagementModalComponent } from './case-management-modal.component';
+import { CaseService } from '../../../services/case.service';
 
 interface StatItem {
   id: string;
@@ -45,11 +27,11 @@ interface DesignerStat {
 @Component({
   selector: 'app-case-library',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, CaseDetailPanelComponent],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, CaseDetailPanelComponent, CaseManagementModalComponent],
   templateUrl: './case-library.html',
   styleUrls: ['./case-library.scss']
 })
-export class CaseLibrary implements OnInit {
+export class CaseLibrary implements OnInit, OnDestroy {
   // 表单控件
   searchControl = new FormControl('');
   projectTypeControl = new FormControl('');
@@ -71,9 +53,13 @@ export class CaseLibrary implements OnInit {
   showStatsPanel = false;
   selectedCase: Case | null = null; // 用于详情面板
   selectedCaseForShare: Case | null = null; // 用于分享模态框
+  showManagementModal = false; // 用于管理模态框
+  editingCase: Case | null = null; // 正在编辑的案例
   currentPage = 1;
   itemsPerPage = 10; // 每页显示10个案例
   totalPages = 1;
+  totalCount = 0;
+  loading = false;
   
   // 用户类型(模拟)
   isInternalUser = true; // 可根据实际用户权限设置
@@ -82,38 +68,119 @@ export class CaseLibrary implements OnInit {
   private pageStartTime = Date.now();
   private caseViewStartTimes = new Map<string, number>();
 
-  constructor(private router: Router) {}
+  constructor(
+    private router: Router,
+    private caseService: CaseService
+  ) {}
 
   ngOnInit() {
-    this.initializeData();
+    this.loadCases();
+    this.loadStatistics();
     this.setupFilterListeners();
     this.setupBehaviorTracking();
   }
+
+  ngOnDestroy() {
+    // 页面销毁时的清理工作
+    console.log('案例库页面销毁');
+  }
   
   private setupBehaviorTracking() {
-    // 记录页面访问
-    this.recordBehavior('page_view', 'case-library', {
-      timestamp: new Date().toISOString()
-    });
-    
-    // 页面卸载时记录停留时长
-    window.addEventListener('beforeunload', () => {
-      const stayDuration = Date.now() - this.pageStartTime;
-      this.recordBehavior('page_stay', 'case-library', {
-        duration: stayDuration,
-        durationMinutes: Math.round(stayDuration / 60000 * 100) / 100
+    // 记录页面访问(可选)
+    console.log('案例库页面已加载');
+  }
+
+  /**
+   * 加载案例列表
+   */
+  async loadCases() {
+    this.loading = true;
+    try {
+      const filters = this.getFilters();
+      const result = await this.caseService.findCases({
+        ...filters,
+        page: this.currentPage,
+        pageSize: this.itemsPerPage
       });
-    });
+      
+      this.cases = result.cases;
+      this.filteredCases = result.cases;
+      this.totalCount = result.total;
+      this.totalPages = Math.ceil(result.total / this.itemsPerPage) || 1;
+      
+      // 如果没有数据,显示友好提示
+      if (result.total === 0) {
+        console.log('暂无案例数据,请先在Parse数据库中创建Case表并添加数据');
+      }
+    } catch (error) {
+      console.error('加载案例列表失败:', error);
+      // 即使出错也设置为空数组,避免页面崩溃
+      this.cases = [];
+      this.filteredCases = [];
+      this.totalCount = 0;
+      this.totalPages = 1;
+      this.showToast('加载案例列表失败,请检查数据库连接', 'error');
+    } finally {
+      this.loading = false;
+    }
   }
 
-  private initializeData() {
-    // 模拟案例数据
-    this.cases = this.generateMockCases();
-    this.filteredCases = [...this.cases];
-    this.updatePagination();
-    
-    // 初始化统计数据
-    this.initializeStats();
+  /**
+   * 加载统计数据
+   */
+  async loadStatistics() {
+    try {
+      // 暂时使用空数据,后续可以添加统计功能
+      this.topSharedCases = [];
+      this.favoriteStyles = [];
+      this.designerRecommendations = [];
+    } catch (error) {
+      console.error('加载统计数据失败:', error);
+    }
+  }
+
+  /**
+   * 获取当前筛选条件
+   */
+  private getFilters() {
+    const filters: any = {
+      isPublished: true
+    };
+
+    const searchKeyword = this.searchControl.value?.trim();
+    if (searchKeyword) {
+      filters.searchKeyword = searchKeyword;
+    }
+
+    const projectType = this.projectTypeControl.value;
+    if (projectType) {
+      filters.projectType = projectType;
+    }
+
+    const spaceType = this.spaceTypeControl.value;
+    if (spaceType) {
+      filters.spaceType = spaceType;
+    }
+
+    const renderingLevel = this.renderingLevelControl.value;
+    if (renderingLevel) {
+      filters.renderingLevel = renderingLevel;
+    }
+
+    const style = this.styleControl.value;
+    if (style) {
+      // 使用tags数组进行筛选
+      filters.tags = [style];
+    }
+
+    const areaRange = this.areaRangeControl.value;
+    if (areaRange) {
+      // 解析面积范围 "0-50" -> { min: 0, max: 50 }
+      const [min, max] = areaRange.split('-').map(Number);
+      filters.areaRange = { min, max: max || undefined };
+    }
+
+    return filters;
   }
 
   private setupFilterListeners() {
@@ -135,147 +202,9 @@ export class CaseLibrary implements OnInit {
     });
   }
 
-  private generateMockCases(): Case[] {
-    const mockCases: Case[] = [];
-    const projectTypes: ('工装' | '家装')[] = ['工装', '家装'];
-    const spaceTypes: ('平层' | '复式' | '别墅' | '自建房')[] = ['平层', '复式', '别墅', '自建房'];
-    const renderingLevels: ('高端' | '中端' | '低端')[] = ['高端', '中端', '低端'];
-    const styles = ['现代', '中式', '欧式', '美式', '日式', '工业风', '极简风', '轻奢风'];
-    const designers = ['张三', '李四', '王五', '赵六', '钱七'];
-    const teams = ['设计一组', '设计二组', '设计三组', '设计四组'];
-
-    for (let i = 1; i <= 50; i++) {
-      const projectType = projectTypes[Math.floor(Math.random() * projectTypes.length)];
-      const spaceType = spaceTypes[Math.floor(Math.random() * spaceTypes.length)];
-      const renderingLevel = renderingLevels[Math.floor(Math.random() * renderingLevels.length)];
-      const styleCount = Math.floor(Math.random() * 3) + 1;
-      const styleTags = Array.from({ length: styleCount }, () => 
-        styles[Math.floor(Math.random() * styles.length)]
-      );
-
-      mockCases.push({
-        id: `case-${i}`,
-        name: `${projectType}${spaceType}设计案例 ${i}`,
-        coverImage: this.generatePlaceholderImage(400, 300, i),
-        projectType,
-        spaceType,
-        renderingLevel,
-        designer: designers[Math.floor(Math.random() * designers.length)],
-        team: teams[Math.floor(Math.random() * teams.length)],
-        area: Math.floor(Math.random() * 200) + 50,
-        styleTags: [...new Set(styleTags)], // 去重
-        customerReview: Math.random() > 0.3 ? `客户非常满意这次${projectType}设计,${spaceType}空间利用得很合理` : undefined,
-        viewCount: Math.floor(Math.random() * 1000),
-        shareCount: Math.floor(Math.random() * 100),
-        favoriteCount: Math.floor(Math.random() * 50),
-        isFavorite: Math.random() > 0.7,
-        isExcellent: Math.random() > 0.5,
-        createdAt: new Date(Date.now() - Math.floor(Math.random() * 365 * 24 * 60 * 60 * 1000))
-      });
-    }
-
-    return mockCases;
-  }
-
-  private generatePlaceholderImage(width: number, height: number, seed: number): string {
-    return `https://picsum.photos/seed/${seed}/${width}/${height}`;
-  }
-
-  private initializeStats() {
-    // Top5 分享案例
-    this.topSharedCases = this.cases
-      .sort((a, b) => b.shareCount - a.shareCount)
-      .slice(0, 5)
-      .map(caseItem => ({
-        id: caseItem.id,
-        name: caseItem.name,
-        shareCount: caseItem.shareCount
-      }));
-
-    // 客户最喜欢案例风格
-    const styleCounts = new Map<string, number>();
-    this.cases.forEach(caseItem => {
-      caseItem.styleTags.forEach(style => {
-        styleCounts.set(style, (styleCounts.get(style) || 0) + caseItem.favoriteCount);
-      });
-    });
-    
-    this.favoriteStyles = Array.from(styleCounts.entries())
-      .sort(([, a], [, b]) => b - a)
-      .slice(0, 5)
-      .map(([style, count]) => ({ style, count }));
-
-    // 设计师作品推荐率
-    const designerStats = new Map<string, { total: number; excellent: number }>();
-    this.cases.forEach(caseItem => {
-      const stats = designerStats.get(caseItem.designer) || { total: 0, excellent: 0 };
-      stats.total++;
-      if (caseItem.isExcellent) stats.excellent++;
-      designerStats.set(caseItem.designer, stats);
-    });
-
-    this.designerRecommendations = Array.from(designerStats.entries())
-      .map(([designer, stats]) => ({
-        designer,
-        rate: Math.round((stats.excellent / stats.total) * 100)
-      }))
-      .sort((a, b) => b.rate - a.rate)
-      .slice(0, 5);
-  }
-
   applyFilters() {
-    const searchTerm = this.searchControl.value?.toLowerCase() || '';
-    const projectType = this.projectTypeControl.value;
-    const spaceType = this.spaceTypeControl.value;
-    const renderingLevel = this.renderingLevelControl.value;
-    const style = this.styleControl.value;
-    const areaRange = this.areaRangeControl.value;
-
-    this.filteredCases = this.cases.filter(caseItem => {
-      // 搜索条件
-      if (searchTerm && !(
-        caseItem.name.toLowerCase().includes(searchTerm) ||
-        caseItem.designer.toLowerCase().includes(searchTerm) ||
-        caseItem.styleTags.some(tag => tag.toLowerCase().includes(searchTerm))
-      )) {
-        return false;
-      }
-
-      // 项目类型筛选
-      if (projectType && caseItem.projectType !== projectType) {
-        return false;
-      }
-
-      // 空间类型筛选
-      if (spaceType && caseItem.spaceType !== spaceType) {
-        return false;
-      }
-
-      // 渲染水平筛选
-      if (renderingLevel && caseItem.renderingLevel !== renderingLevel) {
-        return false;
-      }
-
-      // 风格筛选
-      if (style && !caseItem.styleTags.includes(style)) {
-        return false;
-      }
-
-      // 面积范围筛选
-      if (areaRange) {
-        const [min, max] = areaRange.split('-').map(Number);
-        if (max === undefined) {
-          if (caseItem.area < min) return false;
-        } else if (caseItem.area < min || caseItem.area > max) {
-          return false;
-        }
-      }
-
-      return true;
-    });
-
     this.currentPage = 1;
-    this.updatePagination();
+    this.loadCases();
   }
 
   resetFilters() {
@@ -286,132 +215,75 @@ export class CaseLibrary implements OnInit {
     this.styleControl.setValue('');
     this.areaRangeControl.setValue('');
     
-    this.filteredCases = [...this.cases];
     this.currentPage = 1;
-    this.updatePagination();
-  }
-
-  updatePagination() {
-    this.totalPages = Math.ceil(this.filteredCases.length / this.itemsPerPage);
-    if (this.currentPage > this.totalPages) {
-      this.currentPage = this.totalPages || 1;
-    }
+    this.loadCases();
   }
 
   get paginatedCases(): Case[] {
-    const startIndex = (this.currentPage - 1) * this.itemsPerPage;
-    return this.filteredCases.slice(startIndex, startIndex + this.itemsPerPage);
+    return this.filteredCases;
   }
 
   nextPage() {
     if (this.currentPage < this.totalPages) {
       this.currentPage++;
+      this.loadCases();
     }
   }
 
   previousPage() {
     if (this.currentPage > 1) {
       this.currentPage--;
+      this.loadCases();
     }
   }
 
-  showStatistics() {
-    this.showStatsPanel = !this.showStatsPanel;
-  }
-
-  viewCaseDetail(caseItem: Case) {
+  async viewCaseDetail(caseItem: Case) {
     // 记录案例查看开始时间
     this.caseViewStartTimes.set(caseItem.id, Date.now());
     
-    // 增加浏览次数
-    caseItem.viewCount++;
-    
-    // 记录浏览行为
-    this.recordBehavior('case_view', caseItem.id, {
-      caseName: caseItem.name,
-      designer: caseItem.designer,
-      projectType: caseItem.projectType,
-      spaceType: caseItem.spaceType
-    });
-    
-    // 设置当前选中的案例以显示详情面板
-    this.selectedCase = caseItem;
+    try {
+      // 从数据库获取完整的案例详情
+      const detail = await this.caseService.getCase(caseItem.id);
+      
+      // 设置当前选中的案例以显示详情面板
+      this.selectedCase = detail;
+    } catch (error) {
+      console.error('查看案例详情失败:', error);
+      this.showToast('查看案例详情失败', 'error');
+    }
   }
 
   // 跳转到独立的案例详情页面
-  navigateToCaseDetail(caseItem: Case) {
+  async navigateToCaseDetail(caseItem: Case) {
     // 记录案例查看开始时间
     this.caseViewStartTimes.set(caseItem.id, Date.now());
     
-    // 增加浏览次数
-    caseItem.viewCount++;
-    
-    // 记录浏览行为
-    this.recordBehavior('case_view', caseItem.id, {
-      caseName: caseItem.name,
-      designer: caseItem.designer,
-      projectType: caseItem.projectType,
-      spaceType: caseItem.spaceType
-    });
-    
     // 跳转到独立的案例详情页面
     this.router.navigate(['/customer-service/case-detail', caseItem.id]);
   }
 
   closeCaseDetail() {
-    // 记录案例查看时长
-    if (this.selectedCase) {
-      const viewStartTime = this.caseViewStartTimes.get(this.selectedCase.id);
-      if (viewStartTime) {
-        const viewDuration = Date.now() - viewStartTime;
-        this.recordBehavior('case_view_duration', this.selectedCase.id, {
-          duration: viewDuration,
-          durationSeconds: Math.round(viewDuration / 1000)
-        });
-        this.caseViewStartTimes.delete(this.selectedCase.id);
-      }
-    }
-    
     this.selectedCase = null;
   }
 
-  toggleFavorite(caseItem: Case) {
-    const wasLiked = caseItem.isFavorite;
-    caseItem.isFavorite = !caseItem.isFavorite;
-    
-    if (caseItem.isFavorite) {
-      caseItem.favoriteCount++;
-      this.showToast('已收藏该案例', 'success');
-      
-      // 记录收藏行为
-      this.recordBehavior('case_favorite', caseItem.id, {
-        action: 'add',
-        caseName: caseItem.name,
-        designer: caseItem.designer
-      });
-    } else {
-      caseItem.favoriteCount = Math.max(0, caseItem.favoriteCount - 1);
-      this.showToast('已取消收藏', 'info');
+  async toggleFavorite(caseItem: Case) {
+    try {
+      // 暂时只切换前端状态,后续可添加收藏功能
+      caseItem.isFavorite = !caseItem.isFavorite;
       
-      // 记录取消收藏行为
-      this.recordBehavior('case_favorite', caseItem.id, {
-        action: 'remove',
-        caseName: caseItem.name,
-        designer: caseItem.designer
-      });
+      if (caseItem.isFavorite) {
+        this.showToast('已收藏该案例', 'success');
+      } else {
+        this.showToast('已取消收藏', 'info');
+      }
+    } catch (error) {
+      console.error('切换收藏状态失败:', error);
+      this.showToast('操作失败,请重试', 'error');
     }
   }
 
-  shareCase(caseItem: Case) {
+  async shareCase(caseItem: Case) {
     this.selectedCaseForShare = caseItem;
-    caseItem.shareCount++;
-    
-    // 记录分享行为数据
-    this.recordBehavior('share', caseItem.id, {
-      caseName: caseItem.name,
-      designer: caseItem.designer,
-      projectType: caseItem.projectType
-    });
   }
 
   closeShareModal() {
@@ -440,64 +312,118 @@ export class CaseLibrary implements OnInit {
     return `${window.location.origin}/customer-service/case-detail/${caseItem.id}?from=share&designer=${encodeURIComponent(caseItem.designer)}`;
   }
 
-  copyShareLink() {
-    if (this.selectedCase) {
-      const link = this.generateShareLink(this.selectedCase);
-      navigator.clipboard.writeText(link).then(() => {
+  async copyShareLink() {
+    if (this.selectedCaseForShare) {
+      try {
+        const shareUrl = this.generateShareLink(this.selectedCaseForShare);
+        await navigator.clipboard.writeText(shareUrl);
         this.showToast('链接已复制到剪贴板,可直接分享给客户!', 'success');
-        
-        // 记录复制行为
-        this.recordBehavior('copy_link', this.selectedCase!.id, {
-          link: link
-        });
-      }).catch(err => {
+      } catch (err) {
         this.showToast('复制失败,请手动复制链接', 'error');
         console.error('复制链接失败:', err);
-      });
+      }
     }
   }
 
-  shareToWeCom() {
-    if (this.selectedCase) {
-      // 实际项目中应集成企业微信分享SDK
-      const shareData = {
-        title: `${this.selectedCase.name} - ${this.selectedCase.designer}设计作品`,
-        description: `${this.selectedCase.projectType} | ${this.selectedCase.spaceType} | ${this.selectedCase.area}㎡`,
-        link: this.generateShareLink(this.selectedCase),
-        imgUrl: this.selectedCase.coverImage
-      };
-      
-      // 模拟企业微信分享
-      console.log('分享到企业微信:', shareData);
-      this.showToast('已调用企业微信分享', 'success');
-      
-      // 记录企业微信分享行为
-      this.recordBehavior('share_wecom', this.selectedCase.id, shareData);
-      
-      this.closeShareModal();
+  async shareToWeCom() {
+    if (this.selectedCaseForShare) {
+      try {
+        const shareUrl = this.generateShareLink(this.selectedCaseForShare);
+        
+        // 实际项目中应集成企业微信分享SDK
+        const shareData = {
+          title: `${this.selectedCaseForShare.name} - ${this.selectedCaseForShare.designer}设计作品`,
+          description: `${this.selectedCaseForShare.projectType} | ${this.selectedCaseForShare.spaceType} | ${this.selectedCaseForShare.area}㎡`,
+          link: shareUrl,
+          imgUrl: this.selectedCaseForShare.coverImage
+        };
+        
+        // 模拟企业微信分享
+        console.log('分享到企业微信:', shareData);
+        this.showToast('已调用企业微信分享', 'success');
+        
+        this.closeShareModal();
+      } catch (error) {
+        console.error('分享失败:', error);
+        this.showToast('分享失败,请重试', 'error');
+      }
     }
   }
 
-  // 新增:行为数据记录方法
-  private recordBehavior(action: string, caseId: string, data?: any) {
-    const behaviorData = {
-      action,
-      caseId,
-      timestamp: new Date().toISOString(),
-      userAgent: navigator.userAgent,
-      data: data || {}
-    };
+  showStatistics() {
+    this.showStatsPanel = !this.showStatsPanel;
+    if (this.showStatsPanel) {
+      this.loadStatistics();
+    }
+  }
+
+  // ========== 案例管理功能 ==========
+
+  /**
+   * 打开创建案例模态框
+   */
+  openCreateModal() {
+    this.editingCase = null;
+    this.showManagementModal = true;
+  }
+
+  /**
+   * 打开编辑案例模态框
+   */
+  openEditModal(caseItem: Case) {
+    this.editingCase = caseItem;
+    this.showManagementModal = true;
+  }
+
+  /**
+   * 关闭管理模态框
+   */
+  closeManagementModal() {
+    this.showManagementModal = false;
+    this.editingCase = null;
+  }
+
+  /**
+   * 管理操作成功回调
+   */
+  async handleManagementSuccess(result: any) {
+    console.log('案例操作成功:', result);
     
-    // 实际项目中应发送到后端API
-    console.log('记录用户行为:', behaviorData);
+    if (this.editingCase) {
+      this.showToast('案例更新成功', 'success');
+    } else {
+      this.showToast('案例创建成功', 'success');
+    }
     
-    // 模拟存储到本地(实际应发送到服务器)
-    const behaviors = JSON.parse(localStorage.getItem('caseBehaviors') || '[]');
-    behaviors.push(behaviorData);
-    localStorage.setItem('caseBehaviors', JSON.stringify(behaviors));
+    // 刷新列表
+    await this.loadCases();
+  }
+
+  /**
+   * 删除案例
+   */
+  async deleteCase(caseItem: Case) {
+    if (!confirm(`确定要删除案例"${caseItem.name}"吗?此操作不可恢复。`)) {
+      return;
+    }
+
+    try {
+      const success = await this.caseService.deleteCase(caseItem.id);
+      
+      if (success) {
+        this.showToast('案例删除成功', 'success');
+        // 刷新列表
+        await this.loadCases();
+      } else {
+        this.showToast('案例删除失败', 'error');
+      }
+    } catch (error) {
+      console.error('删除案例失败:', error);
+      this.showToast('删除案例失败: ' + (error as Error).message, 'error');
+    }
   }
 
-  // 新增:显示提示消息
+  // 显示提示消息
   private showToast(message: string, type: 'success' | 'error' | 'info' = 'info') {
     // 实际项目中应使用专业的Toast组件
     const toast = document.createElement('div');

+ 580 - 0
src/app/pages/customer-service/case-library/case-management-modal.component.ts

@@ -0,0 +1,580 @@
+import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { CaseService } from '../../../services/case.service';
+
+/**
+ * 案例管理模态框组件
+ * 用于创建和编辑案例
+ */
+@Component({
+  selector: 'app-case-management-modal',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  template: `
+    @if (isVisible) {
+      <div class="modal-overlay" (click)="closeModal()">
+        <div class="modal-content" (click)="$event.stopPropagation()">
+          <!-- 模态框头部 -->
+          <div class="modal-header">
+            <h2>{{ isEditMode ? '编辑案例' : '创建案例' }}</h2>
+            <button class="close-btn" (click)="closeModal()">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <line x1="18" y1="6" x2="6" y2="18"></line>
+                <line x1="6" y1="6" x2="18" y2="18"></line>
+              </svg>
+            </button>
+          </div>
+
+          <!-- 模态框内容 -->
+          <div class="modal-body">
+            <form (ngSubmit)="handleSubmit()" #caseForm="ngForm">
+              <!-- 基础信息 -->
+              <div class="form-section">
+                <h3>基础信息</h3>
+                
+                <div class="form-group">
+                  <label for="name">案例名称 <span class="required">*</span></label>
+                  <input 
+                    type="text" 
+                    id="name" 
+                    [(ngModel)]="formData.name" 
+                    name="name"
+                    required
+                    placeholder="请输入案例名称"
+                  />
+                </div>
+
+                <div class="form-row">
+                  <div class="form-group">
+                    <label for="projectType">项目类型 <span class="required">*</span></label>
+                    <select id="projectType" [(ngModel)]="formData.info.projectType" name="projectType" required>
+                      <option value="">请选择</option>
+                      <option value="工装">工装</option>
+                      <option value="家装">家装</option>
+                    </select>
+                  </div>
+
+                  <div class="form-group">
+                    <label for="roomType">户型 <span class="required">*</span></label>
+                    <select id="roomType" [(ngModel)]="formData.info.roomType" name="roomType" required>
+                      <option value="">请选择</option>
+                      <option value="一居室">一居室</option>
+                      <option value="二居室">二居室</option>
+                      <option value="三居室">三居室</option>
+                      <option value="四居+">四居+</option>
+                    </select>
+                  </div>
+                </div>
+
+                <div class="form-row">
+                  <div class="form-group">
+                    <label for="spaceType">空间类型 <span class="required">*</span></label>
+                    <select id="spaceType" [(ngModel)]="formData.info.spaceType" name="spaceType" required>
+                      <option value="">请选择</option>
+                      <option value="平层">平层</option>
+                      <option value="复式">复式</option>
+                      <option value="别墅">别墅</option>
+                      <option value="自建房">自建房</option>
+                    </select>
+                  </div>
+
+                  <div class="form-group">
+                    <label for="renderingLevel">渲染水平 <span class="required">*</span></label>
+                    <select id="renderingLevel" [(ngModel)]="formData.info.renderingLevel" name="renderingLevel" required>
+                      <option value="">请选择</option>
+                      <option value="高端">高端</option>
+                      <option value="中端">中端</option>
+                      <option value="低端">低端</option>
+                    </select>
+                  </div>
+                </div>
+
+                <div class="form-row">
+                  <div class="form-group">
+                    <label for="area">面积(㎡) <span class="required">*</span></label>
+                    <input 
+                      type="number" 
+                      id="area" 
+                      [(ngModel)]="formData.info.area" 
+                      name="area"
+                      required
+                      min="0"
+                      placeholder="请输入面积"
+                    />
+                  </div>
+
+                  <div class="form-group">
+                    <label for="totalPrice">案例总额(元) <span class="required">*</span></label>
+                    <input 
+                      type="number" 
+                      id="totalPrice" 
+                      [(ngModel)]="formData.totalPrice" 
+                      name="totalPrice"
+                      required
+                      min="0"
+                      placeholder="请输入总额"
+                    />
+                  </div>
+                </div>
+
+                <div class="form-group">
+                  <label for="completionDate">完工时间 <span class="required">*</span></label>
+                  <input 
+                    type="date" 
+                    id="completionDate" 
+                    [(ngModel)]="completionDateString" 
+                    name="completionDate"
+                    required
+                  />
+                </div>
+
+                <div class="form-group">
+                  <label for="tags">风格标签 <span class="required">*</span></label>
+                  <input 
+                    type="text" 
+                    id="tags" 
+                    [(ngModel)]="tagsString" 
+                    name="tags"
+                    placeholder="多个标签用逗号分隔,如:现代,简约,轻奢"
+                  />
+                  <small class="form-hint">请用逗号分隔多个标签</small>
+                </div>
+
+                <div class="form-group">
+                  <label for="coverImage">封面图URL <span class="required">*</span></label>
+                  <input 
+                    type="text" 
+                    id="coverImage" 
+                    [(ngModel)]="formData.coverImage" 
+                    name="coverImage"
+                    required
+                    placeholder="请输入封面图URL"
+                  />
+                </div>
+
+                <div class="form-group">
+                  <label for="customerReview">客户评价</label>
+                  <textarea 
+                    id="customerReview" 
+                    [(ngModel)]="formData.customerReview" 
+                    name="customerReview"
+                    rows="3"
+                    placeholder="请输入客户评价"
+                  ></textarea>
+                </div>
+              </div>
+
+              <!-- 状态设置 -->
+              <div class="form-section">
+                <h3>状态设置</h3>
+                
+                <div class="form-group checkbox-group">
+                  <label>
+                    <input 
+                      type="checkbox" 
+                      [(ngModel)]="formData.isPublished" 
+                      name="isPublished"
+                    />
+                    <span>发布案例</span>
+                  </label>
+                </div>
+
+                <div class="form-group checkbox-group">
+                  <label>
+                    <input 
+                      type="checkbox" 
+                      [(ngModel)]="formData.isExcellent" 
+                      name="isExcellent"
+                    />
+                    <span>标记为优秀案例</span>
+                  </label>
+                </div>
+
+                <div class="form-group">
+                  <label for="index">展示排序</label>
+                  <input 
+                    type="number" 
+                    id="index" 
+                    [(ngModel)]="formData.index" 
+                    name="index"
+                    min="0"
+                    placeholder="数字越小越靠前"
+                  />
+                  <small class="form-hint">数字越小排序越靠前</small>
+                </div>
+              </div>
+
+              <!-- 提交按钮 -->
+              <div class="modal-footer">
+                <button type="button" class="btn-secondary" (click)="closeModal()">
+                  取消
+                </button>
+                <button type="submit" class="btn-primary" [disabled]="!caseForm.form.valid || submitting">
+                  {{ submitting ? '提交中...' : (isEditMode ? '保存' : '创建') }}
+                </button>
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+    }
+  `,
+  styles: [`
+    .modal-overlay {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0, 0, 0, 0.5);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      z-index: 1000;
+      padding: 20px;
+    }
+
+    .modal-content {
+      background: white;
+      border-radius: 12px;
+      width: 100%;
+      max-width: 800px;
+      max-height: 90vh;
+      overflow-y: auto;
+      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+    }
+
+    .modal-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 24px;
+      border-bottom: 1px solid #e5e7eb;
+    }
+
+    .modal-header h2 {
+      margin: 0;
+      font-size: 20px;
+      font-weight: 600;
+      color: #111827;
+    }
+
+    .close-btn {
+      background: none;
+      border: none;
+      cursor: pointer;
+      padding: 4px;
+      color: #6b7280;
+      transition: color 0.2s;
+    }
+
+    .close-btn:hover {
+      color: #111827;
+    }
+
+    .modal-body {
+      padding: 24px;
+    }
+
+    .form-section {
+      margin-bottom: 32px;
+    }
+
+    .form-section h3 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #374151;
+    }
+
+    .form-group {
+      margin-bottom: 20px;
+    }
+
+    .form-row {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 16px;
+    }
+
+    label {
+      display: block;
+      margin-bottom: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #374151;
+    }
+
+    .required {
+      color: #ef4444;
+    }
+
+    input[type="text"],
+    input[type="number"],
+    input[type="date"],
+    select,
+    textarea {
+      width: 100%;
+      padding: 10px 12px;
+      border: 1px solid #d1d5db;
+      border-radius: 6px;
+      font-size: 14px;
+      transition: border-color 0.2s;
+    }
+
+    input:focus,
+    select:focus,
+    textarea:focus {
+      outline: none;
+      border-color: #3b82f6;
+    }
+
+    .form-hint {
+      display: block;
+      margin-top: 4px;
+      font-size: 12px;
+      color: #6b7280;
+    }
+
+    .checkbox-group label {
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+    }
+
+    .checkbox-group input[type="checkbox"] {
+      width: auto;
+      margin-right: 8px;
+    }
+
+    .modal-footer {
+      display: flex;
+      justify-content: flex-end;
+      gap: 12px;
+      padding-top: 24px;
+      border-top: 1px solid #e5e7eb;
+    }
+
+    .btn-secondary,
+    .btn-primary {
+      padding: 10px 20px;
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s;
+      border: none;
+    }
+
+    .btn-secondary {
+      background: #f3f4f6;
+      color: #374151;
+    }
+
+    .btn-secondary:hover {
+      background: #e5e7eb;
+    }
+
+    .btn-primary {
+      background: #3b82f6;
+      color: white;
+    }
+
+    .btn-primary:hover:not(:disabled) {
+      background: #2563eb;
+    }
+
+    .btn-primary:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  `]
+})
+export class CaseManagementModalComponent implements OnInit {
+  @Input() isVisible = false;
+  @Input() editingCase: any = null;
+  @Output() close = new EventEmitter<void>();
+  @Output() success = new EventEmitter<any>();
+
+  isEditMode = false;
+  submitting = false;
+
+  // 表单数据
+  formData = {
+    name: '',
+    coverImage: '',
+    images: [] as string[],
+    totalPrice: 0,
+    tag: [] as string[],
+    info: {
+      area: 0,
+      projectType: '' as '工装' | '家装' | '',
+      roomType: '' as '一居室' | '二居室' | '三居室' | '四居+' | '',
+      spaceType: '' as '平层' | '复式' | '别墅' | '自建房' | '',
+      renderingLevel: '' as '高端' | '中端' | '低端' | ''
+    },
+    isPublished: false,
+    isExcellent: false,
+    index: 0,
+    customerReview: '',
+    projectId: '',
+    designerId: '',
+    teamId: '',
+    completionDate: new Date()
+  };
+
+  // 辅助字段(用于表单绑定)
+  completionDateString = '';
+  tagsString = '';
+
+  constructor(private caseService: CaseService) {}
+
+  ngOnInit() {
+    // 如果是编辑模式,初始化表单数据
+    if (this.editingCase) {
+      this.isEditMode = true;
+      this.initFormData();
+    }
+  }
+
+  /**
+   * 初始化表单数据(编辑模式)
+   */
+  initFormData() {
+    if (!this.editingCase) return;
+
+    this.formData = {
+      name: this.editingCase.name || '',
+      coverImage: this.editingCase.coverImage || '',
+      images: this.editingCase.images || [],
+      totalPrice: this.editingCase.totalPrice || 0,
+      tag: this.editingCase.tag || [],
+      info: {
+        area: this.editingCase.area || 0,
+        projectType: this.editingCase.projectType || '',
+        roomType: this.editingCase.roomType || '',
+        spaceType: this.editingCase.spaceType || '',
+        renderingLevel: this.editingCase.renderingLevel || ''
+      },
+      isPublished: this.editingCase.isPublished || false,
+      isExcellent: this.editingCase.isExcellent || false,
+      index: this.editingCase.index || 0,
+      customerReview: this.editingCase.customerReview || '',
+      projectId: this.editingCase.projectId || '',
+      designerId: this.editingCase.designerId || '',
+      teamId: this.editingCase.teamId || '',
+      completionDate: this.editingCase.completionDate || new Date()
+    };
+
+    // 转换日期为字符串
+    if (this.editingCase.completionDate) {
+      const date = new Date(this.editingCase.completionDate);
+      this.completionDateString = date.toISOString().split('T')[0];
+    }
+
+    // 转换标签数组为字符串
+    this.tagsString = this.formData.tag.join(',');
+  }
+
+  /**
+   * 处理表单提交
+   */
+  async handleSubmit() {
+    if (this.submitting) return;
+
+    this.submitting = true;
+
+    try {
+      // 转换辅助字段
+      this.formData.completionDate = new Date(this.completionDateString);
+      this.formData.tag = this.tagsString.split(',').map(t => t.trim()).filter(t => t);
+      this.formData.images = [this.formData.coverImage]; // 暂时只用封面图
+
+      // 临时数据(实际应该从项目选择器获取)
+      if (!this.formData.projectId) {
+        this.formData.projectId = 'temp_project_id';
+      }
+      if (!this.formData.designerId) {
+        this.formData.designerId = 'temp_designer_id';
+      }
+      if (!this.formData.teamId) {
+        this.formData.teamId = 'temp_team_id';
+      }
+
+      let result;
+      if (this.isEditMode && this.editingCase) {
+        // 更新案例 - 构建更新数据,排除空值
+        const updateData: any = {
+          name: this.formData.name,
+          coverImage: this.formData.coverImage,
+          images: this.formData.images,
+          totalPrice: this.formData.totalPrice,
+          completionDate: this.formData.completionDate,
+          tag: this.formData.tag,
+          isPublished: this.formData.isPublished,
+          isExcellent: this.formData.isExcellent,
+          index: this.formData.index
+        };
+
+        // 只在有值时添加 info 字段
+        if (this.formData.info.projectType && this.formData.info.roomType && 
+            this.formData.info.spaceType && this.formData.info.renderingLevel) {
+          updateData.info = {
+            area: this.formData.info.area,
+            projectType: this.formData.info.projectType as '工装' | '家装',
+            roomType: this.formData.info.roomType as '一居室' | '二居室' | '三居室' | '四居+',
+            spaceType: this.formData.info.spaceType as '平层' | '复式' | '别墅' | '自建房',
+            renderingLevel: this.formData.info.renderingLevel as '高端' | '中端' | '低端'
+          };
+        }
+
+        if (this.formData.customerReview) {
+          updateData.customerReview = this.formData.customerReview;
+        }
+
+        result = await this.caseService.updateCase(this.editingCase.id, updateData);
+      } else {
+        // 创建案例 - 确保所有必填字段都有值
+        if (!this.formData.info.projectType || !this.formData.info.roomType || 
+            !this.formData.info.spaceType || !this.formData.info.renderingLevel) {
+          alert('请填写完整的分类信息');
+          return;
+        }
+
+        const createData = {
+          ...this.formData,
+          info: {
+            area: this.formData.info.area,
+            projectType: this.formData.info.projectType as '工装' | '家装',
+            roomType: this.formData.info.roomType as '一居室' | '二居室' | '三居室' | '四居+',
+            spaceType: this.formData.info.spaceType as '平层' | '复式' | '别墅' | '自建房',
+            renderingLevel: this.formData.info.renderingLevel as '高端' | '中端' | '低端'
+          }
+        };
+
+        result = await this.caseService.createCase(createData);
+      }
+
+      if (result) {
+        this.success.emit(result);
+        this.closeModal();
+      } else {
+        alert('操作失败,请重试');
+      }
+    } catch (error) {
+      console.error('提交失败:', error);
+      alert('操作失败: ' + (error as Error).message);
+    } finally {
+      this.submitting = false;
+    }
+  }
+
+  /**
+   * 关闭模态框
+   */
+  closeModal() {
+    this.close.emit();
+  }
+}
+

+ 318 - 0
src/app/pages/customer-service/dashboard/dashboard-urgent-tasks-enhanced.scss

@@ -0,0 +1,318 @@
+// 增强版紧急待办样式
+// 使用内联变量定义,不依赖外部文件
+
+/* 增强版任务项目样式 */
+.task-item-enhanced {
+  display: flex;
+  flex-direction: column;
+  background: white;
+  border-radius: 16px;
+  overflow: hidden;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  border: 1px solid #e5e7eb;
+  position: relative;
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+    border-color: #d1d5db;
+  }
+  
+  &.completed {
+    background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
+    border-color: #bae6fd;
+    opacity: 0.8;
+    
+    .task-title {
+      text-decoration: line-through;
+      color: #64748b;
+    }
+  }
+  
+  &.overdue {
+    background: linear-gradient(135deg, #fff1f2, #ffe4e6);
+    border-color: #fecaca;
+  }
+}
+
+/* 优先级指示器 */
+.task-priority-indicator {
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 4px;
+  
+  &.high {
+    background: linear-gradient(180deg, #ef4444, #dc2626);
+  }
+  
+  &.medium {
+    background: linear-gradient(180deg, #f59e0b, #d97706);
+  }
+  
+  &.low {
+    background: linear-gradient(180deg, #10b981, #059669);
+  }
+}
+
+/* 任务主要内容 */
+.task-main-content {
+  padding: 20px 20px 16px 24px;
+  flex: 1;
+}
+
+/* 任务头部行 */
+.task-header-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  margin-bottom: 8px;
+}
+
+/* 任务复选框 */
+.task-item-enhanced .task-checkbox {
+  padding-top: 2px;
+  
+  input[type="checkbox"] {
+    width: 20px;
+    height: 20px;
+    accent-color: #3b82f6;
+    border-radius: 6px;
+    cursor: pointer;
+    transition: all 0.2s;
+    
+    &:hover {
+      transform: scale(1.1);
+    }
+  }
+}
+
+/* 任务信息 */
+.task-info {
+  flex: 1;
+}
+
+/* 任务标题 */
+.task-item-enhanced .task-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+  margin: 0 0 10px 0;
+  line-height: 1.4;
+}
+
+/* 任务标签组 */
+.task-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-top: 8px;
+}
+
+.tag {
+  display: inline-flex;
+  align-items: center;
+  padding: 4px 10px;
+  border-radius: 6px;
+  font-size: 13px;
+  font-weight: 500;
+  background: #f3f4f6;
+  color: #4b5563;
+  
+  &.tag-project {
+    background: #dbeafe;
+    color: #1e40af;
+  }
+  
+  &.tag-stage {
+    background: #fef3c7;
+    color: #92400e;
+  }
+  
+  &.tag-assignee {
+    background: #e0e7ff;
+    color: #4338ca;
+  }
+}
+
+/* 任务描述 */
+.task-description {
+  margin: 12px 0;
+  padding: 12px;
+  background: #f9fafb;
+  border-radius: 8px;
+  border-left: 3px solid #e5e7eb;
+  
+  p {
+    margin: 0;
+    font-size: 14px;
+    color: #6b7280;
+    line-height: 1.6;
+  }
+}
+
+/* 任务元信息行 */
+.task-meta-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid #f3f4f6;
+}
+
+/* 任务元信息 */
+.task-meta-info {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  color: #6b7280;
+  font-size: 14px;
+  
+  svg {
+    flex-shrink: 0;
+    stroke-width: 2;
+  }
+}
+
+.task-time {
+  font-weight: 500;
+  
+  &.overdue {
+    color: #dc2626;
+    font-weight: 600;
+  }
+}
+
+.overdue-badge {
+  display: inline-block;
+  margin-left: 6px;
+  padding: 2px 8px;
+  background: #fee2e2;
+  color: #dc2626;
+  font-size: 12px;
+  font-weight: 600;
+  border-radius: 4px;
+}
+
+/* 优先级徽章 */
+.task-priority-badge {
+  padding: 4px 12px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 600;
+  
+  &.high {
+    background: #fee2e2;
+    color: #dc2626;
+  }
+  
+  &.medium {
+    background: #fef3c7;
+    color: #d97706;
+  }
+  
+  &.low {
+    background: #d1fae5;
+    color: #059669;
+  }
+}
+
+/* 任务操作行 */
+.task-actions-row {
+  display: flex;
+  gap: 8px;
+  padding: 12px 20px 12px 24px;
+  background: #f9fafb;
+  border-top: 1px solid #f3f4f6;
+}
+
+/* 操作按钮 */
+.btn-action {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 16px;
+  border: none;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+  
+  svg {
+    flex-shrink: 0;
+    stroke-width: 2;
+  }
+  
+  &:hover:not(:disabled) {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  }
+  
+  &:active:not(:disabled) {
+    transform: translateY(0);
+  }
+  
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+.btn-process {
+  background: linear-gradient(135deg, #3b82f6, #2563eb);
+  color: white;
+  flex: 1;
+  
+  &:hover:not(:disabled) {
+    background: linear-gradient(135deg, #2563eb, #1d4ed8);
+  }
+  
+  &.processing {
+    background: linear-gradient(135deg, #6b7280, #4b5563);
+  }
+}
+
+.btn-delete {
+  background: white;
+  color: #ef4444;
+  border: 1px solid #fecaca;
+  padding: 8px 12px;
+  
+  &:hover:not(:disabled) {
+    background: #fef2f2;
+    border-color: #fca5a5;
+  }
+}
+
+// 任务进度条
+.task-progress-container {
+  margin-top: 12px;
+  background: #e5e7eb;
+  border-radius: 8px;
+  overflow: hidden;
+  height: 28px;
+  position: relative;
+}
+
+.task-progress-bar {
+  height: 100%;
+  background: linear-gradient(90deg, #3b82f6, #2563eb);
+  transition: width 0.3s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.task-progress-text {
+  color: white;
+  font-size: 12px;
+  font-weight: 600;
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+}
+

+ 130 - 35
src/app/pages/customer-service/dashboard/dashboard.html

@@ -258,16 +258,48 @@
       }
       
       @for (task of urgentTasks(); track task.id) {
-      <div class="task-item" [class.completed]="task.isCompleted" [class.overdue]="task.isOverdue">
-        <div class="task-checkbox">
-          <input type="checkbox" [checked]="task.isCompleted" (change)="markTaskAsCompleted(task.id)">
-        </div>
-        <div class="task-content">
-          <h4 class="task-title">{{ task.title || '未命名任务' }}</h4>
-          <p class="task-project">{{ task.projectName || '未知项目' }}</p>
-          <div class="task-meta">
-            <span class="task-time">{{ formatDate(task.deadline) }}</span>
-            <span class="task-status">{{ getTaskStatus(task) }}</span>
+      <div class="task-item-enhanced" [class.completed]="task.isCompleted" [class.overdue]="task.isOverdue">
+        <div class="task-priority-indicator" [class.high]="task.priority === 'high'" [class.medium]="task.priority === 'medium'" [class.low]="task.priority === 'low'"></div>
+        
+        <div class="task-main-content">
+          <div class="task-header-row">
+            <div class="task-checkbox">
+              <input type="checkbox" [checked]="task.isCompleted" (change)="markTaskAsCompleted(task.id)">
+            </div>
+            <div class="task-info">
+              <h4 class="task-title">{{ task.title || '未命名任务' }}</h4>
+              <div class="task-tags">
+                <span class="tag tag-project">📋 {{ task.projectName || '未知项目' }}</span>
+                <span class="tag tag-stage">🔄 {{ task.stage }}</span>
+                @if (task.assignee && task.assignee !== '未分配') {
+                  <span class="tag tag-assignee">👤 {{ task.assignee }}</span>
+                }
+              </div>
+            </div>
+          </div>
+          
+          @if (task.description) {
+            <div class="task-description">
+              <p>{{ task.description }}</p>
+            </div>
+          }
+          
+          <div class="task-meta-row">
+            <div class="task-meta-info">
+              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <circle cx="12" cy="12" r="10"></circle>
+                <polyline points="12 6 12 12 16 14"></polyline>
+              </svg>
+              <span class="task-time" [class.overdue]="task.isOverdue">
+                {{ formatDate(task.deadline) }}
+                @if (task.isOverdue) {
+                  <span class="overdue-badge">已逾期</span>
+                }
+              </span>
+            </div>
+            <div class="task-priority-badge" [class.high]="task.priority === 'high'" [class.medium]="task.priority === 'medium'" [class.low]="task.priority === 'low'">
+              {{ task.priority === 'high' ? '高优先级' : task.priority === 'medium' ? '中优先级' : '低优先级' }}
+            </div>
           </div>
           
           <!-- 任务处理状态(使用本地变量 s 消除可选链告警) -->
@@ -275,27 +307,45 @@
             <ng-container *ngIf="s.inProgress === true">
               <div class="task-progress-container">
                 <div class="task-progress-bar" [style.width]="s.progress + '%'">
-                  <span class="task-progress-text">{{ s.progress }}%</span>
+                  <span class="task-progress-text">处理中 {{ s.progress }}%</span>
                 </div>
               </div>
             </ng-container>
           </ng-container>
         </div>
-            <div class="task-actions">
+        
+        <div class="task-actions-row">
           <ng-container *ngIf="taskProcessingState()[task.id] as s; else noTaskActionState">
             <button 
-              class="btn-primary"
+              class="btn-action btn-process"
               [class.processing]="s.inProgress === true"
               (click)="handleAssignment(task.id)"
               [disabled]="(s.inProgress === true) || task.isCompleted"
             >
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <polyline points="9 11 12 14 22 4"></polyline>
+                <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
+              </svg>
               {{ s.inProgress === true ? '处理中' : '处理' }}
             </button>
           </ng-container>
           <ng-template #noTaskActionState>
-            <button class="btn-primary" (click)="handleAssignment(task.id)" [disabled]="task.isCompleted">处理</button>
+            <button class="btn-action btn-process" (click)="handleAssignment(task.id)" [disabled]="task.isCompleted">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <polyline points="9 11 12 14 22 4"></polyline>
+                <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
+              </svg>
+              处理
+            </button>
           </ng-template>
-          </div>
+          
+          <button class="btn-action btn-delete" (click)="deleteTask(task.id)" title="删除任务">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="3 6 5 6 21 6"></polyline>
+              <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
+            </svg>
+          </button>
+        </div>
       </div>
       }
     </div>
@@ -331,16 +381,36 @@
           </div>
           
           <div class="form-group">
-            <label for="projectName">项目名称 *</label>
-            <input 
-              type="text" 
-              id="projectName" 
-              [(ngModel)]="newTask.projectName" 
+            <label for="projectSelect">选择项目 *</label>
+            <select 
+              id="projectSelect" 
+              [(ngModel)]="newTask.projectId" 
               [ngModelOptions]="{standalone: true}"
-              placeholder="请输入项目名称"
+              (change)="onProjectChange($any($event.target).value)"
               required
-              class="ios-input"
+              class="ios-select"
             >
+              <option value="">-- 请选择项目 --</option>
+              @for (project of projectList(); track project.id) {
+                <option [value]="project.id">{{ project.title }}</option>
+              }
+            </select>
+          </div>
+          
+          <div class="form-group">
+            <label for="spaceSelect">选择空间(可选)</label>
+            <select 
+              id="spaceSelect" 
+              [(ngModel)]="newTask.spaceId" 
+              [ngModelOptions]="{standalone: true}"
+              class="ios-select"
+              [disabled]="!newTask.projectId"
+            >
+              <option value="">-- 请选择空间 --</option>
+              @for (space of spaceList(); track space.id) {
+                <option [value]="space.id">{{ space.title }}</option>
+              }
+            </select>
           </div>
           
           <div class="form-group">
@@ -352,15 +422,25 @@
               required
               class="ios-select"
             >
-              <option value="前期沟通">前期沟通</option>
-              <option value="建模">建模</option>
-              <option value="软装">软装</option>
-              <option value="渲染">渲染</option>
-              <option value="后期">后期</option>
-              <option value="完成">完成</option>
+              <option value="订单分配">订单分配</option>
+              <option value="慎设需求">慎设需求</option>
+              <option value="交付执行">交付执行</option>
+              <option value="售后">售后</option>
             </select>
           </div>
           
+          <div class="form-group">
+            <label for="region">区域/位置(可选)</label>
+            <input 
+              type="text" 
+              id="region" 
+              [(ngModel)]="newTask.region" 
+              [ngModelOptions]="{standalone: true}"
+              placeholder="例如:客厅、主卧、厨房"
+              class="ios-input"
+            >
+          </div>
+          
           <div class="form-group">
             <label for="taskDeadline">截止时间 *</label>
             
@@ -448,27 +528,42 @@
           }
           
           <div class="form-group">
-            <label for="taskPriority">优先级</label>
+            <label for="assigneeSelect">指派给(可选)</label>
+            <select 
+              id="assigneeSelect" 
+              [(ngModel)]="newTask.assigneeId" 
+              [ngModelOptions]="{standalone: true}"
+              class="ios-select"
+            >
+              <option value="">-- 暂不指派 --</option>
+              @for (member of teamMembers(); track member.id) {
+                <option [value]="member.id">{{ member.name }}{{ member.roleName ? ' (' + member.roleName + ')' : '' }}</option>
+              }
+            </select>
+          </div>
+          
+          <div class="form-group">
+            <label for="taskPriority">优先级 *</label>
             <select 
               id="taskPriority" 
               [(ngModel)]="newTask.priority" 
               [ngModelOptions]="{standalone: true}"
               class="ios-select"
             >
-              <option value="high">高</option>
-              <option value="medium">中</option>
-              <option value="low">低</option>
+              <option value="high">🔴 优先级(紧急)</option>
+              <option value="medium">🟡 优先级(普通)</option>
+              <option value="low">🟢 优先级</option>
             </select>
           </div>
           
           <div class="form-group">
-            <label for="taskDescription">任务描述</label>
+            <label for="taskDescription">详细描述(可选)</label>
             <textarea 
               id="taskDescription" 
               [(ngModel)]="newTask.description" 
               [ngModelOptions]="{standalone: true}"
-              placeholder="请输入任务描述"
-              rows="3"
+              placeholder="请详细描述需要紧急处理的问题..."
+              rows="4"
               class="ios-textarea"
             ></textarea>
           </div>

+ 249 - 1
src/app/pages/customer-service/dashboard/dashboard.scss

@@ -2920,12 +2920,256 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
   }
 }
 
+@media (max-width: 768px) {
+  // 客服工作台容器
+  .customer-service-dashboard {
+    padding: 12px;
+  }
+
+  // 欢迎区域
+  .welcome-section {
+    padding: 16px;
+
+    h1 {
+      font-size: 22px;
+    }
+
+    .welcome-subtitle {
+      font-size: 13px;
+    }
+  }
+
+  // 统计卡片
+  .stats-dashboard {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+
+    .stat-card {
+      padding: 16px;
+
+      .stat-value {
+        font-size: 24px;
+      }
+
+      .stat-label {
+        font-size: 12px;
+      }
+    }
+  }
+
+  // CRM队列
+  .crm-queues {
+    .queue-grid {
+      grid-template-columns: 1fr;
+      gap: 12px;
+    }
+
+    .queue-card {
+      .card-header h3 {
+        font-size: 16px;
+      }
+
+      .crm-item {
+        padding: 12px;
+
+        .avatar {
+          width: 40px;
+          height: 40px;
+        }
+
+        .info {
+          .name {
+            font-size: 14px;
+          }
+
+          .meta {
+            font-size: 12px;
+            flex-wrap: wrap;
+          }
+        }
+      }
+    }
+  }
+
+  // 紧急待办面板
+  .tasks-panel,
+  .ios-panel {
+    width: 100vw;
+    max-width: 100vw;
+    height: 90vh;
+    max-height: 90vh;
+    border-radius: 16px 16px 0 0;
+    bottom: 0;
+    top: auto;
+
+    .panel-header {
+      padding: 16px;
+
+      h2 {
+        font-size: 18px;
+      }
+    }
+
+    .panel-body {
+      padding: 16px;
+
+      .form-group {
+        margin-bottom: 16px;
+
+        label {
+          font-size: 14px;
+        }
+
+        input,
+        select,
+        textarea {
+          font-size: 16px; // 防止iOS自动缩放
+          padding: 12px;
+        }
+      }
+    }
+
+    .panel-footer {
+      padding: 16px;
+      flex-direction: column;
+      gap: 12px;
+
+      button {
+        width: 100%;
+      }
+    }
+  }
+
+  // 任务列表
+  .tasks-list {
+    gap: 12px;
+
+    .task-item,
+    .task-item-enhanced {
+      padding: 14px;
+
+      .task-header,
+      .task-header-row {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 10px;
+
+        .task-title {
+          font-size: 15px;
+        }
+
+        .task-priority {
+          font-size: 11px;
+          padding: 3px 8px;
+        }
+      }
+
+      .task-info,
+      .task-main-content {
+        .task-detail {
+          font-size: 13px;
+        }
+
+        .task-tags {
+          flex-wrap: wrap;
+          gap: 6px;
+
+          .tag {
+            font-size: 11px;
+            padding: 3px 8px;
+          }
+        }
+      }
+
+      .task-actions,
+      .task-actions-row {
+        flex-direction: column;
+        gap: 8px;
+        margin-top: 12px;
+
+        button {
+          width: 100%;
+          justify-content: center;
+        }
+      }
+    }
+  }
+
+  // 快速操作按钮
+  .quick-actions {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+
+    .action-btn {
+      padding: 16px;
+
+      .action-icon {
+        width: 40px;
+        height: 40px;
+      }
+
+      .action-label {
+        font-size: 13px;
+      }
+    }
+  }
+
+  // 日历视图
+  .calendar-view {
+    .calendar-header {
+      flex-direction: column;
+      gap: 12px;
+
+      .month-selector {
+        width: 100%;
+        justify-content: space-between;
+      }
+    }
+
+    .calendar-grid {
+      .calendar-day {
+        min-height: 60px;
+        padding: 4px;
+
+        .day-number {
+          font-size: 12px;
+        }
+
+        .event-dot {
+          width: 4px;
+          height: 4px;
+        }
+      }
+    }
+  }
+}
+
 @media (max-width: 480px) {
+  .customer-service-dashboard {
+    padding: 8px;
+  }
+
   .welcome-section {
     padding: 12px 16px;
+
+    h1 {
+      font-size: 20px;
+    }
+  }
+
+  .stats-dashboard {
+    grid-template-columns: 1fr;
+    margin-bottom: 16px;
+
+    .stat-card {
+      padding: 14px;
+
+      .stat-value {
+        font-size: 22px;
+      }
+    }
   }
 
-  .stats-dashboard,
   .crm-queues {
     margin-bottom: 16px;
   }
@@ -2946,4 +3190,8 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
       gap: 6px;
     }
   }
+
+  .quick-actions {
+    grid-template-columns: 1fr;
+  }
 }

+ 194 - 126
src/app/pages/customer-service/dashboard/dashboard.ts

@@ -4,6 +4,8 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { RouterModule, Router, ActivatedRoute } from '@angular/router';
 import { ProfileService } from '../../../services/profile.service';
+import { UrgentTaskService } from '../../../services/urgent-task.service';
+import { ActivityLogService } from '../../../services/activity-log.service';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
 
 const Parse = FmodeParse.with('nova');
@@ -85,7 +87,7 @@ interface CustomerFeedback {
   standalone: true,
   imports: [CommonModule, FormsModule, RouterModule],
   templateUrl: './dashboard.html',
-  styleUrls: ['./dashboard.scss', '../customer-service-styles.scss']
+  styleUrls: ['./dashboard.scss', './dashboard-urgent-tasks-enhanced.scss', '../customer-service-styles.scss']
 }) 
 export class Dashboard implements OnInit, OnDestroy {
   // 数据看板统计
@@ -148,20 +150,26 @@ export class Dashboard implements OnInit, OnDestroy {
   // 任务表单可见性
   isTaskFormVisible = signal(false);
   
+  // 项目列表(用于下拉选择)
+  projectList = signal<any[]>([]);
+  
+  // 空间列表(用于下拉选择)
+  spaceList = signal<any[]>([]);
+  
+  // 团队成员列表(用于指派)
+  teamMembers = signal<any[]>([]);
+  
   // 新任务数据
-  newTask: Task = {
-    id: '',
-    projectId: '',
-    projectName: '',
+  newTask: any = {
     title: '',
-    stage: '需求沟通',
-    deadline: new Date(),
-    isOverdue: false,
-    isCompleted: false,
-    priority: 'high',
-    assignee: '当前用户',
     description: '',
-    status: '待处理'
+    projectId: '',
+    spaceId: '',
+    stage: '订单分配',
+    region: '',
+    priority: 'high',
+    assigneeId: '',
+    deadline: new Date()
   };
   
   // 用于日期时间输入的属性
@@ -210,7 +218,9 @@ export class Dashboard implements OnInit, OnDestroy {
   constructor(
     private router: Router,
     private route: ActivatedRoute,
-    private profileService: ProfileService
+    private profileService: ProfileService,
+    private urgentTaskService: UrgentTaskService,
+    private activityLogService: ActivityLogService
   ) {}
 
   // 当前用户和公司信息
@@ -371,83 +381,32 @@ export class Dashboard implements OnInit, OnDestroy {
   // 加载紧急任务
   private async loadUrgentTasks(): Promise<void> {
     try {
-      const now = new Date();
-      const todayEnd = new Date();
-      todayEnd.setHours(23, 59, 59, 999);
-      const urgentTasks: Task[] = [];
-
-      // 1. 查询今日截止的项目
-      const todayDeadlineQuery = this.createQuery('Project');
-      todayDeadlineQuery.equalTo('status', '进行中');
-      todayDeadlineQuery.greaterThanOrEqualTo('deadline', now);
-      todayDeadlineQuery.lessThanOrEqualTo('deadline', todayEnd);
-      todayDeadlineQuery.include(['contact', 'assignee']);
-      todayDeadlineQuery.limit(10);
-      const todayProjects = await todayDeadlineQuery.find();
-
-      for (const project of todayProjects) {
-        const contact = project.get('contact');
-        const assignee = project.get('assignee');
-        urgentTasks.push({
-          id: project.id,
-          projectId: project.id,
-          projectName: project.get('title') || '未命名项目',
-          title: `项目截止:${project.get('title')}`,
-          stage: project.get('currentStage') || '进行中',
-          deadline: project.get('deadline'),
-          isOverdue: false,
-          isCompleted: false,
-          priority: 'high',
-          assignee: assignee?.get('name') || '未分配',
-          description: `客户:${contact?.get('name') || '未知'}`,
-          status: '进行中'
-        });
-      }
-
-      // 2. 查询逾期项目
-      const overdueQuery = this.createQuery('Project');
-      overdueQuery.equalTo('status', '进行中');
-      overdueQuery.lessThan('deadline', now);
-      overdueQuery.include(['contact', 'assignee']);
-      overdueQuery.limit(5);
-      const overdueProjects = await overdueQuery.find();
-
-      for (const project of overdueProjects) {
-        const contact = project.get('contact');
-        const assignee = project.get('assignee');
-        urgentTasks.push({
-          id: project.id + '_overdue',
-          projectId: project.id,
-          projectName: project.get('title') || '未命名项目',
-          title: `逾期项目:${project.get('title')}`,
-          stage: project.get('currentStage') || '进行中',
-          deadline: project.get('deadline'),
-          isOverdue: true,
-          isCompleted: false,
-          priority: 'high',
-          assignee: assignee?.get('name') || '未分配',
-          description: `客户:${contact?.get('name') || '未知'}`,
-          status: '逾期'
-        });
-      }
-
-      // 按优先级和截止时间排序
-      urgentTasks.sort((a, b) => {
-        if (a.isOverdue !== b.isOverdue) {
-          return a.isOverdue ? -1 : 1;
-        }
-        if (a.priority !== b.priority) {
-          const priorityOrder = { high: 0, medium: 1, low: 2 };
-          return priorityOrder[a.priority] - priorityOrder[b.priority];
-        }
-        return a.deadline.getTime() - b.deadline.getTime();
-      });
-
-      this.urgentTasks.set(urgentTasks);
-      console.log(`✅ 紧急任务加载完成: ${urgentTasks.length} 个任务`);
+      // 使用UrgentTaskService加载紧急事项
+      const result = await this.urgentTaskService.findUrgentTasks({
+        isCompleted: false
+      }, 1, 20);
+      
+      // 转换数据格式以兼容现有UI
+      const formattedTasks: Task[] = result.tasks.map(task => ({
+        id: task.id,
+        projectId: task.projectId,
+        projectName: task.projectName,
+        title: task.title,
+        stage: task.stage,
+        deadline: task.deadline,
+        isOverdue: task.isOverdue,
+        isCompleted: task.isCompleted,
+        priority: task.priority as 'high' | 'medium' | 'low',
+        assignee: task.assigneeName,
+        description: task.description || '',
+        status: task.status
+      }));
+      
+      this.urgentTasks.set(formattedTasks);
+      console.log(`✅ 紧急任务加载完成: ${formattedTasks.length} 个任务`);
     } catch (error) {
       console.error('❌ 紧急任务加载失败:', error);
-      // 不抛出错误,允许其他数据继续加载
+      this.urgentTasks.set([]);
     }
   }
 
@@ -522,12 +481,60 @@ export class Dashboard implements OnInit, OnDestroy {
   }
 
   // 处理任务完成
-  markTaskAsCompleted(taskId: string): void {
-    this.urgentTasks.set(
-      this.urgentTasks().map(task => 
-        task.id === taskId ? { ...task, isCompleted: true, status: '已完成' } : task
-      )
-    );
+  async markTaskAsCompleted(taskId: string): Promise<void> {
+    try {
+      const task = this.urgentTasks().find(t => t.id === taskId);
+      
+      await this.urgentTaskService.markAsCompleted(taskId);
+      
+      // 记录活动日志
+      if (task) {
+        try {
+          const user = this.currentUser();
+          await this.activityLogService.logActivity({
+            actorId: user?.id || 'unknown',
+            actorName: user?.get('name') || '客服',
+            actorRole: user?.get('roleName') || 'customer_service',
+            actionType: 'complete',
+            module: 'urgent_task',
+            entityType: 'UrgentTask',
+            entityId: taskId,
+            entityName: task.title,
+            description: '完成了紧急事项',
+            metadata: {
+              priority: task.priority,
+              projectName: task.projectName
+            }
+          });
+        } catch (logError) {
+          console.error('记录活动日志失败:', logError);
+        }
+      }
+      
+      // 重新加载任务列表
+      await this.loadUrgentTasks();
+      console.log('✅ 任务标记为已完成');
+    } catch (error) {
+      console.error('❌ 标记任务完成失败:', error);
+      alert('操作失败,请稍后重试');
+    }
+  }
+  
+  // 删除任务
+  async deleteTask(taskId: string): Promise<void> {
+    if (!confirm('确定要删除这个紧急事项吗?')) {
+      return;
+    }
+    
+    try {
+      await this.urgentTaskService.deleteUrgentTask(taskId);
+      // 重新加载任务列表
+      await this.loadUrgentTasks();
+      console.log('✅ 任务删除成功');
+    } catch (error) {
+      console.error('❌ 删除任务失败:', error);
+      alert('删除失败,请稍后重试');
+    }
   }
 
   // 处理派单操作
@@ -574,21 +581,18 @@ export class Dashboard implements OnInit, OnDestroy {
   }
 
   // 显示任务表单
-  showTaskForm(): void {
+  async showTaskForm(): Promise<void> {
     // 重置表单数据
     this.newTask = {
-      id: '',
-      projectId: '',
-      projectName: '',
       title: '',
-      stage: '需求沟通',
-      deadline: new Date(),
-      isOverdue: false,
-      isCompleted: false,
-      priority: 'high',
-      assignee: '当前用户',
       description: '',
-      status: '待处理'
+      projectId: '',
+      spaceId: '',
+      stage: '订单分配',
+      region: '',
+      priority: 'high',
+      assigneeId: '',
+      deadline: new Date()
     };
     
     // 重置相关状态
@@ -598,6 +602,20 @@ export class Dashboard implements OnInit, OnDestroy {
     // 计算并设置默认预设时长
     this.setDefaultPreset();
     
+    // 加载下拉列表数据
+    try {
+      const [projects, members] = await Promise.all([
+        this.urgentTaskService.getProjects(),
+        this.urgentTaskService.getTeamMembers()
+      ]);
+      
+      this.projectList.set(projects);
+      this.teamMembers.set(members);
+      this.spaceList.set([]); // 初始为空,等待选择项目后加载
+    } catch (error) {
+      console.error('加载下拉列表数据失败:', error);
+    }
+    
     // 显示表单
     this.isTaskFormVisible.set(true);
     
@@ -607,6 +625,22 @@ export class Dashboard implements OnInit, OnDestroy {
     }, 10);
   }
   
+  // 项目选择变化时加载空间列表
+  async onProjectChange(projectId: string): Promise<void> {
+    if (!projectId) {
+      this.spaceList.set([]);
+      return;
+    }
+    
+    try {
+      const spaces = await this.urgentTaskService.getProjectSpaces(projectId);
+      this.spaceList.set(spaces);
+    } catch (error) {
+      console.error('加载空间列表失败:', error);
+      this.spaceList.set([]);
+    }
+  }
+  
   // 设置默认预设时长
   private setDefaultPreset(): void {
     const now = new Date();
@@ -752,31 +786,65 @@ export class Dashboard implements OnInit, OnDestroy {
   }
   
   // 处理添加任务表单提交
-  handleAddTaskSubmit(): void {
+  async handleAddTaskSubmit(): Promise<void> {
     // 验证表单数据
-    if (!this.newTask.title.trim() || !this.newTask.projectName.trim() || !this.deadlineInput || this.isSubmitDisabled) {
-      // 在实际应用中,这里应该显示错误提示
-      alert('请填写必填字段(任务标题、项目名称、截止时间)');
+    if (!this.newTask.title.trim() || !this.newTask.projectId || !this.newTask.stage || !this.deadlineInput || this.isSubmitDisabled) {
+      alert('请填写必填字段(任务标题、项目、阶段、截止时间)');
       return;
     }
     
-    // 创建新任务
-    const taskToAdd: Task = {
-      ...this.newTask,
-      id: `task-${Date.now()}`,
-      projectId: `project-${Math.floor(Math.random() * 1000)}`,
-      deadline: new Date(this.deadlineInput),
-      isOverdue: new Date(this.deadlineInput) < new Date()
-    };
-    
-    // 添加到任务列表
-    this.urgentTasks.set([taskToAdd, ...this.urgentTasks()]);
-    
-    // 更新统计数据
-    this.stats.pendingAssignments.set(this.stats.pendingAssignments() + 1);
-    
-    // 隐藏表单
-    this.hideTaskForm();
+    try {
+      // 创建紧急事项
+      const task = await this.urgentTaskService.createUrgentTask({
+        title: this.newTask.title,
+        description: this.newTask.description,
+        projectId: this.newTask.projectId,
+        spaceId: this.newTask.spaceId || undefined,
+        stage: this.newTask.stage,
+        region: this.newTask.region,
+        priority: this.newTask.priority,
+        assigneeId: this.newTask.assigneeId || undefined,
+        deadline: new Date(this.deadlineInput)
+      });
+      
+      // 记录活动日志
+      try {
+        const user = this.currentUser();
+        const projectName = this.projectList().find(p => p.id === this.newTask.projectId)?.get('title') || '未知项目';
+        
+        await this.activityLogService.logActivity({
+          actorId: user?.id || 'unknown',
+          actorName: user?.get('name') || '客服',
+          actorRole: user?.get('roleName') || 'customer_service',
+          actionType: 'create',
+          module: 'urgent_task',
+          entityType: 'UrgentTask',
+          entityId: task.id,
+          entityName: this.newTask.title,
+          description: '创建了紧急事项',
+          metadata: {
+            priority: this.newTask.priority,
+            projectName: projectName,
+            stage: this.newTask.stage,
+            region: this.newTask.region,
+            deadline: this.deadlineInput
+          }
+        });
+      } catch (logError) {
+        console.error('记录活动日志失败:', logError);
+      }
+      
+      // 重新加载任务列表
+      await this.loadUrgentTasks();
+      
+      console.log('✅ 紧急事项创建成功');
+      
+      // 隐藏表单
+      this.hideTaskForm();
+    } catch (error) {
+      console.error('❌ 创建紧急事项失败:', error);
+      alert('创建失败,请稍后重试');
+    }
   }
   
   // 添加新的紧急事项

+ 6 - 17
src/app/pages/customer-service/project-list/project-list.html

@@ -110,17 +110,6 @@
             <div class="kanban-body">
               @for (col of columns; track col.id) {
                 <div class="kanban-column" [attr.data-col]="col.id">
-                  <!-- 空状态提示 -->
-                  @if (getProjectsByColumn(col.id).length === 0) {
-                    <div class="empty-column">
-                      <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" opacity="0.3">
-                        <circle cx="12" cy="12" r="10"></circle>
-                        <line x1="12" y1="8" x2="12" y2="16"></line>
-                        <line x1="8" y1="12" x2="16" y2="12"></line>
-                      </svg>
-                      <p>暂无项目</p>
-                    </div>
-                  }
                   @for (project of getProjectsByColumn(col.id); track project.id) {
                     <div class="kanban-card" (click)="navigateToProject(project, col.id)">
                       <div class="kanban-card-header">
@@ -158,12 +147,12 @@
                       </div>
                     </div>
                   }
-                   @if (getProjectsByColumn(col.id).length === 0) {
-                     <div class="empty-column">
-                       <span class="empty-icon">📦</span>
-                       <p>暂无项目</p>
-                     </div>
-                   }
+                  @if (getProjectsByColumn(col.id).length === 0) {
+                    <div class="empty-column">
+                      <span class="empty-icon">📦</span>
+                      <p>暂无项目</p>
+                    </div>
+                  }
                  </div>
                }
             </div>

+ 284 - 0
src/app/pages/customer-service/project-list/project-list.scss

@@ -1395,3 +1395,287 @@ $transition: all 0.3s ease;
     }
   }
 }
+
+// ========================================
+// 项目列表移动端适配
+// ========================================
+
+@media (max-width: 768px) {
+  .project-list-container {
+    height: auto;
+    min-height: 100vh;
+  }
+
+  // 头部区域
+  .header-section {
+    flex-direction: column;
+    padding: 16px 12px;
+    gap: 12px;
+
+    .header-left {
+      width: 100%;
+
+      h1 {
+        font-size: 22px;
+      }
+
+      .subtitle {
+        font-size: 13px;
+      }
+    }
+
+    .header-right {
+      width: 100%;
+
+      .view-toggle-buttons {
+        width: 100%;
+        display: grid;
+        grid-template-columns: repeat(2, 1fr);
+        gap: 8px;
+
+        .btn-view-toggle {
+          width: 100%;
+          font-size: 13px;
+          padding: 10px 12px;
+        }
+      }
+    }
+  }
+
+  // 统计卡片
+  .stats-cards {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    padding: 12px;
+
+    .stat-card {
+      padding: 16px;
+
+      .stat-icon {
+        width: 40px;
+        height: 40px;
+      }
+
+      .stat-info {
+        .stat-value {
+          font-size: 22px;
+        }
+
+        .stat-label {
+          font-size: 12px;
+        }
+      }
+    }
+  }
+
+  // 看板列
+  .kanban-board {
+    flex-direction: column;
+    padding: 12px;
+    gap: 16px;
+
+    .kanban-column {
+      width: 100%;
+      max-width: 100%;
+
+      .column-header {
+        padding: 14px;
+
+        .column-title {
+          font-size: 16px;
+        }
+
+        .column-count {
+          font-size: 12px;
+          min-width: 24px;
+          height: 24px;
+          line-height: 24px;
+        }
+      }
+
+      .column-body {
+        padding: 12px;
+        max-height: 400px;
+        overflow-y: auto;
+      }
+    }
+  }
+
+  // 项目卡片
+  .project-card {
+    padding: 14px;
+
+    .project-header {
+      .project-title {
+        font-size: 15px;
+      }
+
+      .project-id {
+        font-size: 11px;
+      }
+    }
+
+    .project-info {
+      flex-direction: column;
+      gap: 10px;
+
+      .info-item {
+        font-size: 12px;
+
+        svg {
+          width: 14px;
+          height: 14px;
+        }
+      }
+    }
+
+    .project-progress {
+      .progress-text {
+        font-size: 12px;
+      }
+
+      .progress-bar {
+        height: 6px;
+      }
+    }
+
+    .project-team {
+      flex-wrap: wrap;
+      gap: 6px;
+
+      .team-member {
+        width: 30px;
+        height: 30px;
+        font-size: 12px;
+      }
+
+      .team-more {
+        width: 30px;
+        height: 30px;
+        font-size: 11px;
+      }
+    }
+
+    .project-footer {
+      flex-direction: column;
+      gap: 10px;
+
+      .project-tags {
+        width: 100%;
+
+        .tag {
+          font-size: 11px;
+          padding: 3px 8px;
+        }
+      }
+
+      .project-actions {
+        width: 100%;
+        justify-content: center;
+
+        button {
+          min-width: 36px;
+          padding: 6px;
+        }
+      }
+    }
+  }
+
+  // 空状态
+  .empty-state {
+    padding: 60px 20px;
+
+    svg {
+      width: 100px;
+      height: 100px;
+    }
+
+    h3 {
+      font-size: 18px;
+    }
+
+    p {
+      font-size: 14px;
+    }
+  }
+
+  // Loading状态
+  .loading-container {
+    min-height: 300px;
+
+    .loading-spinner {
+      width: 40px;
+      height: 40px;
+    }
+
+    p {
+      font-size: 13px;
+    }
+  }
+
+  // 监控大盘
+  .dashboard-container {
+    min-height: 500px;
+    padding: 12px;
+
+    iframe {
+      min-height: 500px;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .header-section {
+    padding: 12px 8px;
+
+    .header-left h1 {
+      font-size: 20px;
+    }
+
+    .header-right .view-toggle-buttons {
+      grid-template-columns: 1fr;
+      gap: 6px;
+
+      .btn-view-toggle {
+        font-size: 12px;
+        padding: 8px 10px;
+      }
+    }
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr;
+    padding: 8px;
+    gap: 10px;
+
+    .stat-card {
+      padding: 14px;
+
+      .stat-value {
+        font-size: 20px;
+      }
+    }
+  }
+
+  .kanban-board {
+    padding: 8px;
+    gap: 12px;
+  }
+
+  .project-card {
+    padding: 12px;
+
+    .project-title {
+      font-size: 14px;
+    }
+  }
+
+  .dashboard-container {
+    min-height: 400px;
+    padding: 8px;
+
+    iframe {
+      min-height: 400px;
+    }
+  }
+}

+ 32 - 6
src/app/pages/customer-service/project-list/project-list.ts

@@ -229,11 +229,37 @@ export class ProjectList implements OnInit, OnDestroy {
         console.warn('3. 数据是否正确关联到当前公司');
       }
 
-      // 转换为Project接口格式
-      const projects: Project[] = projectObjects.map((obj: FmodeObject) => {
+      // 转换为Project接口格式(并从Product表同步最新阶段)
+      const projects: Project[] = await Promise.all(projectObjects.map(async (obj: FmodeObject) => {
         const contact = obj.get('contact');
         const assignee = obj.get('assignee');
-        const mappedStage = this.mapStage(obj.get('currentStage'));
+        
+        // 🔄 从Product表读取最新阶段(与组长端保持一致)
+        let currentStage = obj.get('currentStage') || '订单分配';
+        try {
+          const ProductQuery = new Parse.Query('Product');
+          ProductQuery.equalTo('project', { __type: 'Pointer', className: 'Project', objectId: obj.id });
+          ProductQuery.notEqualTo('isDeleted', true);
+          ProductQuery.descending('updatedAt');
+          ProductQuery.limit(1);
+          
+          const latestProduct = await ProductQuery.first();
+          if (latestProduct) {
+            const productStage = latestProduct.get('stage');
+            if (productStage) {
+              currentStage = productStage;
+              console.log(`📦 项目 ${obj.get('title')} 从Product同步阶段: ${productStage}`);
+            }
+          }
+        } catch (error) {
+          console.warn(`⚠️ 查询项目 ${obj.id} 的Product失败:`, error);
+        }
+        
+        const mappedStage = this.mapStage(currentStage);
+        
+        // 确保updatedAt是Date对象
+        const updatedAt = obj.get('updatedAt');
+        const createdAt = obj.get('createdAt');
         
         return {
           id: obj.id,
@@ -246,8 +272,8 @@ export class ProjectList implements OnInit, OnDestroy {
           assigneeId: assignee?.id || '',
           assigneeName: assignee?.get('name') || '未分配',
           deadline: obj.get('deadline') || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
-          createdAt: obj.get('createdAt') || new Date(),
-          updatedAt: obj.get('updatedAt') || new Date(),
+          createdAt: createdAt instanceof Date ? createdAt : (createdAt ? new Date(createdAt) : new Date()),
+          updatedAt: updatedAt instanceof Date ? updatedAt : (updatedAt ? new Date(updatedAt) : new Date()),
           description: obj.get('description') || '',
           priority: obj.get('priority') || 'medium',
           customerTags: [],
@@ -255,7 +281,7 @@ export class ProjectList implements OnInit, OnDestroy {
           skillsRequired: [],
           contact: contact
         };
-      });
+      }));
 
       this.allProjects.set(projects);
       this.baseProjects = projects;

+ 419 - 0
src/app/services/activity-log.service.ts

@@ -0,0 +1,419 @@
+import { Injectable, inject } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/core';
+
+/**
+ * 活动日志服务
+ * 用于记录和查询系统中所有成员的操作活动
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class ActivityLogService {
+  private Parse = FmodeParse.with('nova');
+
+  /**
+   * 记录活动日志
+   */
+  async logActivity(data: {
+    actorId: string;
+    actorName: string;
+    actorRole: string;
+    actionType: string;
+    module: string;
+    entityType: string;
+    entityId: string;
+    entityName: string;
+    description: string;
+    metadata?: any;
+  }): Promise<any> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        console.warn('No current user, skipping activity log');
+        return null;
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        console.warn('No company found, skipping activity log');
+        return null;
+      }
+
+      const ActivityLog = this.Parse.Object.extend('ActivityLog');
+      const log = new ActivityLog();
+
+      log.set('company', company);
+      log.set('actor', { __type: 'Pointer', className: 'Profile', objectId: data.actorId });
+      log.set('actorName', data.actorName);
+      log.set('actorRole', data.actorRole);
+      log.set('actionType', data.actionType);
+      log.set('module', data.module);
+      log.set('entityType', data.entityType);
+      log.set('entityId', data.entityId);
+      log.set('entityName', data.entityName);
+      log.set('description', data.description);
+      log.set('metadata', data.metadata || {});
+      log.set('userAgent', navigator.userAgent);
+      log.set('isDeleted', false);
+
+      const saved = await log.save();
+      return saved;
+    } catch (error) {
+      console.error('Error logging activity:', error);
+      return null;
+    }
+  }
+
+  /**
+   * 获取最近的活动日志
+   */
+  async getRecentActivities(limit: number = 10): Promise<any[]> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return [];
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return [];
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.notEqualTo('isDeleted', true);
+      query.include('actor');
+      query.descending('createdAt');
+      query.limit(limit);
+
+      const results = await query.find();
+      return results.map(log => this.formatActivityLog(log));
+    } catch (error) {
+      console.error('Error fetching recent activities:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 获取所有活动日志(支持分页)
+   */
+  async getAllActivities(page: number = 1, limit: number = 50): Promise<{ activities: any[], total: number }> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return { activities: [], total: 0 };
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return { activities: [], total: 0 };
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.notEqualTo('isDeleted', true);
+      query.include('actor');
+      query.descending('createdAt');
+      query.skip((page - 1) * limit);
+      query.limit(limit);
+
+      const [results, total] = await Promise.all([
+        query.find(),
+        query.count()
+      ]);
+
+      return {
+        activities: results.map(log => this.formatActivityLog(log)),
+        total
+      };
+    } catch (error) {
+      console.error('Error fetching all activities:', error);
+      return { activities: [], total: 0 };
+    }
+  }
+
+  /**
+   * 按模块查询活动日志
+   */
+  async getActivitiesByModule(module: string, limit: number = 20): Promise<any[]> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return [];
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return [];
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.equalTo('module', module);
+      query.notEqualTo('isDeleted', true);
+      query.include('actor');
+      query.descending('createdAt');
+      query.limit(limit);
+
+      const results = await query.find();
+      return results.map(log => this.formatActivityLog(log));
+    } catch (error) {
+      console.error('Error fetching activities by module:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 按用户查询活动日志
+   */
+  async getActivitiesByActor(actorId: string, limit: number = 20): Promise<any[]> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return [];
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return [];
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.equalTo('actor', { __type: 'Pointer', className: 'Profile', objectId: actorId });
+      query.notEqualTo('isDeleted', true);
+      query.descending('createdAt');
+      query.limit(limit);
+
+      const results = await query.find();
+      return results.map(log => this.formatActivityLog(log));
+    } catch (error) {
+      console.error('Error fetching activities by actor:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 按时间范围查询活动日志
+   */
+  async getActivitiesByDateRange(startDate: Date, endDate: Date): Promise<any[]> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return [];
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return [];
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.greaterThanOrEqualTo('createdAt', startDate);
+      query.lessThanOrEqualTo('createdAt', endDate);
+      query.notEqualTo('isDeleted', true);
+      query.include('actor');
+      query.descending('createdAt');
+
+      const results = await query.find();
+      return results.map(log => this.formatActivityLog(log));
+    } catch (error) {
+      console.error('Error fetching activities by date range:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 获取活动统计
+   */
+  async getActivityStats(startDate?: Date, endDate?: Date): Promise<{
+    total: number;
+    byModule: Record<string, number>;
+    byAction: Record<string, number>;
+    byActor: Record<string, number>;
+  }> {
+    try {
+      const currentUser = await this.Parse.User.current();
+      if (!currentUser) {
+        return { total: 0, byModule: {}, byAction: {}, byActor: {} };
+      }
+
+      const company = currentUser.get('company');
+      if (!company) {
+        return { total: 0, byModule: {}, byAction: {}, byActor: {} };
+      }
+
+      const query = new this.Parse.Query('ActivityLog');
+      query.equalTo('company', company);
+      query.notEqualTo('isDeleted', true);
+
+      if (startDate) {
+        query.greaterThanOrEqualTo('createdAt', startDate);
+      }
+      if (endDate) {
+        query.lessThanOrEqualTo('createdAt', endDate);
+      }
+
+      const results = await query.find();
+
+      const stats = {
+        total: results.length,
+        byModule: {} as Record<string, number>,
+        byAction: {} as Record<string, number>,
+        byActor: {} as Record<string, number>
+      };
+
+      results.forEach(log => {
+        const module = log.get('module');
+        const action = log.get('actionType');
+        const actor = log.get('actorName');
+
+        stats.byModule[module] = (stats.byModule[module] || 0) + 1;
+        stats.byAction[action] = (stats.byAction[action] || 0) + 1;
+        stats.byActor[actor] = (stats.byActor[actor] || 0) + 1;
+      });
+
+      return stats;
+    } catch (error) {
+      console.error('Error fetching activity stats:', error);
+      return { total: 0, byModule: {}, byAction: {}, byActor: {} };
+    }
+  }
+
+  /**
+   * 格式化活动日志对象
+   */
+  private formatActivityLog(log: any): any {
+    const actor = log.get('actor');
+    return {
+      id: log.id,
+      actorName: log.get('actorName'),
+      actorRole: log.get('actorRole'),
+      actorAvatar: actor?.get('data')?.avatar || '/assets/avatars/default.png',
+      actionType: log.get('actionType'),
+      module: log.get('module'),
+      entityType: log.get('entityType'),
+      entityId: log.get('entityId'),
+      entityName: log.get('entityName'),
+      description: log.get('description'),
+      metadata: log.get('metadata') || {},
+      createdAt: log.get('createdAt'),
+      formattedTime: this.formatTime(log.get('createdAt')),
+      icon: this.getActivityIcon(log.get('module'), log.get('actionType')),
+      color: this.getActivityColor(log.get('module'), log.get('actionType'))
+    };
+  }
+
+  /**
+   * 获取活动图标
+   */
+  private getActivityIcon(module: string, actionType: string): string {
+    const iconMap: Record<string, string> = {
+      'project-create': '📋',
+      'project-update': '📝',
+      'project-complete': '✅',
+      'project-assign': '👤',
+      'customer-create': '🆕',
+      'customer-update': '✏️',
+      'task-create': '📌',
+      'task-complete': '✔️',
+      'design-create': '🎨',
+      'design-upload': '📤',
+      'design-complete': '🎨',
+      'finance-create': '💰',
+      'finance-approve': '✓',
+      'urgent_task-create': '🚨',
+      'urgent_task-complete': '✅',
+      'case_library-create': '📚',
+      'case_library-share': '🔗',
+      'groupchat-create': '💬',
+      'employee-create': '👥',
+      'system-login': '🔐'
+    };
+
+    const key = `${module}-${actionType}`;
+    return iconMap[key] || '📝';
+  }
+
+  /**
+   * 获取活动颜色
+   */
+  private getActivityColor(module: string, actionType: string): string {
+    const colorMap: Record<string, string> = {
+      'create': '#4CAF50',
+      'update': '#2196F3',
+      'delete': '#F44336',
+      'complete': '#8BC34A',
+      'assign': '#9C27B0',
+      'upload': '#FF9800',
+      'approve': '#00BCD4'
+    };
+
+    return colorMap[actionType] || '#607D8B';
+  }
+
+  /**
+   * 格式化时间
+   */
+  private formatTime(date: Date): string {
+    const now = new Date();
+    const diff = now.getTime() - date.getTime();
+    const seconds = Math.floor(diff / 1000);
+    const minutes = Math.floor(seconds / 60);
+    const hours = Math.floor(minutes / 60);
+    const days = Math.floor(hours / 24);
+
+    if (seconds < 60) {
+      return '刚刚';
+    } else if (minutes < 60) {
+      return `${minutes}分钟前`;
+    } else if (hours < 24) {
+      return `${hours}小时前`;
+    } else if (days < 7) {
+      return `${days}天前`;
+    } else {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hour = String(date.getHours()).padStart(2, '0');
+      const minute = String(date.getMinutes()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hour}:${minute}`;
+    }
+  }
+
+  /**
+   * 获取活动描述的友好文本
+   */
+  getActivityDescription(activity: any): string {
+    const actionMap: Record<string, string> = {
+      create: '创建了',
+      update: '更新了',
+      delete: '删除了',
+      complete: '完成了',
+      assign: '分配了',
+      upload: '上传了',
+      share: '分享了',
+      approve: '审批了',
+      reject: '拒绝了',
+      cancel: '取消了',
+      comment: '评论了'
+    };
+
+    const moduleMap: Record<string, string> = {
+      project: '项目',
+      customer: '客户',
+      task: '任务',
+      design: '设计',
+      finance: '报价单',
+      urgent_task: '紧急事项',
+      case_library: '案例',
+      groupchat: '群聊',
+      employee: '员工',
+      department: '部门'
+    };
+
+    return activity.description || `${actionMap[activity.actionType] || activity.actionType}${moduleMap[activity.module] || activity.module}`;
+  }
+}
+

+ 665 - 0
src/app/services/case.service.ts

@@ -0,0 +1,665 @@
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+import { FmodeObject } from 'fmode-ng/core';
+
+const Parse = FmodeParse.with('nova');
+
+/**
+ * 案例管理服务
+ * 对接Case表(统一案例表)
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class CaseService {
+  private companyId = localStorage.getItem("company")!;
+
+  constructor() {}
+
+  // ==================== Case 案例增删查改 ====================
+
+  /**
+   * 查询案例列表(带筛选和分页)
+   */
+  async findCases(options?: {
+    searchKeyword?: string;
+    projectType?: string;
+    roomType?: string;
+    spaceType?: string;
+    renderingLevel?: string;
+    tags?: string[];
+    areaRange?: { min: number; max?: number };
+    priceRange?: { min: number; max?: number };
+    isPublished?: boolean;
+    isExcellent?: boolean;
+    designerId?: string;
+    teamId?: string;
+    projectId?: string;
+    page?: number;
+    pageSize?: number;
+  }): Promise<{ cases: any[]; total: number }> {
+    try {
+      const query = new Parse.Query('Case');
+      
+      // 多租户隔离
+      query.equalTo('company', this.getCompanyPointer());
+      query.notEqualTo('isDeleted', true);
+
+      // 筛选条件
+      if (options) {
+        // 发布状态(默认只显示已发布)
+        if (options.isPublished !== undefined) {
+          query.equalTo('isPublished', options.isPublished);
+        } else {
+          query.equalTo('isPublished', true);
+        }
+
+        // 优秀案例筛选
+        if (options.isExcellent !== undefined) {
+          query.equalTo('isExcellent', options.isExcellent);
+        }
+
+        // 项目类型筛选(info.projectType)
+        if (options.projectType) {
+          query.equalTo('info.projectType', options.projectType);
+        }
+
+        // 户型筛选(info.roomType)
+        if (options.roomType) {
+          query.equalTo('info.roomType', options.roomType);
+        }
+
+        // 空间类型筛选(info.spaceType)
+        if (options.spaceType) {
+          query.equalTo('info.spaceType', options.spaceType);
+        }
+
+        // 渲染水平筛选(info.renderingLevel)
+        if (options.renderingLevel) {
+          query.equalTo('info.renderingLevel', options.renderingLevel);
+        }
+
+        // 标签筛选(tag数组)
+        if (options.tags && options.tags.length > 0) {
+          query.containsAll('tag', options.tags);
+        }
+
+        // 面积范围筛选(info.area)
+        if (options.areaRange) {
+          query.greaterThanOrEqualTo('info.area', options.areaRange.min);
+          if (options.areaRange.max) {
+            query.lessThanOrEqualTo('info.area', options.areaRange.max);
+          }
+        }
+
+        // 价格范围筛选(totalPrice)
+        if (options.priceRange) {
+          query.greaterThanOrEqualTo('totalPrice', options.priceRange.min);
+          if (options.priceRange.max) {
+            query.lessThanOrEqualTo('totalPrice', options.priceRange.max);
+          }
+        }
+
+        // 设计师筛选
+        if (options.designerId) {
+          query.equalTo('designer', this.getPointer('Profile', options.designerId));
+        }
+
+        // 团队筛选
+        if (options.teamId) {
+          query.equalTo('team', this.getPointer('Department', options.teamId));
+        }
+
+        // 项目筛选
+        if (options.projectId) {
+          query.equalTo('project', this.getPointer('Project', options.projectId));
+        }
+
+        // 搜索关键词(案例名称)
+        if (options.searchKeyword && options.searchKeyword.trim()) {
+          query.matches('name', new RegExp(options.searchKeyword.trim(), 'i'));
+        }
+      }
+
+      // Include 关联对象
+      query.include('designer');
+      query.include('team');
+      query.include('project');
+
+      // 排序:优先按展示排序,然后按发布时间
+      query.ascending('index');
+      query.descending('publishedAt');
+
+      // 分页
+      const page = options?.page || 1;
+      const pageSize = options?.pageSize || 10;
+      query.limit(pageSize);
+      query.skip((page - 1) * pageSize);
+
+      // 并发查询数据和总数
+      const [cases, total] = await Promise.all([
+        query.find(),
+        query.count()
+      ]);
+
+      // 格式化数据
+      const formattedCases = cases.map(c => this.formatCase(c));
+
+      return { cases: formattedCases, total };
+    } catch (error) {
+      console.error('查询案例列表失败:', error);
+      return { cases: [], total: 0 };
+    }
+  }
+
+  /**
+   * 根据ID获取案例详情
+   */
+  async getCase(caseId: string): Promise<any | null> {
+    try {
+      const query = new Parse.Query('Case');
+      query.include('designer');
+      query.include('team');
+      query.include('project');
+      query.include('project.contact'); // 包含项目的客户信息
+      query.include('store');
+      query.include('address');
+
+      const caseObj = await query.get(caseId);
+      return this.formatCase(caseObj);
+    } catch (error) {
+      console.error('获取案例详情失败:', error);
+      return null;
+    }
+  }
+
+  /**
+   * 创建案例
+   */
+  async createCase(data: {
+    name: string;
+    projectId: string;
+    designerId: string;
+    teamId: string;
+    coverImage: string;
+    images: string[];
+    totalPrice: number;
+    completionDate: Date;
+    tag: string[];
+    info: {
+      area: number;
+      projectType: '工装' | '家装';
+      roomType: '一居室' | '二居室' | '三居室' | '四居+';
+      spaceType: '平层' | '复式' | '别墅' | '自建房';
+      renderingLevel: '高端' | '中端' | '低端';
+    };
+    storeId?: string;
+    addressId?: string;
+    publishedAt?: Date;
+    isPublished?: boolean;
+    isExcellent?: boolean;
+    index?: number;
+    customerReview?: string;
+    targetObject?: string[];
+    data?: any;
+  }): Promise<any> {
+    try {
+      const Case = Parse.Object.extend('Case');
+      const newCase = new Case();
+
+      // 必填字段
+      newCase.set('company', this.getCompanyPointer());
+      newCase.set('name', data.name);
+      newCase.set('project', this.getPointer('Project', data.projectId));
+      newCase.set('designer', this.getPointer('Profile', data.designerId));
+      newCase.set('team', this.getPointer('Department', data.teamId));
+      newCase.set('coverImage', data.coverImage);
+      newCase.set('images', data.images);
+      newCase.set('totalPrice', data.totalPrice);
+      newCase.set('completionDate', data.completionDate);
+      newCase.set('tag', data.tag);
+      newCase.set('info', data.info);
+
+      // 可选字段
+      if (data.storeId) {
+        newCase.set('store', this.getPointer('ShopStore', data.storeId));
+      }
+
+      if (data.addressId) {
+        newCase.set('address', this.getPointer('ShopAddress', data.addressId));
+      }
+
+      // 状态字段
+      newCase.set('isPublished', data.isPublished || false);
+      newCase.set('isExcellent', data.isExcellent || false);
+      newCase.set('index', data.index || 0);
+
+      // 发布时间
+      if (data.isPublished && data.publishedAt) {
+        newCase.set('publishedAt', data.publishedAt);
+      } else if (data.isPublished) {
+        newCase.set('publishedAt', new Date());
+      }
+
+      // 客户评价
+      if (data.customerReview) {
+        newCase.set('customerReview', data.customerReview);
+      }
+
+      // 关联产品
+      if (data.targetObject && data.targetObject.length > 0) {
+        newCase.set('targetObject', data.targetObject.map(id => this.getPointer('ShopGoods', id)));
+      } else {
+        newCase.set('targetObject', []);
+      }
+
+      // 扩展数据
+      if (data.data) {
+        newCase.set('data', data.data);
+      } else {
+        newCase.set('data', {});
+      }
+
+      // 软删除标记
+      newCase.set('isDeleted', false);
+
+      const savedCase = await newCase.save();
+      console.log('✅ 案例创建成功:', savedCase.id);
+
+      return this.formatCase(savedCase);
+    } catch (error) {
+      console.error('❌ 创建案例失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 更新案例
+   */
+  async updateCase(caseId: string, updates: {
+    name?: string;
+    coverImage?: string;
+    images?: string[];
+    totalPrice?: number;
+    completionDate?: Date;
+    tag?: string[];
+    info?: {
+      area?: number;
+      projectType?: '工装' | '家装';
+      roomType?: '一居室' | '二居室' | '三居室' | '四居+';
+      spaceType?: '平层' | '复式' | '别墅' | '自建房';
+      renderingLevel?: '高端' | '中端' | '低端';
+    };
+    isPublished?: boolean;
+    isExcellent?: boolean;
+    index?: number;
+    customerReview?: string;
+    targetObject?: string[];
+    data?: any;
+  }): Promise<any | null> {
+    try {
+      const query = new Parse.Query('Case');
+      const caseObj = await query.get(caseId);
+
+      // 更新基础字段
+      if (updates.name !== undefined) {
+        caseObj.set('name', updates.name);
+      }
+
+      if (updates.coverImage !== undefined) {
+        caseObj.set('coverImage', updates.coverImage);
+      }
+
+      if (updates.images !== undefined) {
+        caseObj.set('images', updates.images);
+      }
+
+      if (updates.totalPrice !== undefined) {
+        caseObj.set('totalPrice', updates.totalPrice);
+      }
+
+      if (updates.completionDate !== undefined) {
+        caseObj.set('completionDate', updates.completionDate);
+      }
+
+      if (updates.tag !== undefined) {
+        caseObj.set('tag', updates.tag);
+      }
+
+      // 更新info对象(部分更新)
+      if (updates.info) {
+        const currentInfo = caseObj.get('info') || {};
+        caseObj.set('info', { ...currentInfo, ...updates.info });
+      }
+
+      // 更新状态字段
+      if (updates.isPublished !== undefined) {
+        const wasPublished = caseObj.get('isPublished');
+        caseObj.set('isPublished', updates.isPublished);
+
+        // 如果从未发布变为发布,设置发布时间
+        if (!wasPublished && updates.isPublished) {
+          caseObj.set('publishedAt', new Date());
+        }
+      }
+
+      if (updates.isExcellent !== undefined) {
+        caseObj.set('isExcellent', updates.isExcellent);
+      }
+
+      if (updates.index !== undefined) {
+        caseObj.set('index', updates.index);
+      }
+
+      if (updates.customerReview !== undefined) {
+        caseObj.set('customerReview', updates.customerReview);
+      }
+
+      // 更新关联产品
+      if (updates.targetObject !== undefined) {
+        caseObj.set('targetObject', updates.targetObject.map(id => this.getPointer('ShopGoods', id)));
+      }
+
+      // 更新扩展数据(部分更新)
+      if (updates.data) {
+        const currentData = caseObj.get('data') || {};
+        caseObj.set('data', { ...currentData, ...updates.data });
+      }
+
+      const savedCase = await caseObj.save();
+      console.log('✅ 案例更新成功:', caseId);
+
+      return this.formatCase(savedCase);
+    } catch (error) {
+      console.error('❌ 更新案例失败:', error);
+      return null;
+    }
+  }
+
+  /**
+   * 删除案例(软删除)
+   */
+  async deleteCase(caseId: string): Promise<boolean> {
+    try {
+      const query = new Parse.Query('Case');
+      const caseObj = await query.get(caseId);
+
+      caseObj.set('isDeleted', true);
+      await caseObj.save();
+
+      console.log('✅ 案例删除成功:', caseId);
+      return true;
+    } catch (error) {
+      console.error('❌ 删除案例失败:', error);
+      return false;
+    }
+  }
+
+  /**
+   * 批量删除案例
+   */
+  async deleteCases(caseIds: string[]): Promise<number> {
+    try {
+      const query = new Parse.Query('Case');
+      query.containedIn('objectId', caseIds);
+      const cases = await query.find();
+
+      // 逐个删除
+      for (const c of cases) {
+        c.set('isDeleted', true);
+        await c.save();
+      }
+
+      console.log(`✅ 批量删除 ${cases.length} 个案例`);
+      return cases.length;
+    } catch (error) {
+      console.error('❌ 批量删除案例失败:', error);
+      return 0;
+    }
+  }
+
+  /**
+   * 发布案例
+   */
+  async publishCase(caseId: string): Promise<any | null> {
+    return this.updateCase(caseId, {
+      isPublished: true
+    });
+  }
+
+  /**
+   * 取消发布案例
+   */
+  async unpublishCase(caseId: string): Promise<any | null> {
+    return this.updateCase(caseId, {
+      isPublished: false
+    });
+  }
+
+  /**
+   * 标记为优秀案例
+   */
+  async markAsExcellent(caseId: string): Promise<any | null> {
+    return this.updateCase(caseId, {
+      isExcellent: true
+    });
+  }
+
+  /**
+   * 取消优秀案例标记
+   */
+  async unmarkAsExcellent(caseId: string): Promise<any | null> {
+    return this.updateCase(caseId, {
+      isExcellent: false
+    });
+  }
+
+  // ==================== 统计和查询 ====================
+
+  /**
+   * 统计案例数量
+   */
+  async countCases(filters?: {
+    isPublished?: boolean;
+    isExcellent?: boolean;
+    designerId?: string;
+    teamId?: string;
+  }): Promise<number> {
+    try {
+      const query = new Parse.Query('Case');
+      query.equalTo('company', this.getCompanyPointer());
+      query.notEqualTo('isDeleted', true);
+
+      if (filters) {
+        if (filters.isPublished !== undefined) {
+          query.equalTo('isPublished', filters.isPublished);
+        }
+        if (filters.isExcellent !== undefined) {
+          query.equalTo('isExcellent', filters.isExcellent);
+        }
+        if (filters.designerId) {
+          query.equalTo('designer', this.getPointer('Profile', filters.designerId));
+        }
+        if (filters.teamId) {
+          query.equalTo('team', this.getPointer('Department', filters.teamId));
+        }
+      }
+
+      return await query.count();
+    } catch (error) {
+      console.error('统计案例数量失败:', error);
+      return 0;
+    }
+  }
+
+  /**
+   * 获取设计师的案例列表
+   */
+  async getDesignerCases(designerId: string, limit: number = 10): Promise<any[]> {
+    const result = await this.findCases({
+      designerId,
+      isPublished: true,
+      page: 1,
+      pageSize: limit
+    });
+    return result.cases;
+  }
+
+  /**
+   * 获取团队的案例列表
+   */
+  async getTeamCases(teamId: string, limit: number = 10): Promise<any[]> {
+    const result = await this.findCases({
+      teamId,
+      isPublished: true,
+      page: 1,
+      pageSize: limit
+    });
+    return result.cases;
+  }
+
+  /**
+   * 获取项目的案例列表
+   */
+  async getProjectCases(projectId: string): Promise<any[]> {
+    const result = await this.findCases({
+      projectId,
+      page: 1,
+      pageSize: 100
+    });
+    return result.cases;
+  }
+
+  /**
+   * 获取优秀案例列表
+   */
+  async getExcellentCases(limit: number = 10): Promise<any[]> {
+    const result = await this.findCases({
+      isExcellent: true,
+      isPublished: true,
+      page: 1,
+      pageSize: limit
+    });
+    return result.cases;
+  }
+
+  // ==================== 辅助方法 ====================
+
+  /**
+   * 格式化案例数据
+   */
+  private formatCase(caseObj: FmodeObject): any {
+    const designer = caseObj.get('designer');
+    const team = caseObj.get('team');
+    const project = caseObj.get('project');
+    const store = caseObj.get('store');
+    const address = caseObj.get('address');
+    const info = caseObj.get('info') || {};
+    const data = caseObj.get('data') || {};
+
+    return {
+      // 系统字段
+      id: caseObj.id,
+      objectId: caseObj.id,
+      company: caseObj.get('company'),
+      isDeleted: caseObj.get('isDeleted') || false,
+      createdAt: caseObj.get('createdAt'),
+      updatedAt: caseObj.get('updatedAt'),
+
+      // 基础信息
+      name: caseObj.get('name') || '',
+
+      // 关联关系
+      projectId: project?.id || '',
+      projectName: project?.get('title') || '',
+      designerId: designer?.id || '',
+      designer: designer?.get('name') || '',
+      designerAvatar: designer?.get('data')?.avatar || '',
+      teamId: team?.id || '',
+      team: team?.get('name') || '',
+      storeId: store?.id || '',
+      storeName: store?.get('name') || '',
+      addressId: address?.id || '',
+      addressDetail: address?.get('address') || '',
+
+      // 媒体资源
+      coverImage: caseObj.get('coverImage') || '',
+      images: caseObj.get('images') || [],
+
+      // 数值信息
+      totalPrice: caseObj.get('totalPrice') || 0,
+
+      // 时间节点
+      completionDate: caseObj.get('completionDate'),
+      publishedAt: caseObj.get('publishedAt'),
+
+      // 标签/分类
+      tag: caseObj.get('tag') || [],
+
+      // 状态标记
+      isPublished: caseObj.get('isPublished') || false,
+      isExcellent: caseObj.get('isExcellent') || false,
+      index: caseObj.get('index') || 0,
+
+      // 评价信息
+      customerReview: caseObj.get('customerReview') || '',
+
+      // 关联产品
+      targetObject: caseObj.get('targetObject') || [],
+
+      // info字段(分类/类型)
+      area: info.area || 0,
+      projectType: info.projectType || '',
+      roomType: info.roomType || '',
+      spaceType: info.spaceType || '',
+      renderingLevel: info.renderingLevel || '',
+
+      // data扩展数据
+      data: data,
+
+      // 常用data字段展开(方便前端使用)
+      renovationSpec: data.renovationSpec,
+      productsDetail: data.productsDetail,
+      budget: data.budget,
+      timeline: data.timeline,
+      highlights: data.highlights,
+      features: data.features,
+      challenges: data.challenges,
+      materials: data.materials,
+      clientInfo: data.clientInfo,
+      constructionTeam: data.constructionTeam,
+      qualityInspection: data.qualityInspection,
+      imagesDetail: data.imagesDetail
+    };
+  }
+
+  /**
+   * 获取Company指针
+   */
+  private getCompanyPointer() {
+    return {
+      __type: 'Pointer',
+      className: 'Company',
+      objectId: this.companyId
+    };
+  }
+
+  /**
+   * 获取通用指针
+   */
+  private getPointer(className: string, objectId: string) {
+    return {
+      __type: 'Pointer',
+      className,
+      objectId
+    };
+  }
+
+  /**
+   * 批量转换为JSON
+   */
+  toJSONArray(cases: FmodeObject[]): any[] {
+    return cases.map(c => this.formatCase(c));
+  }
+}
+

+ 390 - 0
src/app/services/urgent-task.service.ts

@@ -0,0 +1,390 @@
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+
+/**
+ * 紧急事项服务
+ * 负责UrgentTask的CRUD操作,对接Parse Server数据库
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class UrgentTaskService {
+  private Parse = FmodeParse.with("nova");
+  private companyId = localStorage.getItem("company")!;
+  private currentUserId = localStorage.getItem("profile")!;
+
+  constructor() {}
+
+  /**
+   * 查询紧急事项列表(带筛选和分页)
+   */
+  async findUrgentTasks(filters?: {
+    status?: string;
+    priority?: string;
+    assignee?: string;
+    projectId?: string;
+    isCompleted?: boolean;
+  }, page: number = 1, pageSize: number = 20): Promise<{ tasks: any[], total: number }> {
+    try {
+      const query = this.buildUrgentTaskQuery(filters);
+      
+      // 分页
+      query.limit(pageSize);
+      query.skip((page - 1) * pageSize);
+      
+      // Include关联数据
+      query.include("project");
+      query.include("assignee");
+      query.include("creator");
+      query.include("space");
+      
+      // 排序:优先级高的在前,截止时间早的在前
+      query.descending("priority");
+      query.ascending("deadline");
+      
+      const [tasks, total] = await Promise.all([
+        query.find(),
+        query.count()
+      ]);
+
+      // 如果没有数据,直接返回空数组
+      if (!tasks || tasks.length === 0) {
+        console.log('紧急事项暂无数据');
+        return { tasks: [], total: 0 };
+      }
+
+      // 格式化数据
+      const formattedTasks = tasks.map(t => this.formatTask(t));
+
+      return { tasks: formattedTasks, total };
+    } catch (error) {
+      console.error('查询紧急事项列表失败:', error);
+      return { tasks: [], total: 0 };
+    }
+  }
+
+  /**
+   * 构建查询条件
+   */
+  private buildUrgentTaskQuery(filters?: any) {
+    try {
+      const UrgentTask = this.Parse.Object.extend("UrgentTask");
+      const query = new this.Parse.Query(UrgentTask);
+      
+      query.equalTo("company", this.getCompanyPointer());
+      query.notEqualTo("isDeleted", true);
+    
+      if (filters) {
+        // 完成状态
+        if (filters.isCompleted !== undefined) {
+          query.equalTo("isCompleted", filters.isCompleted);
+        }
+        
+        // 任务状态
+        if (filters.status) {
+          query.equalTo("status", filters.status);
+        }
+        
+        // 优先级
+        if (filters.priority) {
+          query.equalTo("priority", filters.priority);
+        }
+        
+        // 指派人
+        if (filters.assignee) {
+          query.equalTo("assignee", this.getProfilePointer(filters.assignee));
+        }
+        
+        // 项目ID
+        if (filters.projectId) {
+          query.equalTo("project", this.getProjectPointer(filters.projectId));
+        }
+      }
+    
+      return query;
+    } catch (error) {
+      console.error('构建查询条件失败:', error);
+      const UrgentTask = this.Parse.Object.extend("UrgentTask");
+      const query = new this.Parse.Query(UrgentTask);
+      query.equalTo("company", this.getCompanyPointer());
+      return query;
+    }
+  }
+
+  /**
+   * 格式化单个任务数据
+   */
+  private formatTask(taskObj: any) {
+    const project = taskObj.get("project");
+    const assignee = taskObj.get("assignee");
+    const creator = taskObj.get("creator");
+    const space = taskObj.get("space");
+    const deadline = taskObj.get("deadline");
+    const now = new Date();
+
+    return {
+      id: taskObj.id,
+      title: taskObj.get("title") || "",
+      description: taskObj.get("description") || "",
+      projectId: project?.id || "",
+      projectName: project?.get("title") || "",
+      spaceId: space?.id || "",
+      spaceName: space?.get("title") || "",
+      stage: taskObj.get("stage") || "",
+      region: taskObj.get("region") || "",
+      priority: taskObj.get("priority") || "medium",
+      status: taskObj.get("status") || "pending",
+      assigneeId: assignee?.id || "",
+      assigneeName: assignee?.get("name") || "未分配",
+      creatorId: creator?.id || "",
+      creatorName: creator?.get("name") || "",
+      deadline: deadline,
+      isOverdue: deadline ? deadline < now : false,
+      isCompleted: taskObj.get("isCompleted") || false,
+      createdAt: taskObj.get("createdAt"),
+      updatedAt: taskObj.get("updatedAt"),
+      data: taskObj.get("data") || {}
+    };
+  }
+
+  /**
+   * 创建紧急事项
+   */
+  async createUrgentTask(taskData: {
+    title: string;
+    description?: string;
+    projectId: string;
+    spaceId?: string;
+    stage: string;
+    region?: string;
+    priority: string;
+    assigneeId?: string;
+    deadline: Date;
+  }): Promise<any> {
+    try {
+      const UrgentTask = this.Parse.Object.extend("UrgentTask");
+      const task = new UrgentTask();
+      
+      task.set("company", this.getCompanyPointer());
+      task.set("creator", this.getCurrentProfilePointer());
+      task.set("title", taskData.title);
+      task.set("description", taskData.description || "");
+      task.set("project", this.getProjectPointer(taskData.projectId));
+      
+      if (taskData.spaceId) {
+        task.set("space", this.getSpacePointer(taskData.spaceId));
+      }
+      
+      task.set("stage", taskData.stage);
+      task.set("region", taskData.region || "");
+      task.set("priority", taskData.priority);
+      task.set("deadline", taskData.deadline);
+      task.set("status", "pending");
+      task.set("isCompleted", false);
+      task.set("isDeleted", false);
+      
+      if (taskData.assigneeId) {
+        task.set("assignee", this.getProfilePointer(taskData.assigneeId));
+      }
+      
+      const savedTask = await task.save();
+      console.log('✅ 紧急事项创建成功:', savedTask.id);
+      
+      return this.formatTask(savedTask);
+    } catch (error) {
+      console.error('❌ 创建紧急事项失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 更新紧急事项
+   */
+  async updateUrgentTask(taskId: string, updates: {
+    title?: string;
+    description?: string;
+    stage?: string;
+    region?: string;
+    priority?: string;
+    assigneeId?: string;
+    deadline?: Date;
+    status?: string;
+    isCompleted?: boolean;
+  }): Promise<any> {
+    try {
+      const UrgentTask = this.Parse.Object.extend("UrgentTask");
+      const query = new this.Parse.Query(UrgentTask);
+      const task = await query.get(taskId);
+      
+      if (updates.title !== undefined) task.set("title", updates.title);
+      if (updates.description !== undefined) task.set("description", updates.description);
+      if (updates.stage !== undefined) task.set("stage", updates.stage);
+      if (updates.region !== undefined) task.set("region", updates.region);
+      if (updates.priority !== undefined) task.set("priority", updates.priority);
+      if (updates.deadline !== undefined) task.set("deadline", updates.deadline);
+      if (updates.status !== undefined) task.set("status", updates.status);
+      if (updates.isCompleted !== undefined) task.set("isCompleted", updates.isCompleted);
+      
+      if (updates.assigneeId !== undefined) {
+        task.set("assignee", this.getProfilePointer(updates.assigneeId));
+      }
+      
+      const savedTask = await task.save();
+      console.log('✅ 紧急事项更新成功:', savedTask.id);
+      
+      return this.formatTask(savedTask);
+    } catch (error) {
+      console.error('❌ 更新紧急事项失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 删除紧急事项(软删除)
+   */
+  async deleteUrgentTask(taskId: string): Promise<void> {
+    try {
+      const UrgentTask = this.Parse.Object.extend("UrgentTask");
+      const query = new this.Parse.Query(UrgentTask);
+      const task = await query.get(taskId);
+      
+      task.set("isDeleted", true);
+      await task.save();
+      
+      console.log('✅ 紧急事项删除成功:', taskId);
+    } catch (error) {
+      console.error('❌ 删除紧急事项失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 标记任务为已完成
+   */
+  async markAsCompleted(taskId: string): Promise<any> {
+    return this.updateUrgentTask(taskId, {
+      isCompleted: true,
+      status: 'completed'
+    });
+  }
+
+  /**
+   * 获取项目列表(用于下拉选择)
+   */
+  async getProjects(): Promise<any[]> {
+    try {
+      const Project = this.Parse.Object.extend("Project");
+      const query = new this.Parse.Query(Project);
+      query.equalTo("company", this.getCompanyPointer());
+      query.notEqualTo("isDeleted", true);
+      query.limit(1000);
+      // 不使用select,获取所有字段
+      
+      const projects = await query.find();
+      
+      return projects.map(p => ({
+        id: p.id,
+        title: p.get("title") || "未命名项目",
+        stage: p.get("currentStage") || "",
+        status: p.get("status") || ""
+      }));
+    } catch (error) {
+      console.error('获取项目列表失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 获取项目的空间列表(Product表)
+   */
+  async getProjectSpaces(projectId: string): Promise<any[]> {
+    try {
+      const Product = this.Parse.Object.extend("Product");
+      const query = new this.Parse.Query(Product);
+      query.equalTo("project", this.getProjectPointer(projectId));
+      query.equalTo("company", this.getCompanyPointer());
+      query.notEqualTo("isDeleted", true);
+      query.limit(100);
+      // 不使用select,获取所有字段
+      
+      const products = await query.find();
+      
+      return products.map(p => ({
+        id: p.id,
+        title: p.get("title") || "未命名空间"
+      }));
+    } catch (error) {
+      console.error('获取空间列表失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 获取团队成员列表(用于指派)
+   */
+  async getTeamMembers(): Promise<any[]> {
+    try {
+      const Profile = this.Parse.Object.extend("Profile");
+      const query = new this.Parse.Query(Profile);
+      query.equalTo("company", this.getCompanyPointer());
+      query.notEqualTo("isDeleted", true);
+      query.limit(1000);
+      // 不使用select,获取所有字段
+      
+      const profiles = await query.find();
+      
+      return profiles.map(p => ({
+        id: p.id,
+        name: p.get("name") || "未命名",
+        mobile: p.get("mobile") || "",
+        roleName: p.get("roleName") || ""
+      }));
+    } catch (error) {
+      console.error('获取团队成员列表失败:', error);
+      return [];
+    }
+  }
+
+  // ==================== 辅助方法 ====================
+
+  private getCompanyPointer() {
+    return {
+      __type: "Pointer",
+      className: "Company",
+      objectId: this.companyId
+    };
+  }
+
+  private getCurrentProfilePointer() {
+    return {
+      __type: "Pointer",
+      className: "Profile",
+      objectId: this.currentUserId
+    };
+  }
+
+  private getProfilePointer(profileId: string) {
+    return {
+      __type: "Pointer",
+      className: "Profile",
+      objectId: profileId
+    };
+  }
+
+  private getProjectPointer(projectId: string) {
+    return {
+      __type: "Pointer",
+      className: "Project",
+      objectId: projectId
+    };
+  }
+
+  private getSpacePointer(spaceId: string) {
+    return {
+      __type: "Pointer",
+      className: "Product",
+      objectId: spaceId
+    };
+  }
+}
+

+ 29 - 2
src/modules/project/pages/project-loader/project-loader.component.ts

@@ -4,6 +4,7 @@ import { Router, ActivatedRoute } from '@angular/router';
 import { FormsModule } from '@angular/forms';
 import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
+import { ActivityLogService } from '../../../../app/services/activity-log.service';
 
 // WxworkCurrentChat 类型定义
 interface WxworkCurrentChat {
@@ -75,7 +76,8 @@ export class ProjectLoaderComponent implements OnInit {
 
   constructor(
     private router: Router,
-    private route: ActivatedRoute
+    private route: ActivatedRoute,
+    private activityLogService: ActivityLogService
   ) {}
 
   async ngOnInit() {
@@ -339,7 +341,32 @@ export class ProjectLoaderComponent implements OnInit {
       await pg.save();
       wxdebug('ProjectGroup关联创建成功');
 
-      // 4. 跳转项目详情
+      // 4. 记录活动日志
+      try {
+        await this.activityLogService.logActivity({
+          actorId: this.currentUser!.id,
+          actorName: this.currentUser!.get('name') || '系统',
+          actorRole: role,
+          actionType: 'create',
+          module: 'project',
+          entityType: 'Project',
+          entityId: project.id,
+          entityName: this.projectName.trim(),
+          description: '创建了新项目',
+          metadata: {
+            createdFrom: 'wxwork_groupchat',
+            groupChatId: this.groupChat!.id,
+            status: '待分配',
+            stage: '订单分配'
+          }
+        });
+        wxdebug('活动日志记录成功');
+      } catch (logError) {
+        console.error('记录活动日志失败:', logError);
+        // 活动日志失败不影响主流程
+      }
+
+      // 5. 跳转项目详情
       this.project = project;
       await this.navigateToProjectDetail();
     } catch (err: any) {

+ 3 - 0
src/styles.scss

@@ -6,6 +6,9 @@
 // 导入变量
 @use './styles/variables' as *;
 
+// 导入移动端响应式样式
+@import './styles/mobile-responsive.scss';
+
 // 自定义字体配置
 @include mat.core();
 

+ 462 - 0
src/styles/mobile-responsive.scss

@@ -0,0 +1,462 @@
+/**
+ * 全局移动端响应式样式
+ * 适配手机、平板等移动设备
+ */
+
+// 移动端断点定义
+$mobile-xs: 375px;  // 小屏手机
+$mobile-sm: 480px;  // 中屏手机
+$mobile-md: 768px;  // 大屏手机/小平板
+$tablet: 1024px;    // 平板
+$desktop: 1280px;   // 桌面
+
+// 移动端全局样式
+@media (max-width: $mobile-md) {
+  // 1. 基础HTML元素适配
+  html {
+    font-size: 14px; // 移动端基础字号
+    -webkit-text-size-adjust: 100%; // 防止iOS横屏字体自动调整
+    -webkit-tap-highlight-color: transparent; // 去除点击高亮
+  }
+
+  body {
+    font-size: 14px;
+    line-height: 1.6;
+    overflow-x: hidden; // 防止横向滚动
+  }
+
+  // 2. 通用容器适配
+  .container,
+  .page-container,
+  .main-container {
+    padding: 12px !important;
+    max-width: 100% !important;
+  }
+
+  // 3. 卡片组件适配
+  .card,
+  .mat-card,
+  .ios-card {
+    border-radius: 12px !important;
+    padding: 16px !important;
+    margin-bottom: 12px !important;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08) !important;
+  }
+
+  // 4. 表格适配
+  table {
+    display: block;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+    white-space: nowrap;
+
+    thead {
+      display: none; // 移动端隐藏表头,使用卡片式布局
+    }
+
+    tbody {
+      display: block;
+    }
+
+    tr {
+      display: flex;
+      flex-direction: column;
+      border: 1px solid #e5e7eb;
+      border-radius: 8px;
+      padding: 12px;
+      margin-bottom: 12px;
+      background: white;
+
+      &:hover {
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+    }
+
+    td {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border: none;
+      
+      &::before {
+        content: attr(data-label);
+        font-weight: 600;
+        color: #6b7280;
+        margin-right: 12px;
+      }
+    }
+  }
+
+  // 5. 按钮适配
+  button,
+  .btn,
+  .mat-button,
+  .mat-raised-button,
+  .mat-flat-button {
+    min-height: 44px !important; // iOS推荐最小点击区域
+    padding: 12px 20px !important;
+    font-size: 14px !important;
+    border-radius: 8px !important;
+  }
+
+  // 按钮组横向布局改为纵向
+  .button-group,
+  .action-buttons,
+  .mat-dialog-actions {
+    flex-direction: column !important;
+    gap: 12px !important;
+
+    button {
+      width: 100% !important;
+    }
+  }
+
+  // 6. 表单适配
+  input,
+  select,
+  textarea,
+  .mat-form-field {
+    font-size: 16px !important; // 防止iOS自动缩放
+    width: 100% !important;
+  }
+
+  input[type="text"],
+  input[type="email"],
+  input[type="password"],
+  input[type="tel"],
+  input[type="number"],
+  select,
+  textarea {
+    min-height: 44px !important;
+    padding: 12px 16px !important;
+    border-radius: 8px !important;
+  }
+
+  // 表单行改为单列
+  .form-row,
+  .form-grid {
+    grid-template-columns: 1fr !important;
+    gap: 16px !important;
+  }
+
+  // 7. 模态框/对话框适配
+  .modal,
+  .dialog,
+  .mat-dialog-container,
+  .ios-panel {
+    width: 95vw !important;
+    max-width: 95vw !important;
+    max-height: 90vh !important;
+    margin: 5vh auto !important;
+    border-radius: 16px !important;
+  }
+
+  .modal-content,
+  .dialog-content {
+    padding: 20px !important;
+  }
+
+  .modal-header,
+  .dialog-header {
+    padding: 16px 20px !important;
+    font-size: 18px !important;
+  }
+
+  // 8. 导航栏适配
+  .navbar,
+  .header,
+  .page-header {
+    padding: 12px 16px !important;
+    flex-wrap: wrap !important;
+  }
+
+  .nav-menu,
+  .menu-items {
+    flex-direction: column !important;
+    width: 100% !important;
+  }
+
+  // 9. 侧边栏适配
+  .sidebar,
+  .side-panel {
+    width: 100% !important;
+    max-width: 280px !important;
+    position: fixed !important;
+    z-index: 1000 !important;
+    transform: translateX(-100%);
+    transition: transform 0.3s ease;
+
+    &.open,
+    &.active {
+      transform: translateX(0);
+    }
+  }
+
+  // 10. 网格布局适配
+  .grid,
+  .grid-container {
+    grid-template-columns: 1fr !important;
+    gap: 12px !important;
+  }
+
+  // 特定列数适配
+  .grid-2,
+  .grid-3,
+  .grid-4 {
+    grid-template-columns: 1fr !important;
+  }
+
+  // 11. 文字适配
+  h1 { font-size: 24px !important; }
+  h2 { font-size: 20px !important; }
+  h3 { font-size: 18px !important; }
+  h4 { font-size: 16px !important; }
+  h5 { font-size: 14px !important; }
+  h6 { font-size: 12px !important; }
+
+  // 12. 间距适配
+  .p-1 { padding: 4px !important; }
+  .p-2 { padding: 8px !important; }
+  .p-3 { padding: 12px !important; }
+  .p-4 { padding: 16px !important; }
+  .p-5 { padding: 20px !important; }
+
+  .m-1 { margin: 4px !important; }
+  .m-2 { margin: 8px !important; }
+  .m-3 { margin: 12px !important; }
+  .m-4 { margin: 16px !important; }
+  .m-5 { margin: 20px !important; }
+
+  // 13. 隐藏/显示工具类
+  .hide-mobile,
+  .desktop-only {
+    display: none !important;
+  }
+
+  .show-mobile,
+  .mobile-only {
+    display: block !important;
+  }
+
+  // 14. Flex布局适配
+  .flex-row {
+    flex-direction: column !important;
+  }
+
+  .flex-wrap {
+    flex-wrap: wrap !important;
+  }
+
+  // 15. 图片适配
+  img {
+    max-width: 100% !important;
+    height: auto !important;
+  }
+
+  // 16. 视频适配
+  video,
+  iframe {
+    max-width: 100% !important;
+    height: auto !important;
+  }
+
+  // 17. 分页适配
+  .pagination {
+    flex-wrap: wrap !important;
+    gap: 8px !important;
+    
+    button,
+    .page-item {
+      min-width: 40px !important;
+      padding: 8px !important;
+    }
+  }
+
+  // 18. 标签页适配
+  .tabs,
+  .mat-tab-labels {
+    overflow-x: auto !important;
+    -webkit-overflow-scrolling: touch;
+  }
+
+  .tab-label,
+  .mat-tab-label {
+    min-width: auto !important;
+    padding: 12px 16px !important;
+  }
+
+  // 19. 下拉菜单适配
+  .dropdown-menu,
+  .mat-menu-panel {
+    max-width: 90vw !important;
+    max-height: 60vh !important;
+    overflow-y: auto !important;
+  }
+
+  // 20. Toast/消息提示适配
+  .toast,
+  .snackbar,
+  .mat-snack-bar-container {
+    width: 90vw !important;
+    max-width: 90vw !important;
+    left: 5vw !important;
+    right: 5vw !important;
+    bottom: 20px !important;
+  }
+}
+
+// 小屏手机特殊适配 (< 480px)
+@media (max-width: $mobile-sm) {
+  html {
+    font-size: 13px;
+  }
+
+  .container,
+  .page-container {
+    padding: 8px !important;
+  }
+
+  .card {
+    padding: 12px !important;
+  }
+
+  h1 { font-size: 20px !important; }
+  h2 { font-size: 18px !important; }
+  h3 { font-size: 16px !important; }
+}
+
+// 平板横屏适配 (768px - 1024px)
+@media (min-width: $mobile-md) and (max-width: $tablet) {
+  .grid-2 {
+    grid-template-columns: repeat(2, 1fr) !important;
+  }
+
+  .grid-3,
+  .grid-4 {
+    grid-template-columns: repeat(2, 1fr) !important;
+  }
+
+  .form-row {
+    grid-template-columns: repeat(2, 1fr) !important;
+  }
+}
+
+// 移动端特定组件适配
+
+// Stats卡片
+@media (max-width: $mobile-md) {
+  .stats-grid,
+  .dashboard-stats {
+    grid-template-columns: 1fr !important;
+    gap: 12px !important;
+  }
+
+  .stat-card {
+    padding: 16px !important;
+    
+    .stat-value {
+      font-size: 24px !important;
+    }
+
+    .stat-label {
+      font-size: 13px !important;
+    }
+  }
+}
+
+// Chart图表
+@media (max-width: $mobile-md) {
+  .chart-container,
+  canvas {
+    height: 250px !important;
+    max-height: 250px !important;
+  }
+}
+
+// Timeline时间线
+@media (max-width: $mobile-md) {
+  .timeline {
+    padding-left: 20px !important;
+  }
+
+  .timeline-item {
+    padding-left: 30px !important;
+  }
+
+  .timeline-marker {
+    left: 0 !important;
+  }
+}
+
+// Calendar日历
+@media (max-width: $mobile-md) {
+  .calendar {
+    font-size: 12px !important;
+  }
+
+  .calendar-day {
+    min-height: 50px !important;
+    padding: 4px !important;
+  }
+
+  .calendar-event {
+    font-size: 10px !important;
+    padding: 2px 4px !important;
+  }
+}
+
+// 触摸优化
+@media (hover: none) and (pointer: coarse) {
+  // 触摸设备
+  button,
+  a,
+  .clickable {
+    cursor: pointer;
+    user-select: none;
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
+  }
+
+  // 增加点击区域
+  .touch-target {
+    min-height: 44px;
+    min-width: 44px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+
+// 横屏提示(可选)
+@media (max-width: $mobile-md) and (orientation: landscape) {
+  .landscape-warning {
+    display: block;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    background: #fef3c7;
+    color: #92400e;
+    padding: 8px;
+    text-align: center;
+    font-size: 12px;
+    z-index: 9999;
+  }
+}
+
+// 安全区域适配(iPhone刘海屏等)
+@supports (padding: max(0px)) {
+  body {
+    padding-left: max(0px, env(safe-area-inset-left));
+    padding-right: max(0px, env(safe-area-inset-right));
+    padding-bottom: max(0px, env(safe-area-inset-bottom));
+  }
+
+  .header,
+  .navbar {
+    padding-top: max(12px, env(safe-area-inset-top));
+  }
+}
+
+
+
+