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

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

0235711 2 днів тому
батько
коміт
cd94e9d224
32 змінених файлів з 8685 додано та 673 видалено
  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));
+  }
+}
+
+
+
+