فهرست منبع

feat: div in project

ryanemax 1 ماه پیش
والد
کامیت
5a049b46b3
25فایلهای تغییر یافته به همراه5213 افزوده شده و 1571 حذف شده
  1. 1 1
      copy/project-detail.ts
  2. 1 1
      docs/implementation-summary-wxwork-project.md
  3. 2 2
      docs/prd/wxwork-project-management.md
  4. 1 1
      docs/service-integration-complete.md
  5. 1 1
      src/app/pages/auth/login/login.html
  6. 1 1
      src/app/pages/designer/project-detail/project-detail.ts
  7. 217 226
      src/modules/project/pages/contact/contact.component.html
  8. 582 301
      src/modules/project/pages/contact/contact.component.scss
  9. 1 1
      src/modules/project/pages/contact/contact.component.ts
  10. 46 31
      src/modules/project/pages/project-detail/project-detail.component.html
  11. 814 42
      src/modules/project/pages/project-detail/project-detail.component.scss
  12. 1 1
      src/modules/project/pages/project-detail/project-detail.component.ts
  13. 270 140
      src/modules/project/pages/project-detail/stages/stage-aftercare.component.html
  14. 764 167
      src/modules/project/pages/project-detail/stages/stage-aftercare.component.scss
  15. 1 1
      src/modules/project/pages/project-detail/stages/stage-aftercare.component.ts
  16. 104 74
      src/modules/project/pages/project-detail/stages/stage-delivery.component.html
  17. 816 48
      src/modules/project/pages/project-detail/stages/stage-delivery.component.scss
  18. 1 1
      src/modules/project/pages/project-detail/stages/stage-delivery.component.ts
  19. 165 127
      src/modules/project/pages/project-detail/stages/stage-order.component.html
  20. 606 110
      src/modules/project/pages/project-detail/stages/stage-order.component.scss
  21. 2 2
      src/modules/project/pages/project-detail/stages/stage-order.component.ts
  22. 229 170
      src/modules/project/pages/project-detail/stages/stage-requirements.component.html
  23. 581 116
      src/modules/project/pages/project-detail/stages/stage-requirements.component.scss
  24. 2 3
      src/modules/project/pages/project-detail/stages/stage-requirements.component.ts
  25. 4 3
      src/modules/project/pages/project-loader/project-loader.component.ts

+ 1 - 1
copy/project-detail.ts

@@ -816,7 +816,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     // 首先检查查询参数中的role
     const queryParams = this.route.snapshot.queryParamMap;
-    const roleParam = queryParams.get('role');
+    const roleParam = queryParams.get('roleName');
     if (roleParam === 'customer-service') {
       return 'customer-service';
     }

+ 1 - 1
docs/implementation-summary-wxwork-project.md

@@ -290,7 +290,7 @@ buildAIPrompt(): string {
 ### 6. **权限控制体系**
 ```typescript
 // 基于角色的权限
-const role = this.currentUser?.get('role');
+const role = this.currentUser?.get('roleName');
 
 // 编辑权限
 this.canEdit = ['客服', '组员', '组长', '管理员'].includes(role);

+ 2 - 2
docs/prd/wxwork-project-management.md

@@ -860,7 +860,7 @@ if (GroupChat) {
 ```typescript
 // 获取当前登录的员工信息
 const profile = await wxwork.getCurrentUser();
-const role = profile.get('role'); // 客服/组员/组长
+const role = profile.get('roleName'); // 客服/组员/组长
 ```
 
 **发送企微消息**:
@@ -1117,7 +1117,7 @@ function hasPermission(role: string, action: string): boolean {
 }
 
 // 使用示例
-if (!hasPermission(currentUser.get('role'), 'createProject')) {
+if (!hasPermission(currentUser.get('roleName'), 'createProject')) {
   alert('您没有权限创建项目');
   return;
 }

+ 1 - 1
docs/service-integration-complete.md

@@ -238,7 +238,7 @@ if (Contact) {
 
 // 3. 获取当前用户
 const currentUser = await this.wxworkService.getCurrentUser();
-const role = currentUser?.get('role'); // 客服/组员/组长/管理员
+const role = currentUser?.get('roleName'); // 客服/组员/组长/管理员
 const name = currentUser?.get('name');
 
 // 4. 创建群聊

+ 1 - 1
src/app/pages/auth/login/login.html

@@ -51,7 +51,7 @@
 
     <footer class="footer">
       <span>© {{ currentYear }} YSS</span>
-      <a href="https://app.fmode.cn/dev/yss/wxwork/cDL6R1hgSi/project-loader">隐私</a>
+      <a href="https://app.fmode.cn/dev/yss/">隐私</a>
       <a href="https://app.fmode.cn/dev/yss/wxwork/cDL6R1hgSi/project-loader">条款</a>
     </footer>
   </div>

+ 1 - 1
src/app/pages/designer/project-detail/project-detail.ts

@@ -893,7 +893,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     // 首先检查查询参数中的role
     const queryParams = this.route.snapshot.queryParamMap;
-    const roleParam = queryParams.get('role');
+    const roleParam = queryParams.get('roleName');
     if (roleParam === 'customer-service') {
       return 'customer-service';
     }

+ 217 - 226
src/modules/project/pages/contact/contact.component.html

@@ -1,48 +1,48 @@
-<ion-header>
-  <ion-toolbar>
-    <ion-buttons slot="start">
-      <ion-back-button (click)="goBack()" defaultHref="/"></ion-back-button>
-    </ion-buttons>
-    <ion-title>客户画像</ion-title>
-  </ion-toolbar>
-</ion-header>
-
-<ion-content class="ion-padding">
-  <!-- 加载中 -->
-  @if (loading) {
-    <div class="loading-container">
-      <ion-spinner name="crescent"></ion-spinner>
-      <p>加载客户信息...</p>
+<div class="contact-page">
+  <div class="header">
+    <div class="header-toolbar">
+      <button class="back-button" (click)="goBack()">
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M244 400L100 256l144-144M120 256h292" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48"/></svg>
+      </button>
+      <h1 class="title">客户画像</h1>
     </div>
-  }
+  </div>
 
-  <!-- 错误 -->
-  @if (error && !loading) {
-    <div class="error-container">
-      <ion-icon name="alert-circle-outline"></ion-icon>
-      <p>{{ error }}</p>
-      <ion-button (click)="loadData()" fill="outline">重试</ion-button>
-    </div>
-  }
+  <div class="content">
+    <!-- 加载中 -->
+    @if (loading) {
+      <div class="loading-container">
+        <div class="spinner"></div>
+        <p>加载客户信息...</p>
+      </div>
+    }
 
-  <!-- 客户画像内容 -->
-  @if (!loading && !error && contactInfo) {
-    <div class="contact-container">
-      <!-- 头部:头像 + 基础信息 -->
-      <ion-card class="header-card">
-        <ion-card-content>
+    <!-- 错误 -->
+    @if (error && !loading) {
+      <div class="error-container">
+        <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 80c-8.66 0-16.58 7.36-16 16l8 216a8 8 0 008 8h0a8 8 0 008-8l8-216c.58-8.64-7.34-16-16-16z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><circle cx="256" cy="416" r="16" fill="currentColor"/></svg>
+        <p>{{ error }}</p>
+        <button class="btn btn-outline" (click)="loadData()">重试</button>
+      </div>
+    }
+
+    <!-- 客户画像内容 -->
+    @if (!loading && !error && contactInfo) {
+      <div class="contact-container">
+        <!-- 头部:头像 + 基础信息 -->
+        <div class="card header-card">
           <div class="customer-header">
-            <ion-avatar class="customer-avatar">
+            <div class="customer-avatar">
               @if (profile.basic.avatar) {
                 <img [src]="profile.basic.avatar" alt="头像" />
               } @else {
-                <ion-icon name="person-circle-outline"></ion-icon>
+                <svg class="icon-avatar" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M258.9 48C141.92 46.42 46.42 141.92 48 258.9c1.56 112.19 92.91 203.54 205.1 205.1 117 1.6 212.48-93.9 210.88-210.88C462.44 140.91 371.09 49.56 258.9 48zM385.32 375.25a4 4 0 01-6.14-.32 124.27 124.27 0 00-32.35-29.59C321.37 329 289.11 320 256 320s-65.37 9-90.83 25.34a124.24 124.24 0 00-32.35 29.58 4 4 0 01-6.14.32A175.32 175.32 0 0180 259c-1.63-97.31 78.22-178.76 175.57-179S432 158.81 432 256a175.32 175.32 0 01-46.68 119.25z"/><path d="M256 144c-19.72 0-37.55 7.39-50.22 20.82s-19 32-17.57 51.93C191.11 256 221.52 288 256 288s64.83-32 67.79-71.24c1.48-19.74-4.8-38.14-17.68-51.82C293.39 151.44 275.59 144 256 144z"/></svg>
               }
-            </ion-avatar>
+            </div>
             <div class="customer-info">
               <h2>{{ profile.basic.name }}</h2>
               <div class="source-badge">
-                <ion-icon [name]="getSourceIcon(profile.basic.source)"></ion-icon>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 176v160m80-80H176"/></svg>
                 <span>{{ profile.basic.source }}</span>
               </div>
             </div>
@@ -52,227 +52,218 @@
           @if (profile.basic.tags.length > 0) {
             <div class="tags-container">
               @for (tag of profile.basic.tags; track tag) {
-                <ion-chip color="primary" outline="true">
-                  <ion-label>{{ tag }}</ion-label>
-                </ion-chip>
+                <span class="chip chip-primary">{{ tag }}</span>
               }
             </div>
           }
-        </ion-card-content>
-      </ion-card>
+        </div>
 
-      <!-- 基础信息 -->
-      <ion-card class="basic-info-card">
-        <ion-card-header>
-          <ion-card-title>
-            <ion-icon name="information-circle-outline"></ion-icon>
-            基础信息
-          </ion-card-title>
-        </ion-card-header>
-        <ion-card-content>
-          <ion-list lines="none">
-            <ion-item>
-              <ion-icon name="call-outline" slot="start"></ion-icon>
-              <ion-label>
-                <p>手机号</p>
-                <h3>{{ profile.basic.mobile }}</h3>
-              </ion-label>
-            </ion-item>
-            <ion-item>
-              <ion-icon name="logo-wechat" slot="start"></ion-icon>
-              <ion-label>
-                <p>微信号</p>
-                <h3>{{ profile.basic.wechat || '未绑定' }}</h3>
-              </ion-label>
-            </ion-item>
-            @if (!canViewSensitiveInfo) {
-              <div class="permission-notice">
-                <ion-icon name="lock-closed-outline"></ion-icon>
-                <span>仅客服和组长可查看完整联系方式</span>
-              </div>
-            }
-          </ion-list>
-        </ion-card-content>
-      </ion-card>
-
-      <!-- 客户画像 -->
-      <ion-card class="preferences-card">
-        <ion-card-header>
-          <ion-card-title>
-            <ion-icon name="color-palette-outline"></ion-icon>
-            客户画像
-          </ion-card-title>
-        </ion-card-header>
-        <ion-card-content>
-          <div class="preference-grid">
-            <!-- 风格偏好 -->
-            <div class="preference-item">
-              <div class="preference-label">
-                <ion-icon name="brush-outline"></ion-icon>
-                <span>风格偏好</span>
-              </div>
-              <div class="preference-value">
-                @if (profile.preferences.style.length > 0) {
-                  @for (style of profile.preferences.style; track style) {
-                    <ion-badge color="tertiary">{{ style }}</ion-badge>
-                  }
-                } @else {
-                  <span class="empty">未设置</span>
-                }
-              </div>
-            </div>
-
-            <!-- 预算范围 -->
-            <div class="preference-item">
-              <div class="preference-label">
-                <ion-icon name="cash-outline"></ion-icon>
-                <span>预算范围</span>
+        <!-- 基础信息 -->
+        <div class="card basic-info-card">
+          <div class="card-header">
+            <h3 class="card-title">
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="64"/><path d="M490.84 238.6c-26.46-40.92-60.79-75.68-99.27-100.53C349 110.55 302 96 255.66 96c-42.52 0-84.33 12.15-124.27 36.11-40.73 24.43-77.63 60.12-109.68 106.07a31.92 31.92 0 00-.64 35.54c26.41 41.33 60.4 76.14 98.28 100.65C162 402 207.9 416 255.66 416c46.71 0 93.81-14.43 136.2-41.72 38.46-24.77 72.72-59.66 99.08-100.92a32.2 32.2 0 00-.1-34.76zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z"/></svg>
+              基础信息
+            </h3>
+          </div>
+          <div class="card-content">
+            <div class="info-list">
+              <div class="info-item">
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M391 480c-19.52 0-46.94-7.06-88-30-49.93-28-88.55-53.85-138.21-103.38C116.91 298.77 93.61 267.79 61 208.45c-36.84-67-30.56-102.12-23.54-117.13C45.82 73.38 58.16 62.65 74.11 52a176.3 176.3 0 0128.64-15.2c1-.43 1.93-.84 2.76-1.21 4.95-2.23 12.45-5.6 21.95-2 6.34 2.38 12 7.25 20.86 16 18.17 17.92 43 57.83 52.16 77.43 6.15 13.21 10.22 21.93 10.23 31.71 0 11.45-5.76 20.28-12.75 29.81-1.31 1.79-2.61 3.5-3.87 5.16-7.61 10-9.28 12.89-8.18 18.05 2.23 10.37 18.86 41.24 46.19 68.51s57.31 42.85 67.72 45.07c5.38 1.15 8.33-.59 18.65-8.47 1.48-1.13 3-2.3 4.59-3.47 10.66-7.93 19.08-13.54 30.26-13.54h.06c9.73 0 18.06 4.22 31.86 11.18 18 9.08 59.11 33.59 77.14 51.78 8.77 8.84 13.66 14.48 16.05 20.81 3.6 9.53.21 17-2 22-.37.83-.78 1.74-1.21 2.75a176.49 176.49 0 01-15.29 28.58c-10.63 15.9-21.4 28.21-39.38 36.58A67.42 67.42 0 01391 480z"/></svg>
+                <div class="info-text">
+                  <p class="info-label">手机号</p>
+                  <h4 class="info-value">{{ profile.basic.mobile }}</h4>
+                </div>
               </div>
-              <div class="preference-value">
-                <ion-badge color="success">
-                  {{ formatBudget(profile.preferences.budget) }}
-                </ion-badge>
+              <div class="info-item">
+                <svg class="icon icon-wechat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M408.67 298.53a21 21 0 1120.9-21 20.85 20.85 0 01-20.9 21m-102.17 0a21 21 0 1120.9-21 20.84 20.84 0 01-20.9 21m152.09 118.86C491.1 394.08 512 359.13 512 319.51c0-71.08-68.5-129.35-154.41-129.35s-154.42 58.27-154.42 129.35 68.5 129.34 154.42 129.34c17.41 0 34.83-2.33 49.92-7 2.49-.86 3.48-1.17 4.64-1.17a16.67 16.67 0 018.13 2.34L454 462.83a11.62 11.62 0 003.48 1.17 5 5 0 005-4.65v-.33a47.62 47.62 0 00-4.16-26.26 11.62 11.62 0 01-1.15-4.65 14.35 14.35 0 011.65-7.32"/><path d="M246.13 178.51a24.47 24.47 0 010-48.94c12.77 0 24.38 11.65 24.38 24.47 1 12.82-11.61 24.47-24.38 24.47m-123.06 0a24.47 24.47 0 1124.38-24.47c.01 12.82-11.6 24.47-24.38 24.47m74.13 145.09c-85.87 0-154.41-58.26-154.41-129.34S126.73 65 212.6 65 367 123.27 367 194.35c0 37.08-19.15 71.49-51.82 94.45-1.15 1.17-1.15 2.34-1.15 4.65a36.44 36.44 0 01-1.15 8.14 49.21 49.21 0 00-3.49 14.78 11.05 11.05 0 01-3.48 4.65c-1.15 1.17-3.49 1.17-4.64.07l-38-21.05a28.13 28.13 0 00-7-2.34c-1.16 0-2.32 0-4.64 1.16-13.97 4.66-28.97 6.99-44.95 7z"/></svg>
+                <div class="info-text">
+                  <p class="info-label">微信号</p>
+                  <h4 class="info-value">{{ profile.basic.wechat || '未绑定' }}</h4>
+                </div>
               </div>
+              @if (!canViewSensitiveInfo) {
+                <div class="permission-notice">
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M336 208v-95a80 80 0 00-160 0v95" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><rect x="96" y="208" width="320" height="272" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
+                  <span>仅客服和组长可查看完整联系方式</span>
+                </div>
+              }
             </div>
+          </div>
+        </div>
 
-            <!-- 色彩氛围 -->
-            @if (profile.preferences.colorAtmosphere) {
+        <!-- 客户画像 -->
+        <div class="card preferences-card">
+          <div class="card-header">
+            <h3 class="card-title">
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M430.11 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 11-35.5-3.6-47.7zM120 216c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm40 134c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm40-134c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm64 212c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm72-78c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm24-136c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"/></svg>
+              客户画像
+            </h3>
+          </div>
+          <div class="card-content">
+            <div class="preference-grid">
+              <!-- 风格偏好 -->
               <div class="preference-item">
                 <div class="preference-label">
-                  <ion-icon name="sunny-outline"></ion-icon>
-                  <span>色彩氛围</span>
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M452.37 59.63h0a40.49 40.49 0 00-57.26 0L184 294.74c23.08 4.7 46.12 27.29 49.26 49.26l219.11-211.11a40.49 40.49 0 000-57.26zM138 336c-29.88 0-54 24.5-54 54.86 0 23.95-20.88 36.57-36 36.57C64.56 449.74 92.82 464 120 464c39.78 0 72-32.73 72-73.14 0-30.36-24.12-54.86-54-54.86z"/></svg>
+                  <span>风格偏好</span>
                 </div>
                 <div class="preference-value">
-                  <ion-badge color="warning">
-                    {{ profile.preferences.colorAtmosphere }}
-                  </ion-badge>
+                  @if (profile.preferences.style.length > 0) {
+                    @for (style of profile.preferences.style; track style) {
+                      <span class="badge badge-tertiary">{{ style }}</span>
+                    }
+                  } @else {
+                    <span class="empty">未设置</span>
+                  }
                 </div>
               </div>
-            }
 
-            <!-- 需求类型 -->
-            @if (profile.preferences.demandType) {
+              <!-- 预算范围 -->
               <div class="preference-item">
                 <div class="preference-label">
-                  <ion-icon name="newspaper-outline"></ion-icon>
-                  <span>需求类型</span>
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M310.4 140.6c-17.8 0-32.4 14.2-32.4 31.7v151.4c0 17.5 14.6 31.7 32.4 31.7M201.6 140.6c17.8 0 32.4 14.2 32.4 31.7v151.4c0 17.5-14.6 31.7-32.4 31.7" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
+                  <span>预算范围</span>
                 </div>
                 <div class="preference-value">
-                  <ion-badge color="medium">
-                    {{ profile.preferences.demandType }}
-                  </ion-badge>
+                  <span class="badge badge-success">
+                    {{ formatBudget(profile.preferences.budget) }}
+                  </span>
                 </div>
               </div>
-            }
-          </div>
-        </ion-card-content>
-      </ion-card>
 
-      <!-- 所在群聊 -->
-      @if (profile.groups.length > 0) {
-        <ion-card class="groups-card">
-          <ion-card-header>
-            <ion-card-title>
-              <ion-icon name="chatbubbles-outline"></ion-icon>
-              所在群聊
-            </ion-card-title>
-            <ion-card-subtitle>点击可跳转到群聊</ion-card-subtitle>
-          </ion-card-header>
-          <ion-card-content>
-            <div class="groups-grid">
-              @for (item of profile.groups; track item.groupChat.id) {
-                <div
-                  class="group-item"
-                  (click)="navigateToGroupChat(item.groupChat.get('chat_id'))">
-                  <div class="group-info">
-                    <ion-icon name="people-circle-outline"></ion-icon>
-                    <div class="group-text">
-                      <h4>{{ item.groupChat.get('name') }}</h4>
-                      @if (item.project) {
-                        <p class="project-name">
-                          <ion-icon name="briefcase-outline"></ion-icon>
-                          {{ item.project.get('title') }}
-                        </p>
-                      } @else {
-                        <p class="no-project">暂无关联项目</p>
-                      }
-                    </div>
+              <!-- 色彩氛围 -->
+              @if (profile.preferences.colorAtmosphere) {
+                <div class="preference-item">
+                  <div class="preference-label">
+                    <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="208" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M256 48v208m121.57-38.13l-169.14 169.14m207.57-85.01H208"/></svg>
+                    <span>色彩氛围</span>
+                  </div>
+                  <div class="preference-value">
+                    <span class="badge badge-warning">
+                      {{ profile.preferences.colorAtmosphere }}
+                    </span>
                   </div>
-                  <ion-icon name="chevron-forward-outline" class="arrow"></ion-icon>
                 </div>
               }
-            </div>
-          </ion-card-content>
-        </ion-card>
-      }
 
-      <!-- 历史项目 -->
-      @if (profile.projects.length > 0) {
-        <ion-card class="projects-card">
-          <ion-card-header>
-            <ion-card-title>
-              <ion-icon name="folder-outline"></ion-icon>
-              历史项目
-            </ion-card-title>
-          </ion-card-header>
-          <ion-card-content>
-            <ion-list lines="full">
-              @for (project of profile.projects; track project.id) {
-                <ion-item
-                  button
-                  (click)="navigateToProject(project)"
-                  detail="true">
-                  <ion-label>
-                    <h2>{{ project.get('title') }}</h2>
-                    <p>
-                      <ion-badge [class]="getProjectStatusClass(project.get('status'))">
-                        {{ project.get('status') }}
-                      </ion-badge>
-                      <span style="margin-left: 8px;">
-                        {{ project.get('currentStage') }}
-                      </span>
-                    </p>
-                    <p class="ion-text-wrap">
-                      更新时间: {{ formatDate(project.get('updatedAt')) }}
-                    </p>
-                  </ion-label>
-                  <ion-icon name="chevron-forward-outline" slot="end"></ion-icon>
-                </ion-item>
+              <!-- 需求类型 -->
+              @if (profile.preferences.demandType) {
+                <div class="preference-item">
+                  <div class="preference-label">
+                    <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M368 415.86V72a24.07 24.07 0 00-24-24H72a24.07 24.07 0 00-24 24v352a40.12 40.12 0 0040 40h328" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path d="M416 464h0a48 48 0 01-48-48V128h72a24 24 0 0124 24v264a48 48 0 01-48 48z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M240 128h64m-64 64h64m-192 64h192m-192 64h192m-192 64h192"/><path d="M176 208h-64a16 16 0 01-16-16v-64a16 16 0 0116-16h64a16 16 0 0116 16v64a16 16 0 01-16 16z"/></svg>
+                    <span>需求类型</span>
+                  </div>
+                  <div class="preference-value">
+                    <span class="badge badge-medium">
+                      {{ profile.preferences.demandType }}
+                    </span>
+                  </div>
+                </div>
               }
-            </ion-list>
-          </ion-card-content>
-        </ion-card>
-      }
+            </div>
+          </div>
+        </div>
 
-      <!-- 跟进记录时间线 -->
-      @if (profile.followUpRecords.length > 0) {
-        <ion-card class="timeline-card">
-          <ion-card-header>
-            <ion-card-title>
-              <ion-icon name="time-outline"></ion-icon>
-              跟进记录
-            </ion-card-title>
-          </ion-card-header>
-          <ion-card-content>
-            <div class="timeline">
-              @for (record of profile.followUpRecords; track $index) {
-                <div class="timeline-item">
-                  <div class="timeline-dot">
-                    <ion-icon [name]="getFollowUpIcon(record.type)"></ion-icon>
+        <!-- 所在群聊 -->
+        @if (profile.groups.length > 0) {
+          <div class="card groups-card">
+            <div class="card-header">
+              <h3 class="card-title">
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M87.48 380c1.2-4.38-1.43-10.47-3.94-14.86a42.63 42.63 0 00-2.54-3.8 199.81 199.81 0 01-33-110C47.65 139.09 140.73 48 255.83 48 356.21 48 440 117.54 459.58 209.85a199 199 0 014.42 41.64c0 112.41-89.49 204.93-204.59 204.93-18.3 0-43-4.6-56.47-8.37s-26.92-8.77-30.39-10.11a31.09 31.09 0 00-11.12-2.07 30.71 30.71 0 00-12.09 2.43l-67.83 24.48a16 16 0 01-4.67 1.22 9.6 9.6 0 01-9.57-9.74 15.85 15.85 0 01.6-3.29z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/></svg>
+                所在群聊
+              </h3>
+              <p class="card-subtitle">点击可跳转到群聊</p>
+            </div>
+            <div class="card-content">
+              <div class="groups-grid">
+                @for (item of profile.groups; track item.groupChat.id) {
+                  <div class="group-item" (click)="navigateToGroupChat(item.groupChat.get('chat_id'))">
+                    <div class="group-info">
+                      <svg class="icon icon-group" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M402 168c-2.93 40.67-33.1 72-66 72s-63.12-31.32-66-72c-3-42.31 26.37-72 66-72s69 30.46 66 72z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M336 304c-65.17 0-127.84 32.37-143.54 95.41-2.08 8.34 3.15 16.59 11.72 16.59h263.65c8.57 0 13.77-8.25 11.72-16.59C463.85 335.36 401.18 304 336 304z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M200 185.94c-2.34 32.48-26.72 58.06-53 58.06s-50.7-25.57-53-58.06C91.61 152.15 115.34 128 147 128s55.39 24.77 53 57.94z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path d="M206 306c-18.05-8.27-37.93-11.45-59-11.45-52 0-102.1 25.85-114.65 76.2-1.65 6.66 2.53 13.25 9.37 13.25H154" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/></svg>
+                      <div class="group-text">
+                        <h4>{{ item.groupChat.get('name') }}</h4>
+                        @if (item.project) {
+                          <p class="project-name">
+                            <svg class="icon-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M336 264.13V352h86.13A80 80 0 00336 264.13z"/><path d="M256 232v-80a24 24 0 00-24-24H88a24 24 0 00-24 24v144a24 24 0 0024 24h144a24 24 0 0024-24v-40a8 8 0 018-8c3.55 0 8.3 2.6 10.41 5.67l46.78 68.14a24 24 0 0019.79 10.19h81.19z"/><path d="M264 216a40 40 0 1140 40 40 40 0 01-40-40z"/></svg>
+                            {{ item.project.get('title') }}
+                          </p>
+                        } @else {
+                          <p class="no-project">暂无关联项目</p>
+                        }
+                      </div>
+                    </div>
+                    <svg class="icon arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>
                   </div>
-                  <div class="timeline-content">
-                    <div class="timeline-time">{{ formatDate(record.time) }}</div>
-                    <div class="timeline-text">
-                      <strong>{{ record.operator }}</strong>
-                      <p>{{ record.content }}</p>
+                }
+              </div>
+            </div>
+          </div>
+        }
+
+        <!-- 历史项目 -->
+        @if (profile.projects.length > 0) {
+          <div class="card projects-card">
+            <div class="card-header">
+              <h3 class="card-title">
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M440 432H72a40 40 0 01-40-40V120a40 40 0 0140-40h75.89a40 40 0 0122.19 6.72l27.84 18.56a40 40 0 0022.19 6.72H440a40 40 0 0140 40v240a40 40 0 01-40 40z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
+                历史项目
+              </h3>
+            </div>
+            <div class="card-content">
+              <div class="project-list">
+                @for (project of profile.projects; track project.id) {
+                  <div class="project-item" (click)="navigateToProject(project)">
+                    <div class="project-content">
+                      <h4>{{ project.get('title') }}</h4>
+                      <p class="project-status">
+                        <span class="badge" [class]="getProjectStatusClass(project.get('status'))">
+                          {{ project.get('status') }}
+                        </span>
+                        <span class="stage">{{ project.get('currentStage') }}</span>
+                      </p>
+                      <p class="project-time">
+                        更新时间: {{ formatDate(project.get('updatedAt')) }}
+                      </p>
                     </div>
+                    <svg class="icon arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>
                   </div>
-                </div>
-              }
+                }
+              </div>
             </div>
-          </ion-card-content>
-        </ion-card>
-      }
-    </div>
-  }
-</ion-content>
+          </div>
+        }
+
+        <!-- 跟进记录时间线 -->
+        @if (profile.followUpRecords.length > 0) {
+          <div class="card timeline-card">
+            <div class="card-header">
+              <h3 class="card-title">
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 64C150 64 64 150 64 256s86 192 192 192 192-86 192-192S362 64 256 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 128v144h96"/></svg>
+                跟进记录
+              </h3>
+            </div>
+            <div class="card-content">
+              <div class="timeline">
+                @for (record of profile.followUpRecords; track $index) {
+                  <div class="timeline-item">
+                    <div class="timeline-dot">
+                      <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-50.22 116.82C218.45 151.39 236.28 144 256 144s37.39 7.44 50.11 20.94c12.89 13.68 19.16 32.06 17.68 51.82C320.83 256 290.43 288 256 288s-64.89-32-67.79-71.25c-1.47-19.92 4.79-38.36 17.57-51.93zM256 432a175.49 175.49 0 01-126-53.22 122.91 122.91 0 0135.14-33.44C190.63 329 222.89 320 256 320s65.37 9 90.83 25.34A122.87 122.87 0 01382 378.78 175.45 175.45 0 01256 432z"/></svg>
+                    </div>
+                    <div class="timeline-content">
+                      <div class="timeline-time">{{ formatDate(record.time) }}</div>
+                      <div class="timeline-text">
+                        <strong>{{ record.operator }}</strong>
+                        <p>{{ record.content }}</p>
+                      </div>
+                    </div>
+                  </div>
+                }
+              </div>
+            </div>
+          </div>
+        }
+      </div>
+    }
+  </div>
+</div>

+ 582 - 301
src/modules/project/pages/contact/contact.component.scss

@@ -1,4 +1,78 @@
-// 客户画像组件样式
+// 客户画像组件样式 - 使用纯 div+scss 实现
+
+// CSS 变量定义
+:host {
+  --primary-color: #3880ff;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #10dc60;
+  --warning-color: #ffce00;
+  --danger-color: #f04141;
+  --dark-color: #222428;
+  --medium-color: #989aa2;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+}
+
+.contact-page {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f5f5f5;
+}
+
+// 头部导航
+.header {
+  background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+  color: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  position: sticky;
+  top: 0;
+  z-index: 100;
+
+  .header-toolbar {
+    display: flex;
+    align-items: center;
+    padding: 12px 16px;
+    max-height: 56px;
+
+    .back-button {
+      background: none;
+      border: none;
+      color: white;
+      padding: 8px;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 50%;
+      transition: background-color 0.3s;
+
+      &:hover {
+        background-color: rgba(255, 255, 255, 0.1);
+      }
+
+      .icon {
+        width: 24px;
+        height: 24px;
+      }
+    }
+
+    .title {
+      flex: 1;
+      margin: 0 16px;
+      font-size: 18px;
+      font-weight: 600;
+    }
+  }
+}
+
+// 内容区域
+.content {
+  flex: 1;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+}
 
 // 加载和错误容器
 .loading-container,
@@ -11,378 +85,586 @@
   padding: 20px;
   text-align: center;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
+  .spinner {
+    width: 48px;
+    height: 48px;
+    border: 4px solid var(--light-color);
+    border-top-color: var(--primary-color);
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
     margin-bottom: 16px;
   }
 
-  ion-icon {
-    font-size: 64px;
-    color: var(--ion-color-danger);
+  .icon-large {
+    width: 64px;
+    height: 64px;
+    color: var(--danger-color);
     margin-bottom: 16px;
   }
 
   p {
-    color: var(--ion-color-medium);
+    color: var(--medium-color);
     margin-bottom: 16px;
   }
 }
 
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+// 通用按钮样式
+.btn {
+  padding: 12px 24px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: none;
+  outline: none;
+
+  &.btn-outline {
+    background: white;
+    color: var(--primary-color);
+    border: 2px solid var(--primary-color);
+
+    &:hover {
+      background: var(--primary-color);
+      color: white;
+    }
+  }
+}
+
 // 客户画像容器
 .contact-container {
   max-width: 800px;
   margin: 0 auto;
-  padding-bottom: 20px;
+  padding: 16px;
+  padding-bottom: 32px;
+}
 
-  // 通用卡片样式
-  ion-card {
-    margin-bottom: 16px;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+// 通用卡片样式
+.card {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 16px;
+  overflow: hidden;
+
+  .card-header {
+    padding: 16px;
+    border-bottom: 1px solid var(--light-shade);
 
-    ion-card-title {
+    .card-title {
       display: flex;
       align-items: center;
       gap: 8px;
+      margin: 0;
       font-size: 16px;
       font-weight: 600;
+      color: var(--dark-color);
 
-      ion-icon {
-        font-size: 20px;
-        color: var(--ion-color-primary);
+      .icon {
+        width: 20px;
+        height: 20px;
+        color: var(--primary-color);
       }
     }
 
-    ion-card-subtitle {
-      color: var(--ion-color-medium);
+    .card-subtitle {
+      margin: 4px 0 0;
       font-size: 12px;
-      margin-top: 4px;
+      color: var(--medium-color);
     }
   }
 
-  // 头部卡片
-  .header-card {
-    background: linear-gradient(135deg, var(--ion-color-primary) 0%, var(--ion-color-secondary) 100%);
-    color: white;
+  .card-content {
+    padding: 16px;
+  }
+}
 
-    .customer-header {
+// 头部卡片
+.header-card {
+  background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+  color: white;
+
+  .customer-header {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    padding: 16px;
+
+    .customer-avatar {
+      width: 80px;
+      height: 80px;
+      border: 3px solid white;
+      border-radius: 50%;
+      overflow: hidden;
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
       display: flex;
       align-items: center;
-      gap: 16px;
-      margin-bottom: 16px;
+      justify-content: center;
+      background: rgba(255, 255, 255, 0.2);
 
-      .customer-avatar {
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+
+      .icon-avatar {
         width: 80px;
         height: 80px;
-        border: 3px solid white;
-        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
-
-        ion-icon {
-          font-size: 80px;
-          color: white;
-        }
+        color: white;
       }
+    }
 
-      .customer-info {
-        flex: 1;
+    .customer-info {
+      flex: 1;
+      min-width: 0;
 
-        h2 {
-          margin: 0 0 8px;
-          font-size: 24px;
-          font-weight: 700;
-          color: white;
-        }
+      h2 {
+        margin: 0 0 8px;
+        font-size: 24px;
+        font-weight: 700;
+        color: white;
+      }
 
-        .source-badge {
-          display: inline-flex;
-          align-items: center;
-          gap: 6px;
-          padding: 4px 12px;
-          background-color: rgba(255, 255, 255, 0.2);
-          border-radius: 12px;
-          font-size: 13px;
+      .source-badge {
+        display: inline-flex;
+        align-items: center;
+        gap: 6px;
+        padding: 4px 12px;
+        background-color: rgba(255, 255, 255, 0.2);
+        border-radius: 12px;
+        font-size: 13px;
 
-          ion-icon {
-            font-size: 16px;
-          }
+        .icon {
+          width: 16px;
+          height: 16px;
         }
       }
     }
+  }
 
-    .tags-container {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
+  .tags-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    padding: 0 16px 16px;
+  }
+}
 
-      ion-chip {
-        --background: rgba(255, 255, 255, 0.2);
-        --color: white;
-        margin: 0;
-        height: 28px;
+// Chip 组件
+.chip {
+  display: inline-flex;
+  align-items: center;
+  padding: 6px 12px;
+  border-radius: 16px;
+  font-size: 12px;
+  font-weight: 500;
+  background: rgba(255, 255, 255, 0.2);
+  color: white;
+
+  &.chip-primary {
+    background: rgba(255, 255, 255, 0.2);
+    color: white;
+  }
+}
+
+// Badge 组件
+.badge {
+  display: inline-block;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 11px;
+  font-weight: 600;
+
+  &.badge-primary {
+    background: var(--primary-color);
+    color: white;
+  }
+
+  &.badge-secondary {
+    background: var(--secondary-color);
+    color: white;
+  }
+
+  &.badge-tertiary {
+    background: var(--tertiary-color);
+    color: white;
+  }
+
+  &.badge-success {
+    background: var(--success-color);
+    color: white;
+  }
+
+  &.badge-warning {
+    background: var(--warning-color);
+    color: var(--dark-color);
+  }
+
+  &.badge-medium {
+    background: var(--medium-color);
+    color: white;
+  }
+}
+
+// 图标样式
+.icon {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+
+  &.icon-sm {
+    width: 14px;
+    height: 14px;
+  }
+
+  &.icon-wechat {
+    width: 24px;
+    height: 24px;
+    color: #09BB07;
+  }
+
+  &.icon-group {
+    width: 40px;
+    height: 40px;
+  }
+
+  &.arrow {
+    width: 20px;
+    height: 20px;
+    color: var(--medium-color);
+  }
+}
+
+// 基础信息卡片
+.basic-info-card {
+  .info-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .info-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+
+    .icon {
+      width: 24px;
+      height: 24px;
+      color: var(--primary-color);
+      flex-shrink: 0;
+    }
+
+    .info-text {
+      flex: 1;
+      min-width: 0;
+
+      .info-label {
+        margin: 0 0 4px;
+        color: var(--medium-color);
         font-size: 12px;
       }
+
+      .info-value {
+        margin: 0;
+        color: var(--dark-color);
+        font-size: 15px;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .permission-notice {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px;
+    margin-top: 12px;
+    background-color: var(--light-color);
+    border-radius: 8px;
+    font-size: 13px;
+    color: var(--medium-color);
+
+    .icon {
+      width: 18px;
+      height: 18px;
+      flex-shrink: 0;
     }
   }
+}
+
+// 客户画像卡片
+.preferences-card {
+  .preference-grid {
+    display: grid;
+    gap: 16px;
+
+    .preference-item {
+      .preference-label {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        margin-bottom: 8px;
+        font-size: 13px;
+        color: var(--medium-color);
+        font-weight: 500;
 
-  // 基础信息卡片
-  .basic-info-card {
-    ion-list {
-      ion-item {
-        --padding-start: 0;
-        margin-bottom: 12px;
-
-        ion-icon {
-          font-size: 24px;
-          color: var(--ion-color-primary);
-          margin-right: 12px;
+        .icon {
+          width: 18px;
+          height: 18px;
         }
+      }
 
-        ion-label {
-          p {
-            color: var(--ion-color-medium);
-            font-size: 12px;
-            margin-bottom: 4px;
-          }
+      .preference-value {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 6px;
 
-          h3 {
-            color: var(--ion-color-dark);
-            font-size: 15px;
-            font-weight: 500;
-            margin: 0;
-          }
+        .empty {
+          color: var(--medium-color);
+          font-size: 13px;
+          font-style: italic;
         }
       }
     }
+  }
+}
 
-    .permission-notice {
+// 群聊卡片
+.groups-card {
+  .groups-grid {
+    display: grid;
+    gap: 12px;
+
+    .group-item {
       display: flex;
       align-items: center;
-      gap: 8px;
+      justify-content: space-between;
       padding: 12px;
-      margin-top: 12px;
-      background-color: var(--ion-color-light);
+      background-color: var(--light-color);
       border-radius: 8px;
-      font-size: 13px;
-      color: var(--ion-color-medium);
+      cursor: pointer;
+      transition: all 0.3s;
 
-      ion-icon {
-        font-size: 18px;
-        flex-shrink: 0;
+      &:hover {
+        background-color: var(--light-shade);
+        transform: translateX(4px);
       }
-    }
-  }
 
-  // 客户画像卡片
-  .preferences-card {
-    .preference-grid {
-      display: grid;
-      gap: 16px;
-
-      .preference-item {
-        .preference-label {
-          display: flex;
-          align-items: center;
-          gap: 6px;
-          margin-bottom: 8px;
-          font-size: 13px;
-          color: var(--ion-color-medium);
-          font-weight: 500;
+      &:active {
+        transform: translateX(2px);
+      }
 
-          ion-icon {
-            font-size: 18px;
-          }
-        }
+      .group-info {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        flex: 1;
+        min-width: 0;
 
-        .preference-value {
-          display: flex;
-          flex-wrap: wrap;
-          gap: 6px;
+        .group-text {
+          flex: 1;
+          min-width: 0;
+
+          h4 {
+            margin: 0 0 4px;
+            font-size: 15px;
+            font-weight: 600;
+            color: var(--dark-color);
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+          }
 
-          ion-badge {
+          .project-name {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            margin: 0;
             font-size: 12px;
-            padding: 6px 12px;
+            color: var(--success-color);
+
+            .icon-sm {
+              width: 14px;
+              height: 14px;
+            }
           }
 
-          .empty {
-            color: var(--ion-color-medium);
-            font-size: 13px;
+          .no-project {
+            margin: 0;
+            font-size: 12px;
+            color: var(--medium-color);
             font-style: italic;
           }
         }
       }
     }
   }
+}
 
-  // 群聊卡片
-  .groups-card {
-    .groups-grid {
-      display: grid;
-      gap: 12px;
+// 历史项目卡片
+.projects-card {
+  .project-list {
+    display: flex;
+    flex-direction: column;
+    gap: 0;
+  }
 
-      .group-item {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 12px;
-        background-color: var(--ion-color-light);
-        border-radius: 8px;
-        cursor: pointer;
-        transition: all 0.2s;
-
-        &:hover {
-          background-color: var(--ion-color-light-shade);
-          transform: translateX(4px);
-        }
+  .project-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 16px 8px;
+    cursor: pointer;
+    transition: background-color 0.3s;
+    border-bottom: 1px solid var(--light-shade);
+
+    &:last-child {
+      border-bottom: none;
+    }
 
-        .group-info {
-          display: flex;
-          align-items: center;
-          gap: 12px;
-          flex: 1;
+    &:hover {
+      background-color: var(--light-color);
+    }
 
-          > ion-icon {
-            font-size: 40px;
-            color: var(--ion-color-primary);
-            flex-shrink: 0;
-          }
+    &:active {
+      background-color: var(--light-shade);
+    }
 
-          .group-text {
-            flex: 1;
-            min-width: 0;
-
-            h4 {
-              margin: 0 0 4px;
-              font-size: 15px;
-              font-weight: 600;
-              color: var(--ion-color-dark);
-              white-space: nowrap;
-              overflow: hidden;
-              text-overflow: ellipsis;
-            }
+    .project-content {
+      flex: 1;
+      min-width: 0;
 
-            .project-name {
-              display: flex;
-              align-items: center;
-              gap: 4px;
-              margin: 0;
-              font-size: 12px;
-              color: var(--ion-color-success);
-
-              ion-icon {
-                font-size: 14px;
-              }
-            }
+      h4 {
+        margin: 0 0 4px;
+        font-size: 15px;
+        font-weight: 600;
+        color: var(--dark-color);
+      }
 
-            .no-project {
-              margin: 0;
-              font-size: 12px;
-              color: var(--ion-color-medium);
-              font-style: italic;
-            }
-          }
-        }
+      .project-status {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin: 4px 0;
 
-        .arrow {
-          font-size: 20px;
-          color: var(--ion-color-medium);
-          flex-shrink: 0;
+        .stage {
+          font-size: 13px;
+          color: var(--medium-color);
         }
       }
+
+      .project-time {
+        margin: 4px 0 0;
+        font-size: 12px;
+        color: var(--medium-color);
+      }
     }
   }
+}
 
-  // 历史项目卡片
-  .projects-card {
-    ion-item {
-      --padding-start: 8px;
-      cursor: pointer;
+// 项目状态样式
+.status-pending {
+  background: var(--warning-color) !important;
+  color: var(--dark-color) !important;
+}
 
-      h2 {
-        font-weight: 600;
-        margin-bottom: 4px;
-        color: var(--ion-color-dark);
-      }
+.status-active {
+  background: var(--primary-color) !important;
+  color: white !important;
+}
 
-      p {
-        font-size: 13px;
-        color: var(--ion-color-medium);
-        margin: 4px 0;
-      }
+.status-completed {
+  background: var(--success-color) !important;
+  color: white !important;
+}
 
-      ion-badge {
-        font-size: 11px;
-        padding: 4px 8px;
-        border-radius: 4px;
-      }
+.status-paused {
+  background: var(--medium-color) !important;
+  color: white !important;
+}
+
+.status-cancelled {
+  background: var(--danger-color) !important;
+  color: white !important;
+}
+
+.status-default {
+  background: var(--light-color) !important;
+  color: var(--dark-color) !important;
+}
+
+// 时间线卡片
+.timeline-card {
+  .timeline {
+    position: relative;
+    padding-left: 40px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 15px;
+      top: 8px;
+      bottom: 8px;
+      width: 2px;
+      background-color: var(--light-shade);
     }
-  }
 
-  // 时间线卡片
-  .timeline-card {
-    .timeline {
+    .timeline-item {
       position: relative;
-      padding-left: 40px;
+      margin-bottom: 24px;
 
-      &::before {
-        content: '';
-        position: absolute;
-        left: 15px;
-        top: 8px;
-        bottom: 8px;
-        width: 2px;
-        background-color: var(--ion-color-light-shade);
+      &:last-child {
+        margin-bottom: 0;
       }
 
-      .timeline-item {
-        position: relative;
-        margin-bottom: 24px;
-
-        &:last-child {
-          margin-bottom: 0;
+      .timeline-dot {
+        position: absolute;
+        left: -40px;
+        top: 2px;
+        width: 32px;
+        height: 32px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background-color: var(--primary-color);
+        border-radius: 50%;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+        .icon {
+          width: 18px;
+          height: 18px;
+          color: white;
         }
+      }
 
-        .timeline-dot {
-          position: absolute;
-          left: -40px;
-          top: 2px;
-          width: 32px;
-          height: 32px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          background-color: var(--ion-color-primary);
-          border-radius: 50%;
-          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-
-          ion-icon {
-            font-size: 18px;
-            color: white;
-          }
+      .timeline-content {
+        .timeline-time {
+          font-size: 11px;
+          color: var(--medium-color);
+          margin-bottom: 4px;
         }
 
-        .timeline-content {
-          .timeline-time {
-            font-size: 11px;
-            color: var(--ion-color-medium);
-            margin-bottom: 4px;
-          }
-
-          .timeline-text {
-            padding: 12px;
-            background-color: var(--ion-color-light);
-            border-radius: 8px;
+        .timeline-text {
+          padding: 12px;
+          background-color: var(--light-color);
+          border-radius: 8px;
 
-            strong {
-              font-size: 13px;
-              color: var(--ion-color-dark);
-            }
+          strong {
+            font-size: 13px;
+            color: var(--dark-color);
+          }
 
-            p {
-              margin: 4px 0 0;
-              font-size: 13px;
-              color: var(--ion-color-medium);
-              line-height: 1.5;
-            }
+          p {
+            margin: 4px 0 0;
+            font-size: 13px;
+            color: var(--medium-color);
+            line-height: 1.5;
           }
         }
       }
@@ -390,42 +672,11 @@
   }
 }
 
-// 项目状态徽标
-ion-badge {
-  &.status-pending {
-    --background: var(--ion-color-warning);
-    --color: white;
-  }
-
-  &.status-active {
-    --background: var(--ion-color-primary);
-    --color: white;
-  }
-
-  &.status-completed {
-    --background: var(--ion-color-success);
-    --color: white;
-  }
-
-  &.status-paused {
-    --background: var(--ion-color-medium);
-    --color: white;
-  }
-
-  &.status-cancelled {
-    --background: var(--ion-color-danger);
-    --color: white;
-  }
-
-  &.status-default {
-    --background: var(--ion-color-light);
-    --color: var(--ion-color-dark);
-  }
-}
-
 // 响应式适配
 @media (min-width: 768px) {
   .contact-container {
+    padding: 24px;
+
     .preference-grid {
       grid-template-columns: repeat(2, 1fr);
     }
@@ -435,3 +686,33 @@ ion-badge {
     }
   }
 }
+
+@media (max-width: 480px) {
+  .header {
+    .header-toolbar {
+      .title {
+        font-size: 16px;
+      }
+    }
+  }
+
+  .header-card {
+    .customer-header {
+      .customer-avatar {
+        width: 64px;
+        height: 64px;
+
+        .icon-avatar {
+          width: 64px;
+          height: 64px;
+        }
+      }
+
+      .customer-info {
+        h2 {
+          font-size: 20px;
+        }
+      }
+    }
+  }
+}

+ 1 - 1
src/modules/project/pages/contact/contact.component.ts

@@ -142,7 +142,7 @@ export class CustomerProfileComponent implements OnInit {
       }
 
       // 检查权限
-      const role = this.currentUser?.get('role');
+      const role = this.currentUser?.get('roleName');
       this.canViewSensitiveInfo = ['客服', '组长', '管理员'].includes(role);
 
       // 3. 加载客户信息(如果没有传入)

+ 46 - 31
src/modules/project/pages/project-detail/project-detail.component.html

@@ -1,18 +1,24 @@
-<ion-header>
-  <ion-toolbar>
-    <ion-buttons slot="start">
-      <ion-back-button (click)="goBack()" defaultHref="/"></ion-back-button>
-    </ion-buttons>
-    <ion-title>{{ project?.get('title') || '项目详情' }}</ion-title>
-    <ion-buttons slot="end">
-      <ion-button>
-        <ion-icon name="ellipsis-vertical-outline"></ion-icon>
-      </ion-button>
-    </ion-buttons>
-  </ion-toolbar>
+<div class="header">
+  <div class="toolbar">
+    <div class="buttons-start">
+      <button class="back-button" (click)="goBack()">
+        <svg class="icon" viewBox="0 0 512 512">
+          <path fill="currentColor" d="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48zm35.31 292.69a16 16 0 11-22.62 22.62l-96-96a16 16 0 010-22.62l96-96a16 16 0 0122.62 22.62L206.63 256z"/>
+        </svg>
+      </button>
+    </div>
+    <div class="title">{{ project?.get('title') || '项目详情' }}</div>
+    <div class="buttons-end">
+      <button class="icon-button">
+        <svg class="icon" viewBox="0 0 512 512">
+          <path fill="currentColor" d="M256 176a32 32 0 11-32 32 32 32 0 0132-32zM256 80a32 32 0 11-32 32 32 32 0 0132-32zM256 272a32 32 0 11-32 32 32 32 0 0132-32z"/>
+        </svg>
+      </button>
+    </div>
+  </div>
 
   <!-- 四阶段导航 -->
-  <ion-toolbar class="stage-toolbar">
+  <div class="stage-toolbar">
     <div class="stage-navigation">
       @for (stage of stages; track stage.id) {
         <div
@@ -23,7 +29,10 @@
           (click)="switchStage(stage.id)">
           <div class="stage-circle">
             @if (getStageStatus(stage.id) === 'completed') {
-              <ion-icon name="checkmark"></ion-icon>
+              <svg class="icon" viewBox="0 0 512 512">
+                <path fill="currentColor" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" opacity=".3"/>
+                <path fill="currentColor" d="M352 176L217.6 336 160 272"/>
+              </svg>
             } @else {
               <span>{{ stage.number }}</span>
             }
@@ -35,14 +44,16 @@
         }
       }
     </div>
-  </ion-toolbar>
-</ion-header>
+  </div>
+</div>
 
-<ion-content>
+<div class="content">
   <!-- 加载中 -->
   @if (loading) {
     <div class="loading-container">
-      <ion-spinner name="crescent"></ion-spinner>
+      <div class="spinner">
+        <div class="spinner-circle"></div>
+      </div>
       <p>加载项目信息...</p>
     </div>
   }
@@ -50,9 +61,11 @@
   <!-- 错误 -->
   @if (error && !loading) {
     <div class="error-container">
-      <ion-icon name="alert-circle-outline"></ion-icon>
+      <svg class="icon error-icon" viewBox="0 0 512 512">
+        <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm0 319.91a20 20 0 1120-20 20 20 0 01-20 20zm21.72-201.15l-5.74 122a16 16 0 01-32 0l-5.74-121.94v-.05a21.74 21.74 0 1143.44 0z"/>
+      </svg>
       <p>{{ error }}</p>
-      <ion-button (click)="loadData()" fill="outline">重试</ion-button>
+      <button class="button outline" (click)="loadData()">重试</button>
     </div>
   }
 
@@ -60,31 +73,33 @@
   @if (!loading && !error && project) {
     <!-- 客户信息快速查看卡片 -->
     <div class="customer-quick-view">
-      <ion-card>
-        <ion-card-content>
+      <div class="card">
+        <div class="card-content">
           <div class="customer-info">
-            <ion-avatar>
+            <div class="avatar">
               @if (customer?.get('data')?.avatar) {
                 <img [src]="customer?.get('data')?.avatar" alt="客户头像" />
               } @else {
-                <ion-icon name="person-circle-outline"></ion-icon>
+                <svg class="icon avatar-icon" viewBox="0 0 512 512">
+                  <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm0 60a60 60 0 11-60 60 60 60 0 0160-60zm0 336c-63.6 0-119.92-36.47-146.39-89.68C109.74 329.09 176.24 296 256 296s146.26 33.09 146.39 58.32C376.92 407.53 319.6 444 256 444z"/>
+                </svg>
               }
-            </ion-avatar>
+            </div>
             <div class="info-text">
               <h3>{{ customer?.get('name') }}</h3>
               @if (canViewCustomerPhone) {
                 <p>{{ customer?.get('mobile') }}</p>
               }
               <div class="tags">
-                <ion-badge color="primary">{{ customer?.get('source') }}</ion-badge>
-                <ion-badge [color]="project.get('status') === '进行中' ? 'success' : 'warning'">
+                <span class="badge badge-primary">{{ customer?.get('source') }}</span>
+                <span class="badge" [class.badge-success]="project.get('status') === '进行中'" [class.badge-warning]="project.get('status') !== '进行中'">
                   {{ project.get('status') }}
-                </ion-badge>
+                </span>
               </div>
             </div>
           </div>
-        </ion-card-content>
-      </ion-card>
+        </div>
+      </div>
     </div>
 
     <!-- 子路由内容(各阶段组件) -->
@@ -92,4 +107,4 @@
       <router-outlet></router-outlet>
     </div>
   }
-</ion-content>
+</div>

+ 814 - 42
src/modules/project/pages/project-detail/project-detail.component.scss

@@ -1,10 +1,94 @@
 // 项目详情核心组件样式
 
+// CSS Variables
+:host {
+  --primary-color: #3880ff;
+  --primary-rgb: 56, 128, 255;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #2dd36f;
+  --warning-color: #ffc409;
+  --danger-color: #eb445a;
+  --dark-color: #222428;
+  --medium-color: #92949c;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+  --white: #ffffff;
+}
+
+// Header Container
+.header {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background: var(--white);
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+// Toolbar
+.toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 56px;
+  padding: 0 16px;
+  background: var(--white);
+  border-bottom: 1px solid var(--light-shade);
+
+  .buttons-start,
+  .buttons-end {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    flex-shrink: 0;
+  }
+
+  .title {
+    flex: 1;
+    font-size: 20px;
+    font-weight: 600;
+    color: var(--dark-color);
+    text-align: center;
+    padding: 0 16px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .back-button,
+  .icon-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 40px;
+    border: none;
+    background: transparent;
+    border-radius: 50%;
+    cursor: pointer;
+    transition: background-color 0.2s;
+    padding: 0;
+
+    &:hover {
+      background-color: rgba(var(--primary-color), 0.1);
+    }
+
+    &:active {
+      background-color: rgba(var(--primary-color), 0.2);
+    }
+
+    .icon {
+      width: 24px;
+      height: 24px;
+      color: var(--primary-color);
+    }
+  }
+}
+
 // 阶段导航工具栏
 .stage-toolbar {
-  --background: white;
-  --border-width: 0 0 1px 0;
-  --border-color: var(--ion-color-light-shade);
+  background: var(--white);
+  border-bottom: 1px solid var(--light-shade);
   padding: 12px 0;
 
   .stage-navigation {
@@ -40,18 +124,19 @@
         font-weight: 600;
         font-size: 14px;
         transition: all 0.3s;
-        border: 2px solid var(--ion-color-light-shade);
-        background-color: white;
-        color: var(--ion-color-medium);
+        border: 2px solid var(--light-shade);
+        background-color: var(--white);
+        color: var(--medium-color);
 
-        ion-icon {
-          font-size: 20px;
+        .icon {
+          width: 20px;
+          height: 20px;
         }
       }
 
       .stage-label {
         font-size: 11px;
-        color: var(--ion-color-medium);
+        color: var(--medium-color);
         text-align: center;
         white-space: nowrap;
       }
@@ -59,28 +144,28 @@
       // 已完成状态
       &.completed {
         .stage-circle {
-          background-color: var(--ion-color-success);
-          border-color: var(--ion-color-success);
-          color: white;
+          background-color: var(--success-color);
+          border-color: var(--success-color);
+          color: var(--white);
         }
 
         .stage-label {
-          color: var(--ion-color-success);
+          color: var(--success-color);
         }
       }
 
       // 进行中状态
       &.active {
         .stage-circle {
-          background-color: var(--ion-color-primary);
-          border-color: var(--ion-color-primary);
-          color: white;
-          box-shadow: 0 0 0 4px rgba(var(--ion-color-primary-rgb), 0.2);
+          background-color: var(--primary-color);
+          border-color: var(--primary-color);
+          color: var(--white);
+          box-shadow: 0 0 0 4px rgba(56, 128, 255, 0.2);
           transform: scale(1.1);
         }
 
         .stage-label {
-          color: var(--ion-color-primary);
+          color: var(--primary-color);
           font-weight: 600;
         }
       }
@@ -88,9 +173,9 @@
       // 待开始状态
       &.pending {
         .stage-circle {
-          background-color: white;
-          border-color: var(--ion-color-light-shade);
-          color: var(--ion-color-medium);
+          background-color: var(--white);
+          border-color: var(--light-shade);
+          color: var(--medium-color);
         }
       }
     }
@@ -98,19 +183,30 @@
     .stage-connector {
       flex: 1;
       height: 2px;
-      background-color: var(--ion-color-light-shade);
+      background-color: var(--light-shade);
       margin: 0 8px;
       margin-bottom: 20px;
       transition: background-color 0.3s;
       min-width: 20px;
 
       &.completed {
-        background-color: var(--ion-color-success);
+        background-color: var(--success-color);
       }
     }
   }
 }
 
+// Content Container
+.content {
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 140px); // 减去 header 的高度
+  overflow-y: auto;
+  overflow-x: hidden;
+  -webkit-overflow-scrolling: touch;
+  background-color: var(--light-color);
+}
+
 // 加载和错误容器
 .loading-container,
 .error-container {
@@ -122,33 +218,89 @@
   padding: 20px;
   text-align: center;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
-    margin-bottom: 16px;
+  p {
+    color: var(--medium-color);
+    margin: 0;
+    font-size: 14px;
   }
+}
 
-  ion-icon {
-    font-size: 64px;
-    color: var(--ion-color-danger);
-    margin-bottom: 16px;
+// Spinner
+.spinner {
+  width: 48px;
+  height: 48px;
+  margin-bottom: 16px;
+  position: relative;
+
+  .spinner-circle {
+    width: 100%;
+    height: 100%;
+    border: 4px solid var(--light-shade);
+    border-top-color: var(--primary-color);
+    border-radius: 50%;
+    animation: spin 0.8s linear infinite;
   }
+}
 
-  p {
-    color: var(--ion-color-medium);
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+// Error Container
+.error-container {
+  .error-icon {
+    width: 64px;
+    height: 64px;
+    color: var(--danger-color);
     margin-bottom: 16px;
   }
+
+  .button {
+    margin-top: 16px;
+    padding: 10px 24px;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.2s;
+    background-color: var(--primary-color);
+    color: var(--white);
+
+    &.outline {
+      background-color: transparent;
+      border: 2px solid var(--primary-color);
+      color: var(--primary-color);
+
+      &:hover {
+        background-color: var(--primary-color);
+        color: var(--white);
+      }
+    }
+
+    &:active {
+      transform: scale(0.95);
+    }
+  }
 }
 
 // 客户快速查看
 .customer-quick-view {
   padding: 12px 12px 0;
 
-  ion-card {
+  .card {
     margin: 0;
+    background: var(--white);
+    border-radius: 12px;
     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    overflow: hidden;
 
-    ion-card-content {
+    .card-content {
       padding: 12px;
 
       .customer-info {
@@ -156,14 +308,27 @@
         align-items: center;
         gap: 12px;
 
-        ion-avatar {
+        .avatar {
           width: 48px;
           height: 48px;
           flex-shrink: 0;
+          border-radius: 50%;
+          overflow: hidden;
+          background-color: var(--light-color);
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
 
-          ion-icon {
-            font-size: 48px;
-            color: var(--ion-color-medium);
+          .avatar-icon {
+            width: 48px;
+            height: 48px;
+            color: var(--medium-color);
           }
         }
 
@@ -175,7 +340,7 @@
             margin: 0 0 4px;
             font-size: 16px;
             font-weight: 600;
-            color: var(--ion-color-dark);
+            color: var(--dark-color);
             white-space: nowrap;
             overflow: hidden;
             text-overflow: ellipsis;
@@ -184,7 +349,7 @@
           p {
             margin: 0 0 6px;
             font-size: 13px;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
           }
 
           .tags {
@@ -192,9 +357,28 @@
             flex-wrap: wrap;
             gap: 6px;
 
-            ion-badge {
+            .badge {
+              display: inline-block;
               font-size: 11px;
               padding: 4px 8px;
+              border-radius: 6px;
+              font-weight: 600;
+              white-space: nowrap;
+
+              &.badge-primary {
+                background-color: rgba(56, 128, 255, 0.15);
+                color: var(--primary-color);
+              }
+
+              &.badge-success {
+                background-color: rgba(45, 211, 111, 0.15);
+                color: var(--success-color);
+              }
+
+              &.badge-warning {
+                background-color: rgba(255, 196, 9, 0.15);
+                color: var(--warning-color);
+              }
             }
           }
         }
@@ -209,6 +393,449 @@
   padding-bottom: 80px; // 为底部操作栏留空间
 }
 
+// 通用卡片样式
+.card {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 16px;
+  overflow: hidden;
+
+  .card-header {
+    padding: 16px;
+    border-bottom: 1px solid var(--light-shade);
+
+    .card-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--dark-color);
+
+      .icon {
+        width: 20px;
+        height: 20px;
+        color: var(--primary-color);
+      }
+    }
+
+    .card-subtitle {
+      margin: 4px 0 0;
+      font-size: 12px;
+      color: var(--medium-color);
+    }
+  }
+
+  .card-content {
+    padding: 16px;
+  }
+}
+
+// Badge 组件增强
+.badge {
+  display: inline-block;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 11px;
+  font-weight: 600;
+
+  &.badge-secondary {
+    background: var(--secondary-color);
+    color: white;
+  }
+
+  &.badge-tertiary {
+    background: var(--tertiary-color);
+    color: white;
+  }
+
+  &.badge-danger {
+    background: var(--danger-color);
+    color: white;
+  }
+
+  &.badge-medium {
+    background: var(--medium-color);
+    color: white;
+  }
+
+  &.badge-light {
+    background: var(--light-color);
+    color: var(--dark-color);
+  }
+}
+
+// 图标样式
+.icon {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+
+  &.icon-sm {
+    width: 14px;
+    height: 14px;
+  }
+
+  &.icon-md {
+    width: 24px;
+    height: 24px;
+  }
+
+  &.icon-lg {
+    width: 32px;
+    height: 32px;
+  }
+}
+
+// 按钮样式
+.btn {
+  padding: 12px 24px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: none;
+  outline: none;
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+
+  &.btn-primary {
+    background: var(--primary-color);
+    color: white;
+
+    &:hover {
+      background: #2f6ce5;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(56, 128, 255, 0.3);
+    }
+
+    &:active {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-secondary {
+    background: var(--secondary-color);
+    color: white;
+
+    &:hover {
+      background: #0bb8d4;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(12, 209, 232, 0.3);
+    }
+
+    &:active {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-success {
+    background: var(--success-color);
+    color: white;
+
+    &:hover {
+      background: #28ba62;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(45, 211, 111, 0.3);
+    }
+
+    &:active {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-outline {
+    background: white;
+    color: var(--primary-color);
+    border: 2px solid var(--primary-color);
+
+    &:hover {
+      background: var(--primary-color);
+      color: white;
+    }
+
+    &:active {
+      transform: scale(0.98);
+    }
+  }
+
+  &.btn-light {
+    background: var(--light-color);
+    color: var(--dark-color);
+
+    &:hover {
+      background: var(--light-shade);
+    }
+
+    &:active {
+      transform: scale(0.98);
+    }
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    pointer-events: none;
+  }
+
+  .icon {
+    width: 20px;
+    height: 20px;
+  }
+}
+
+// 底部操作栏
+.footer-toolbar {
+  background: white;
+  border-top: 1px solid var(--light-shade);
+  padding: 12px 16px;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 90;
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+    max-width: 800px;
+    margin: 0 auto;
+
+    .btn {
+      flex: 1;
+    }
+  }
+}
+
+// 表单样式
+.form-group {
+  margin-bottom: 20px;
+
+  .form-label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--dark-color);
+
+    .required {
+      color: var(--danger-color);
+      margin-left: 4px;
+    }
+  }
+
+  .form-input,
+  .form-textarea,
+  .form-select {
+    width: 100%;
+    padding: 12px 16px;
+    border: 1px solid var(--light-shade);
+    border-radius: 8px;
+    font-size: 14px;
+    color: var(--dark-color);
+    background: white;
+    transition: all 0.3s;
+
+    &:focus {
+      outline: none;
+      border-color: var(--primary-color);
+      box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
+    }
+
+    &::placeholder {
+      color: var(--medium-color);
+    }
+
+    &:disabled {
+      background: var(--light-color);
+      cursor: not-allowed;
+    }
+  }
+
+  .form-textarea {
+    min-height: 100px;
+    resize: vertical;
+    font-family: inherit;
+  }
+
+  .form-help {
+    margin-top: 6px;
+    font-size: 12px;
+    color: var(--medium-color);
+  }
+
+  .form-error {
+    margin-top: 6px;
+    font-size: 12px;
+    color: var(--danger-color);
+  }
+}
+
+// 列表样式
+.list {
+  background: white;
+  border-radius: 12px;
+  overflow: hidden;
+
+  .list-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 16px;
+    border-bottom: 1px solid var(--light-shade);
+    cursor: pointer;
+    transition: background-color 0.3s;
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    &:hover {
+      background-color: var(--light-color);
+    }
+
+    &:active {
+      background-color: var(--light-shade);
+    }
+
+    .item-icon {
+      width: 40px;
+      height: 40px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 50%;
+      background-color: var(--light-color);
+      flex-shrink: 0;
+
+      .icon {
+        width: 24px;
+        height: 24px;
+        color: var(--primary-color);
+      }
+    }
+
+    .item-content {
+      flex: 1;
+      min-width: 0;
+
+      .item-title {
+        margin: 0 0 4px;
+        font-size: 15px;
+        font-weight: 600;
+        color: var(--dark-color);
+      }
+
+      .item-subtitle {
+        margin: 0;
+        font-size: 13px;
+        color: var(--medium-color);
+      }
+    }
+
+    .item-arrow {
+      width: 20px;
+      height: 20px;
+      color: var(--medium-color);
+      flex-shrink: 0;
+    }
+  }
+}
+
+// 空状态
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40px 20px;
+  text-align: center;
+
+  .icon-large {
+    width: 80px;
+    height: 80px;
+    color: var(--medium-color);
+    margin-bottom: 16px;
+    opacity: 0.5;
+  }
+
+  .empty-title {
+    margin: 0 0 8px;
+    font-size: 16px;
+    font-weight: 600;
+    color: var(--dark-color);
+  }
+
+  .empty-message {
+    margin: 0 0 20px;
+    font-size: 14px;
+    color: var(--medium-color);
+  }
+}
+
+// Chip 组件
+.chip {
+  display: inline-flex;
+  align-items: center;
+  padding: 6px 12px;
+  border-radius: 16px;
+  font-size: 12px;
+  font-weight: 500;
+  background: rgba(255, 255, 255, 0.2);
+  color: white;
+
+  &.chip-primary {
+    background: rgba(56, 128, 255, 0.15);
+    color: var(--primary-color);
+  }
+
+  &.chip-success {
+    background: rgba(45, 211, 111, 0.15);
+    color: var(--success-color);
+  }
+
+  &.chip-warning {
+    background: rgba(255, 196, 9, 0.15);
+    color: var(--warning-color);
+  }
+}
+
+// 项目状态样式
+.status-pending {
+  background: var(--warning-color) !important;
+  color: var(--dark-color) !important;
+}
+
+.status-active {
+  background: var(--primary-color) !important;
+  color: white !important;
+}
+
+.status-completed {
+  background: var(--success-color) !important;
+  color: white !important;
+}
+
+.status-paused {
+  background: var(--medium-color) !important;
+  color: white !important;
+}
+
+.status-cancelled {
+  background: var(--danger-color) !important;
+  color: white !important;
+}
+
+.status-default {
+  background: var(--light-color) !important;
+  color: var(--dark-color) !important;
+}
+
 // 响应式适配
 @media (min-width: 768px) {
   .stage-navigation {
@@ -221,6 +848,12 @@
     max-width: 800px;
     margin: 0 auto;
   }
+
+  .footer-toolbar {
+    .action-buttons {
+      max-width: 800px;
+    }
+  }
 }
 
 // 平板和桌面端
@@ -233,4 +866,143 @@
   .stage-content {
     max-width: 1000px;
   }
+
+  .footer-toolbar {
+    .action-buttons {
+      max-width: 1000px;
+    }
+  }
+}
+
+// 移动端优化
+@media (max-width: 480px) {
+  .header {
+    .toolbar {
+      padding: 0 12px;
+
+      .title {
+        font-size: 16px;
+      }
+    }
+  }
+
+  .stage-toolbar {
+    padding: 8px 0;
+
+    .stage-navigation {
+      .stage-item {
+        min-width: 50px;
+
+        .stage-circle {
+          width: 32px;
+          height: 32px;
+          font-size: 12px;
+
+          .icon {
+            width: 16px;
+            height: 16px;
+          }
+        }
+
+        .stage-label {
+          font-size: 10px;
+        }
+      }
+
+      .stage-connector {
+        min-width: 15px;
+        margin: 0 4px;
+        margin-bottom: 16px;
+      }
+    }
+  }
+
+  .customer-quick-view {
+    padding: 8px 8px 0;
+
+    .card {
+      .card-content {
+        padding: 10px;
+
+        .customer-info {
+          gap: 10px;
+
+          .avatar {
+            width: 40px;
+            height: 40px;
+
+            .avatar-icon {
+              width: 40px;
+              height: 40px;
+            }
+          }
+
+          .info-text {
+            h3 {
+              font-size: 14px;
+            }
+
+            p {
+              font-size: 12px;
+            }
+
+            .tags {
+              gap: 4px;
+
+              .badge {
+                font-size: 10px;
+                padding: 3px 6px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .stage-content {
+    padding: 8px;
+    padding-bottom: 70px;
+  }
+
+  .footer-toolbar {
+    padding: 8px 12px;
+
+    .action-buttons {
+      gap: 8px;
+
+      .btn {
+        padding: 10px 16px;
+        font-size: 13px;
+      }
+    }
+  }
+
+  .card {
+    .card-header {
+      padding: 12px;
+
+      .card-title {
+        font-size: 15px;
+      }
+    }
+
+    .card-content {
+      padding: 12px;
+    }
+  }
+
+  .form-group {
+    .form-input,
+    .form-textarea,
+    .form-select {
+      padding: 10px 12px;
+      font-size: 13px;
+    }
+  }
+
+  .btn {
+    padding: 10px 20px;
+    font-size: 13px;
+  }
 }

+ 1 - 1
src/modules/project/pages/project-detail/project-detail.component.ts

@@ -109,7 +109,7 @@ export class ProjectDetailComponent implements OnInit {
       }
 
       // 设置权限
-      this.role = this.currentUser?.get('role') || '';
+      this.role = this.currentUser?.get('roleName') || '';
       this.canEdit = ['客服', '组员', '组长', '管理员'].includes(this.role);
       this.canViewCustomerPhone = ['客服', '组长', '管理员'].includes(this.role);
 

+ 270 - 140
src/modules/project/pages/project-detail/stages/stage-aftercare.component.html

@@ -1,7 +1,9 @@
 <!-- 加载中 -->
 @if (loading) {
   <div class="loading-container">
-    <ion-spinner name="crescent"></ion-spinner>
+    <div class="spinner">
+      <div class="spinner-circle"></div>
+    </div>
     <p>加载售后信息...</p>
   </div>
 }
@@ -10,17 +12,22 @@
 @if (!loading) {
   <div class="stage-aftercare-container">
     <!-- 1. 尾款管理 -->
-    <ion-card class="payment-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="cash-outline"></ion-icon>
-          尾款管理
-        </ion-card-title>
-        <ion-badge [color]="getPaymentStatusColor()">
-          {{ getPaymentStatusText() }}
-        </ion-badge>
-      </ion-card-header>
-      <ion-card-content>
+    <div class="card payment-card">
+      <div class="card-header">
+        <div class="card-title-wrapper">
+          <h3 class="card-title">
+            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M53.12 199.94l400-151.39a8 8 0 0110.33 10.33l-151.39 400a8 8 0 01-15-.34l-67.4-166.09a16 16 0 00-10.11-10.11L53.46 215a8 8 0 01-.34-15.06z"/>
+              <circle cx="256" cy="256" r="16"/>
+            </svg>
+            尾款管理
+          </h3>
+          <span class="badge" [class]="'badge-' + getPaymentStatusColor()">
+            {{ getPaymentStatusText() }}
+          </span>
+        </div>
+      </div>
+      <div class="card-content">
         <div class="payment-summary">
           <div class="summary-item">
             <span class="label">总金额</span>
@@ -36,28 +43,32 @@
           </div>
         </div>
 
-        <ion-progress-bar
-          [value]="finalPayment.totalAmount > 0 ? finalPayment.paidAmount / finalPayment.totalAmount : 0"
-          color="success"></ion-progress-bar>
+        <!-- 进度条 -->
+        <div class="progress-bar">
+          <div
+            class="progress-fill"
+            [style.width.%]="finalPayment.totalAmount > 0 ? (finalPayment.paidAmount / finalPayment.totalAmount) * 100 : 0">
+          </div>
+        </div>
 
         <!-- 支付凭证列表 -->
         @if (finalPayment.paymentVouchers.length > 0) {
           <div class="vouchers-section">
             <h4>支付凭证</h4>
-            <ion-list lines="full">
+            <div class="list">
               @for (voucher of finalPayment.paymentVouchers; track $index) {
-                <ion-item>
-                  <ion-thumbnail slot="start">
+                <div class="list-item">
+                  <div class="thumbnail">
                     <img [src]="voucher.url" alt="支付凭证" />
-                  </ion-thumbnail>
-                  <ion-label>
+                  </div>
+                  <div class="item-content">
                     <h3>¥{{ voucher.amount.toLocaleString() }}</h3>
                     <p>{{ voucher.paymentMethod }}</p>
-                    <p>{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</p>
-                  </ion-label>
-                </ion-item>
+                    <p class="time">{{ voucher.paymentTime | date:'yyyy-MM-dd HH:mm' }}</p>
+                  </div>
+                </div>
               }
-            </ion-list>
+            </div>
           </div>
         }
 
@@ -69,27 +80,32 @@
             [disabled]="uploading"
             hidden
             #voucherInput />
-          <ion-button
-            expand="block"
-            fill="outline"
+          <button
+            class="btn btn-outline btn-block"
             (click)="voucherInput.click()"
             [disabled]="uploading">
-            <ion-icon name="camera-outline" slot="start"></ion-icon>
+            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path d="M350.54 148.68l-26.62-42.06C318.31 100.08 310.62 96 302 96h-92c-8.62 0-16.31 4.08-21.92 10.62l-26.62 42.06C155.85 155.23 148.62 160 140 160H80a32 32 0 00-32 32v192a32 32 0 0032 32h352a32 32 0 0032-32V192a32 32 0 00-32-32h-59c-8.65 0-16.85-4.77-22.46-11.32z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+              <circle cx="256" cy="272" r="80" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M124 158v-22h-24v22"/>
+            </svg>
             上传支付凭证
-          </ion-button>
+          </button>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 2. 客户评价 -->
-    <ion-card class="feedback-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="star-outline"></ion-icon>
+    <div class="card feedback-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path d="M480 208H308L256 48l-52 160H32l140 96-54 160 138-100 138 100-54-160z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+          </svg>
           客户评价
-        </ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+      </div>
+      <div class="card-content">
         @if (!customerFeedback.submitted) {
           <div class="feedback-form">
             <!-- 综合评分 -->
@@ -97,10 +113,18 @@
               <label>综合评分 <span class="required">*</span></label>
               <div class="stars">
                 @for (star of [1,2,3,4,5]; track star) {
-                  <ion-icon
-                    [name]="star <= customerFeedback.rating ? 'star' : 'star-outline'"
+                  <svg
+                    class="star-icon"
                     [class.active]="star <= customerFeedback.rating"
-                    (click)="setRating('rating', star)"></ion-icon>
+                    (click)="setRating('rating', star)"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (star <= customerFeedback.rating) {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
+                    } @else {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
                 }
               </div>
             </div>
@@ -110,10 +134,18 @@
               <label>服务态度</label>
               <div class="stars">
                 @for (star of [1,2,3,4,5]; track star) {
-                  <ion-icon
-                    [name]="star <= customerFeedback.serviceRating ? 'star' : 'star-outline'"
+                  <svg
+                    class="star-icon"
                     [class.active]="star <= customerFeedback.serviceRating"
-                    (click)="setRating('serviceRating', star)"></ion-icon>
+                    (click)="setRating('serviceRating', star)"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (star <= customerFeedback.serviceRating) {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
+                    } @else {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
                 }
               </div>
             </div>
@@ -122,10 +154,18 @@
               <label>设计质量</label>
               <div class="stars">
                 @for (star of [1,2,3,4,5]; track star) {
-                  <ion-icon
-                    [name]="star <= customerFeedback.qualityRating ? 'star' : 'star-outline'"
+                  <svg
+                    class="star-icon"
                     [class.active]="star <= customerFeedback.qualityRating"
-                    (click)="setRating('qualityRating', star)"></ion-icon>
+                    (click)="setRating('qualityRating', star)"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (star <= customerFeedback.qualityRating) {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
+                    } @else {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
                 }
               </div>
             </div>
@@ -134,53 +174,77 @@
               <label>交付及时性</label>
               <div class="stars">
                 @for (star of [1,2,3,4,5]; track star) {
-                  <ion-icon
-                    [name]="star <= customerFeedback.timelinessRating ? 'star' : 'star-outline'"
+                  <svg
+                    class="star-icon"
                     [class.active]="star <= customerFeedback.timelinessRating"
-                    (click)="setRating('timelinessRating', star)"></ion-icon>
+                    (click)="setRating('timelinessRating', star)"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (star <= customerFeedback.timelinessRating) {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
+                    } @else {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
                 }
               </div>
             </div>
 
             <!-- 文字评价 -->
-            <ion-item>
-              <ion-label position="stacked">评价内容</ion-label>
-              <ion-textarea
+            <div class="form-group">
+              <label class="form-label">评价内容</label>
+              <textarea
+                class="form-textarea"
                 [(ngModel)]="customerFeedback.comments"
                 rows="4"
-                placeholder="请分享您的体验和感受"></ion-textarea>
-            </ion-item>
+                placeholder="请分享您的体验和感受"></textarea>
+            </div>
 
-            <ion-item>
-              <ion-label position="stacked">改进建议</ion-label>
-              <ion-textarea
+            <div class="form-group">
+              <label class="form-label">改进建议</label>
+              <textarea
+                class="form-textarea"
                 [(ngModel)]="customerFeedback.improvements"
                 rows="3"
-                placeholder="您希望我们改进的地方"></ion-textarea>
-            </ion-item>
+                placeholder="您希望我们改进的地方"></textarea>
+            </div>
 
-            <ion-item lines="none">
-              <ion-checkbox [(ngModel)]="customerFeedback.wouldRecommend"></ion-checkbox>
-              <ion-label>我愿意推荐给朋友</ion-label>
-            </ion-item>
+            <div class="checkbox-item">
+              <input
+                type="checkbox"
+                id="recommend-checkbox"
+                [(ngModel)]="customerFeedback.wouldRecommend"
+                class="checkbox-input" />
+              <label for="recommend-checkbox" class="checkbox-label">我愿意推荐给朋友</label>
+            </div>
 
-            <ion-button
-              expand="block"
-              color="primary"
+            <button
+              class="btn btn-primary btn-block"
               (click)="submitFeedback()"
               [disabled]="saving">
-              <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+              </svg>
               提交评价
-            </ion-button>
+            </button>
           </div>
         } @else {
           <div class="feedback-result">
             <div class="rating-display">
               <div class="stars-large">
                 @for (star of [1,2,3,4,5]; track star) {
-                  <ion-icon
-                    [name]="star <= customerFeedback.rating ? 'star' : 'star-outline'"
-                    [class.active]="star <= customerFeedback.rating"></ion-icon>
+                  <svg
+                    class="star-icon"
+                    [class.active]="star <= customerFeedback.rating"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (star <= customerFeedback.rating) {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z"/>
+                    } @else {
+                      <path d="M394 480a16 16 0 01-9.39-3L256 383.76 127.39 477a16 16 0 01-24.55-18.08L153 310.35 23 221.2a16 16 0 019-29.2h160.38l48.4-148.95a16 16 0 0130.44 0l48.4 149H480a16 16 0 019.05 29.2L359 310.35l50.13 148.53A16 16 0 01394 480z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
                 }
               </div>
               <p class="rating-text">{{ customerFeedback.rating }}.0 分</p>
@@ -192,42 +256,53 @@
               </div>
             }
 
-            <ion-badge color="success">
-              <ion-icon name="checkmark-circle"></ion-icon>
+            <div class="badge badge-success badge-with-icon">
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+              </svg>
               已提交评价
-            </ion-badge>
+            </div>
           </div>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 3. 项目复盘 -->
-    <ion-card class="retrospective-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="analytics-outline"></ion-icon>
+    <div class="card retrospective-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path d="M104 496H72a24 24 0 01-24-24V328a24 24 0 0124-24h32a24 24 0 0124 24v144a24 24 0 01-24 24zM328 496h-32a24 24 0 01-24-24V232a24 24 0 0124-24h32a24 24 0 0124 24v240a24 24 0 01-24 24zM440 496h-32a24 24 0 01-24-24V120a24 24 0 0124-24h32a24 24 0 0124 24v352a24 24 0 01-24 24zM216 496h-32a24 24 0 01-24-24V40a24 24 0 0124-24h32a24 24 0 0124 24v432a24 24 0 01-24 24z"/>
+          </svg>
           项目复盘
-        </ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+      </div>
+      <div class="card-content">
         @if (!projectRetrospective) {
           <div class="empty-state">
-            <ion-icon name="document-text-outline"></ion-icon>
+            <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+              <path d="M256 56v120a32 32 0 0032 32h120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+            </svg>
             <p>尚未生成项目复盘</p>
             @if (canEdit) {
-              <ion-button
-                expand="block"
-                color="primary"
+              <button
+                class="btn btn-primary"
                 (click)="generateRetrospective()"
                 [disabled]="generating">
                 @if (generating) {
-                  <ion-spinner name="crescent" slot="start"></ion-spinner>
+                  <div class="spinner-small">
+                    <div class="spinner-circle"></div>
+                  </div>
                   生成中...
                 } @else {
-                  <ion-icon name="sparkles-outline" slot="start"></ion-icon>
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                    <path d="M208 352h-64a96 96 0 010-192h64m96 0h64a96 96 0 010 192h-64m-140.71-96h187.42" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="36"/>
+                  </svg>
                   生成复盘
                 }
-              </ion-button>
+              </button>
             }
           </div>
         } @else {
@@ -235,7 +310,13 @@
             <p class="summary">{{ projectRetrospective.summary }}</p>
 
             <div class="section">
-              <h4><ion-icon name="trophy-outline"></ion-icon> 项目亮点</h4>
+              <h4>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                  <path d="M464 256A208 208 0 1148 256a208 208 0 01416 0z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 160l-49.38 118.76L208 314.54M256 464c-114.88 0-208-93.12-208-208S141.12 48 256 48s208 93.12 208 208"/>
+                </svg>
+                项目亮点
+              </h4>
               <ul>
                 @for (item of projectRetrospective.highlights; track item) {
                   <li>{{ item }}</li>
@@ -244,7 +325,13 @@
             </div>
 
             <div class="section">
-              <h4><ion-icon name="alert-circle-outline"></ion-icon> 遇到的挑战</h4>
+              <h4>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                  <path d="M256 80c-8.66 0-16.58 7.36-16 16l8 216a8 8 0 008 8h0a8 8 0 008-8l8-216c.58-8.64-7.34-16-16-16z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                  <circle cx="256" cy="416" r="16" fill="currentColor"/>
+                </svg>
+                遇到的挑战
+              </h4>
               <ul>
                 @for (item of projectRetrospective.challenges; track item) {
                   <li>{{ item }}</li>
@@ -253,7 +340,12 @@
             </div>
 
             <div class="section">
-              <h4><ion-icon name="bulb-outline"></ion-icon> 经验教训</h4>
+              <h4>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                  <path d="M304 384v-24c0-29 31.54-56.43 52-76 28.84-27.57 44-64.61 44-108 0-80-63.73-144-144-144a143.6 143.6 0 00-144 144c0 41.84 15.81 81.39 44 108 20.35 19.21 52 46.7 52 76v24m16 96h64m-80-48h96m-48-48V256" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                </svg>
+                经验教训
+              </h4>
               <ul>
                 @for (item of projectRetrospective.lessons; track item) {
                   <li>{{ item }}</li>
@@ -262,7 +354,15 @@
             </div>
 
             <div class="section">
-              <h4><ion-icon name="compass-outline"></ion-icon> 改进建议</h4>
+              <h4>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                  <circle cx="256" cy="256" r="26" fill="currentColor"/>
+                  <circle cx="346" cy="256" r="26" fill="currentColor"/>
+                  <path d="M222 402.67l-75.68-34.55a16 16 0 01-9.05-13.67L120 203.5a16 16 0 0112.63-17.59l140.34-28.07a16 16 0 0117.59 12.63l17.22 150.82a16 16 0 01-13.09 18.11l-71.5 13.67a16 16 0 01-17.46-13.05z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/>
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                </svg>
+                改进建议
+              </h4>
               <ul>
                 @for (item of projectRetrospective.recommendations; track item) {
                   <li>{{ item }}</li>
@@ -271,75 +371,105 @@
             </div>
 
             @if (canEdit) {
-              <ion-button
-                expand="block"
-                fill="outline"
+              <button
+                class="btn btn-outline btn-block"
                 (click)="generateRetrospective()"
                 [disabled]="generating">
-                <ion-icon name="refresh-outline" slot="start"></ion-icon>
+                <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                  <path d="M320 146s24.36-12-64-12a160 160 0 10160 160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/>
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/>
+                </svg>
                 重新生成
-              </ion-button>
+              </button>
             }
           </div>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 4. 归档操作 -->
     @if (!archiveStatus.archived) {
       @if (canEdit) {
-        <ion-card class="archive-card">
-          <ion-card-content>
+        <div class="card archive-card">
+          <div class="card-content">
             <div class="archive-checklist">
               <h3>归档前检查</h3>
-              <ion-list lines="none">
-                <ion-item>
-                  <ion-icon
-                    [name]="finalPayment.status === 'completed' ? 'checkmark-circle' : 'ellipse-outline'"
-                    [color]="finalPayment.status === 'completed' ? 'success' : 'medium'"
-                    slot="start"></ion-icon>
-                  <ion-label>尾款已结清</ion-label>
-                </ion-item>
-                <ion-item>
-                  <ion-icon
-                    [name]="customerFeedback.submitted ? 'checkmark-circle' : 'ellipse-outline'"
-                    [color]="customerFeedback.submitted ? 'success' : 'medium'"
-                    slot="start"></ion-icon>
-                  <ion-label>客户已评价</ion-label>
-                </ion-item>
-                <ion-item>
-                  <ion-icon
-                    [name]="projectRetrospective ? 'checkmark-circle' : 'ellipse-outline'"
-                    [color]="projectRetrospective ? 'success' : 'medium'"
-                    slot="start"></ion-icon>
-                  <ion-label>项目复盘已完成</ion-label>
-                </ion-item>
-              </ion-list>
+              <div class="checklist">
+                <div class="checklist-item">
+                  <svg
+                    class="icon check-icon"
+                    [class.checked]="finalPayment.status === 'completed'"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (finalPayment.status === 'completed') {
+                      <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                      <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+                    } @else {
+                      <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
+                  <span>尾款已结清</span>
+                </div>
+                <div class="checklist-item">
+                  <svg
+                    class="icon check-icon"
+                    [class.checked]="customerFeedback.submitted"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (customerFeedback.submitted) {
+                      <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                      <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+                    } @else {
+                      <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
+                  <span>客户已评价</span>
+                </div>
+                <div class="checklist-item">
+                  <svg
+                    class="icon check-icon"
+                    [class.checked]="projectRetrospective"
+                    xmlns="http://www.w3.org/2000/svg"
+                    viewBox="0 0 512 512">
+                    @if (projectRetrospective) {
+                      <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+                      <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+                    } @else {
+                      <circle cx="256" cy="256" r="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                    }
+                  </svg>
+                  <span>项目复盘已完成</span>
+                </div>
+              </div>
             </div>
 
-            <ion-button
-              expand="block"
-              color="success"
-              size="large"
+            <button
+              class="btn btn-success btn-block btn-large"
               (click)="archiveProject()"
               [disabled]="saving || finalPayment.status !== 'completed' || !customerFeedback.submitted || !projectRetrospective">
-              <ion-icon name="archive-outline" slot="start"></ion-icon>
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                <path d="M64 164v244a56 56 0 0056 56h272a56 56 0 0056-56V164a4 4 0 00-4-4H68a4 4 0 00-4 4zm330 20a26 26 0 11-26 26 26 26 0 0126-26z"/>
+                <path d="M479.66 268.7l-32-151.81C441.48 83.77 417.68 64 384 64H128c-16.8 0-31 4.69-42.1 13.94S67.66 100 64.34 116.89L32.34 268.7a16 16 0 00-.34 3.3v144a64 64 0 0064 64h320a64 64 0 0064-64V272a16 16 0 00-.34-3.3zM368 320a16 16 0 01-32 0v-32a16 16 0 0132 0zm0-132a26 26 0 11-26 26 26 26 0 0126-26z"/>
+              </svg>
               归档项目
-            </ion-button>
-          </ion-card-content>
-        </ion-card>
+            </button>
+          </div>
+        </div>
       }
     } @else {
-      <ion-card class="archived-card">
-        <ion-card-content>
+      <div class="card archived-card">
+        <div class="card-content">
           <div class="archived-status">
-            <ion-icon name="checkmark-circle" color="success"></ion-icon>
+            <svg class="icon icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z"/>
+              <path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+            </svg>
             <h3>项目已归档</h3>
             <p>归档人: {{ archiveStatus.archivedBy?.name }}</p>
             <p>归档时间: {{ archiveStatus.archiveTime | date:'yyyy-MM-dd HH:mm' }}</p>
           </div>
-        </ion-card-content>
-      </ion-card>
+        </div>
+      </div>
     }
   </div>
 }

+ 764 - 167
src/modules/project/pages/project-detail/stages/stage-aftercare.component.scss

@@ -1,5 +1,22 @@
-// 售后归档阶段样式
+// 售后归档阶段样式 - 使用纯 div+scss 实现
+
+// CSS 变量定义
+:host {
+  --primary-color: #3880ff;
+  --primary-rgb: 56, 128, 255;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #2dd36f;
+  --warning-color: #ffc409;
+  --danger-color: #eb445a;
+  --dark-color: #222428;
+  --medium-color: #92949c;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+  --white: #ffffff;
+}
 
+// 加载容器
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -8,176 +25,762 @@
   min-height: 50vh;
   padding: 20px;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
-    margin-bottom: 16px;
+  p {
+    color: var(--medium-color);
+    margin: 0;
+    font-size: 14px;
+  }
+}
+
+// Spinner
+.spinner {
+  width: 48px;
+  height: 48px;
+  margin-bottom: 16px;
+  position: relative;
+
+  .spinner-circle {
+    width: 100%;
+    height: 100%;
+    border: 4px solid var(--light-shade);
+    border-top-color: var(--primary-color);
+    border-radius: 50%;
+    animation: spin 0.8s linear infinite;
+  }
+}
+
+.spinner-small {
+  width: 20px;
+  height: 20px;
+  position: relative;
+
+  .spinner-circle {
+    width: 100%;
+    height: 100%;
+    border: 3px solid rgba(255, 255, 255, 0.3);
+    border-top-color: white;
+    border-radius: 50%;
+    animation: spin 0.8s linear infinite;
+  }
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
   }
 }
 
+// 售后归档容器
 .stage-aftercare-container {
+  padding: 12px;
+  max-width: 800px;
+  margin: 0 auto;
+
   .required {
-    color: var(--ion-color-danger);
+    color: var(--danger-color);
+    margin-left: 4px;
   }
+}
 
-  ion-card {
-    margin-bottom: 16px;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+// 通用卡片样式
+.card {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 16px;
+  overflow: hidden;
 
-    ion-card-header {
+  .card-header {
+    padding: 16px;
+    border-bottom: 1px solid var(--light-shade);
+
+    .card-title-wrapper {
       display: flex;
       justify-content: space-between;
       align-items: center;
+      gap: 12px;
+    }
 
-      ion-card-title {
-        display: flex;
-        align-items: center;
-        gap: 8px;
+    .card-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--dark-color);
+      flex: 1;
+
+      .icon {
+        width: 20px;
+        height: 20px;
+        color: var(--primary-color);
+        flex-shrink: 0;
+      }
+    }
+  }
 
-        ion-icon {
-          font-size: 20px;
-          color: var(--ion-color-primary);
+  .card-content {
+    padding: 16px;
+  }
+}
+
+// Badge 组件
+.badge {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 12px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 600;
+  white-space: nowrap;
+
+  &.badge-primary {
+    background: var(--primary-color);
+    color: white;
+  }
+
+  &.badge-success {
+    background: var(--success-color);
+    color: white;
+  }
+
+  &.badge-warning {
+    background: var(--warning-color);
+    color: var(--dark-color);
+  }
+
+  &.badge-danger {
+    background: var(--danger-color);
+    color: white;
+  }
+
+  &.badge-medium {
+    background: var(--medium-color);
+    color: white;
+  }
+
+  &.badge-with-icon {
+    .icon {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+// 尾款管理卡片
+.payment-card {
+  .payment-summary {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 16px;
+    margin-bottom: 16px;
+
+    .summary-item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 8px;
+
+      .label {
+        font-size: 12px;
+        color: var(--medium-color);
+      }
+
+      .value {
+        font-size: 18px;
+        font-weight: 700;
+        color: var(--dark-color);
+
+        &.success {
+          color: var(--success-color);
+        }
+
+        &.warning {
+          color: var(--warning-color);
         }
       }
     }
   }
 
-  .payment-card {
-    .payment-summary {
-      display: grid;
-      grid-template-columns: repeat(3, 1fr);
-      gap: 16px;
-      margin-bottom: 16px;
+  // 进度条
+  .progress-bar {
+    width: 100%;
+    height: 6px;
+    background-color: var(--light-shade);
+    border-radius: 3px;
+    overflow: hidden;
+    margin-bottom: 20px;
+
+    .progress-fill {
+      height: 100%;
+      background-color: var(--success-color);
+      transition: width 0.3s ease;
+      border-radius: 3px;
+    }
+  }
 
-      .summary-item {
+  .vouchers-section {
+    margin-top: 20px;
+
+    h4 {
+      margin: 0 0 12px;
+      font-size: 14px;
+      font-weight: 600;
+      color: var(--dark-color);
+    }
+
+    .list {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+
+      .list-item {
         display: flex;
-        flex-direction: column;
         align-items: center;
-        gap: 8px;
+        gap: 12px;
+        padding: 12px;
+        background-color: var(--light-color);
+        border-radius: 8px;
 
-        .label {
-          font-size: 12px;
-          color: var(--ion-color-medium);
+        .thumbnail {
+          width: 60px;
+          height: 60px;
+          flex-shrink: 0;
+          border-radius: 6px;
+          overflow: hidden;
+          background-color: var(--light-shade);
+
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
         }
 
-        .value {
-          font-size: 18px;
-          font-weight: 700;
-          color: var(--ion-color-dark);
+        .item-content {
+          flex: 1;
+          min-width: 0;
 
-          &.success {
-            color: var(--ion-color-success);
+          h3 {
+            margin: 0 0 4px;
+            font-size: 16px;
+            font-weight: 600;
+            color: var(--dark-color);
           }
 
-          &.warning {
-            color: var(--ion-color-warning);
+          p {
+            margin: 2px 0;
+            font-size: 13px;
+            color: var(--medium-color);
+
+            &.time {
+              font-size: 12px;
+            }
           }
         }
       }
     }
+  }
+}
 
-    ion-progress-bar {
-      height: 6px;
-      border-radius: 3px;
+// 客户评价卡片
+.feedback-card {
+  .feedback-form {
+    .rating-section {
       margin-bottom: 20px;
+
+      label {
+        display: block;
+        margin-bottom: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        color: var(--dark-color);
+      }
+
+      .stars {
+        display: flex;
+        gap: 4px;
+
+        .star-icon {
+          width: 32px;
+          height: 32px;
+          color: var(--light-shade);
+          cursor: pointer;
+          transition: all 0.2s;
+
+          &.active {
+            color: var(--warning-color);
+          }
+
+          &:hover {
+            transform: scale(1.1);
+          }
+        }
+      }
     }
 
-    .vouchers-section {
-      margin-top: 20px;
+    .checkbox-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px;
+      background-color: var(--light-color);
+      border-radius: 8px;
+      margin-bottom: 16px;
+
+      .checkbox-input {
+        width: 20px;
+        height: 20px;
+        cursor: pointer;
+        accent-color: var(--primary-color);
+        margin: 0;
+      }
+
+      .checkbox-label {
+        flex: 1;
+        margin: 0;
+        font-size: 14px;
+        color: var(--dark-color);
+        cursor: pointer;
+      }
+    }
+  }
+
+  .feedback-result {
+    text-align: center;
+
+    .rating-display {
+      margin-bottom: 20px;
+
+      .stars-large {
+        display: flex;
+        justify-content: center;
+        gap: 8px;
+        margin-bottom: 12px;
+
+        .star-icon {
+          width: 40px;
+          height: 40px;
+          color: var(--light-shade);
+
+          &.active {
+            color: var(--warning-color);
+          }
+        }
+      }
+
+      .rating-text {
+        font-size: 24px;
+        font-weight: 700;
+        color: var(--dark-color);
+        margin: 0;
+      }
+    }
+
+    .comment-box {
+      padding: 16px;
+      background-color: var(--light-color);
+      border-radius: 8px;
+      margin-bottom: 16px;
+      text-align: left;
+
+      p {
+        margin: 0;
+        line-height: 1.6;
+        color: var(--medium-color);
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+// 项目复盘卡片
+.retrospective-card {
+  .empty-state {
+    text-align: center;
+    padding: 40px 20px;
+
+    .icon-large {
+      width: 64px;
+      height: 64px;
+      color: var(--medium-color);
+      margin-bottom: 16px;
+    }
+
+    p {
+      color: var(--medium-color);
+      margin-bottom: 16px;
+      font-size: 14px;
+    }
+  }
+
+  .retrospective-content {
+    .summary {
+      padding: 16px;
+      background-color: var(--light-color);
+      border-radius: 8px;
+      margin-bottom: 20px;
+      font-size: 14px;
+      line-height: 1.6;
+      color: var(--dark-color);
+    }
+
+    .section {
+      margin-bottom: 20px;
 
       h4 {
+        display: flex;
+        align-items: center;
+        gap: 8px;
         margin: 0 0 12px;
-        font-size: 14px;
+        font-size: 15px;
         font-weight: 600;
+        color: var(--dark-color);
+
+        .icon {
+          width: 20px;
+          height: 20px;
+          color: var(--primary-color);
+          flex-shrink: 0;
+        }
       }
 
-      ion-thumbnail {
-        width: 60px;
-        height: 60px;
+      ul {
+        margin: 0;
+        padding-left: 20px;
+
+        li {
+          margin-bottom: 8px;
+          font-size: 13px;
+          line-height: 1.5;
+          color: var(--medium-color);
+        }
       }
     }
   }
+}
 
-  .feedback-card {
-    .feedback-form {
-      .rating-section {
-        margin-bottom: 20px;
+// 归档卡片
+.archive-card {
+  .archive-checklist {
+    margin-bottom: 20px;
 
-        label {
-          display: block;
-          margin-bottom: 8px;
+    h3 {
+      margin: 0 0 16px;
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--dark-color);
+    }
+
+    .checklist {
+      background: var(--light-color);
+      border-radius: 8px;
+      padding: 12px;
+
+      .checklist-item {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        padding: 8px;
+
+        .check-icon {
+          width: 24px;
+          height: 24px;
+          color: var(--medium-color);
+          flex-shrink: 0;
+
+          &.checked {
+            color: var(--success-color);
+          }
+        }
+
+        span {
           font-size: 14px;
-          font-weight: 500;
-          color: var(--ion-color-dark);
+          color: var(--dark-color);
         }
+      }
+    }
+  }
+}
 
-        .stars {
-          display: flex;
-          gap: 4px;
+// 已归档卡片
+.archived-card {
+  background: linear-gradient(135deg, var(--success-color) 0%, #28ba62 100%);
 
-          ion-icon {
-            font-size: 32px;
-            color: var(--ion-color-light-shade);
-            cursor: pointer;
-            transition: all 0.2s;
+  .card-content {
+    padding: 24px;
+  }
 
-            &.active {
-              color: var(--ion-color-warning);
-            }
+  .archived-status {
+    text-align: center;
+    color: white;
 
-            &:hover {
-              transform: scale(1.1);
-            }
-          }
+    .icon-large {
+      width: 64px;
+      height: 64px;
+      color: white;
+      margin-bottom: 16px;
+    }
+
+    h3 {
+      margin: 0 0 16px;
+      font-size: 20px;
+      font-weight: 700;
+      color: white;
+    }
+
+    p {
+      margin: 4px 0;
+      font-size: 14px;
+      color: white;
+      opacity: 0.9;
+    }
+  }
+}
+
+// 表单组件
+.form-group {
+  margin-bottom: 16px;
+
+  .form-label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--dark-color);
+  }
+
+  .form-textarea {
+    width: 100%;
+    padding: 12px 16px;
+    border: 1px solid var(--light-shade);
+    border-radius: 8px;
+    font-size: 14px;
+    color: var(--dark-color);
+    background: white;
+    transition: all 0.3s;
+    font-family: inherit;
+    resize: vertical;
+    min-height: 100px;
+
+    &:focus {
+      outline: none;
+      border-color: var(--primary-color);
+      box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
+    }
+
+    &::placeholder {
+      color: var(--medium-color);
+    }
+
+    &:disabled {
+      background: var(--light-color);
+      cursor: not-allowed;
+    }
+  }
+}
+
+// 按钮样式
+.btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 12px 24px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: none;
+  outline: none;
+  text-align: center;
+
+  .icon {
+    width: 20px;
+    height: 20px;
+    flex-shrink: 0;
+  }
+
+  &.btn-primary {
+    background: var(--primary-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #2f6ce5;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-success {
+    background: var(--success-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #28ba62;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(45, 211, 111, 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-outline {
+    background: white;
+    color: var(--primary-color);
+    border: 2px solid var(--primary-color);
+
+    &:hover:not(:disabled) {
+      background: var(--primary-color);
+      color: white;
+    }
+
+    &:active:not(:disabled) {
+      transform: scale(0.98);
+    }
+  }
+
+  &.btn-block {
+    width: 100%;
+    display: flex;
+  }
+
+  &.btn-large {
+    padding: 14px 28px;
+    font-size: 16px;
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    pointer-events: none;
+  }
+}
+
+// 图标样式
+.icon {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+}
+
+// 响应式适配
+@media (min-width: 768px) {
+  .stage-aftercare-container {
+    padding: 24px;
+  }
+
+  .payment-summary {
+    gap: 24px !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .stage-aftercare-container {
+    padding: 8px;
+  }
+
+  .card {
+    .card-header {
+      padding: 12px;
+
+      .card-title {
+        font-size: 15px;
+
+        .icon {
+          width: 18px;
+          height: 18px;
         }
       }
+    }
 
-      ion-item {
-        --background: var(--ion-color-light);
-        border-radius: 8px;
-        margin-bottom: 12px;
+    .card-content {
+      padding: 12px;
+    }
+  }
+
+  .payment-card {
+    .payment-summary {
+      gap: 8px;
+
+      .summary-item {
+        .label {
+          font-size: 11px;
+        }
+
+        .value {
+          font-size: 16px;
+        }
       }
     }
 
-    .feedback-result {
-      text-align: center;
+    .vouchers-section {
+      .list {
+        .list-item {
+          padding: 10px;
 
-      .rating-display {
-        margin-bottom: 20px;
+          .thumbnail {
+            width: 50px;
+            height: 50px;
+          }
 
-        .stars-large {
-          display: flex;
-          justify-content: center;
-          gap: 8px;
-          margin-bottom: 12px;
+          .item-content {
+            h3 {
+              font-size: 14px;
+            }
 
-          ion-icon {
-            font-size: 40px;
+            p {
+              font-size: 12px;
 
-            &.active {
-              color: var(--ion-color-warning);
+              &.time {
+                font-size: 11px;
+              }
             }
           }
         }
+      }
+    }
+  }
 
-        .rating-text {
-          font-size: 24px;
-          font-weight: 700;
-          color: var(--ion-color-dark);
-          margin: 0;
+  .feedback-card {
+    .feedback-form {
+      .rating-section {
+        .stars {
+          gap: 2px;
+
+          .star-icon {
+            width: 28px;
+            height: 28px;
+          }
         }
       }
+    }
 
-      .comment-box {
-        padding: 16px;
-        background-color: var(--ion-color-light);
-        border-radius: 8px;
-        margin-bottom: 16px;
+    .feedback-result {
+      .rating-display {
+        .stars-large {
+          gap: 6px;
 
-        p {
-          margin: 0;
-          line-height: 1.6;
-          color: var(--ion-color-medium);
+          .star-icon {
+            width: 36px;
+            height: 36px;
+          }
+        }
+
+        .rating-text {
+          font-size: 20px;
         }
       }
     }
@@ -185,59 +788,35 @@
 
   .retrospective-card {
     .empty-state {
-      text-align: center;
-      padding: 40px 20px;
-
-      ion-icon {
-        font-size: 64px;
-        color: var(--ion-color-medium);
-        margin-bottom: 16px;
-      }
+      padding: 30px 16px;
 
-      p {
-        color: var(--ion-color-medium);
-        margin-bottom: 16px;
+      .icon-large {
+        width: 56px;
+        height: 56px;
       }
     }
 
     .retrospective-content {
       .summary {
-        padding: 16px;
-        background-color: var(--ion-color-light);
-        border-radius: 8px;
-        margin-bottom: 20px;
-        font-size: 14px;
-        line-height: 1.6;
-        color: var(--ion-color-dark);
+        padding: 12px;
+        font-size: 13px;
       }
 
       .section {
-        margin-bottom: 20px;
-
         h4 {
-          display: flex;
-          align-items: center;
-          gap: 8px;
-          margin: 0 0 12px;
-          font-size: 15px;
-          font-weight: 600;
-          color: var(--ion-color-dark);
-
-          ion-icon {
-            font-size: 20px;
-            color: var(--ion-color-primary);
+          font-size: 14px;
+
+          .icon {
+            width: 18px;
+            height: 18px;
           }
         }
 
         ul {
-          margin: 0;
-          padding-left: 20px;
+          padding-left: 16px;
 
           li {
-            margin-bottom: 8px;
-            font-size: 13px;
-            line-height: 1.5;
-            color: var(--ion-color-medium);
+            font-size: 12px;
           }
         }
       }
@@ -246,26 +825,23 @@
 
   .archive-card {
     .archive-checklist {
-      margin-bottom: 20px;
-
       h3 {
-        margin: 0 0 16px;
-        font-size: 16px;
-        font-weight: 600;
-        color: var(--ion-color-dark);
+        font-size: 15px;
       }
 
-      ion-list {
-        background: var(--ion-color-light);
-        border-radius: 8px;
-        padding: 8px;
+      .checklist {
+        padding: 10px;
 
-        ion-item {
-          --padding-start: 8px;
+        .checklist-item {
+          padding: 6px;
 
-          ion-icon[slot="start"] {
-            font-size: 24px;
-            margin-right: 12px;
+          .check-icon {
+            width: 20px;
+            height: 20px;
+          }
+
+          span {
+            font-size: 13px;
           }
         }
       }
@@ -273,29 +849,50 @@
   }
 
   .archived-card {
-    background: linear-gradient(135deg, var(--ion-color-success-tint), var(--ion-color-success-shade));
-    color: white;
-
-    .archived-status {
-      text-align: center;
+    .card-content {
       padding: 20px;
+    }
 
-      ion-icon {
-        font-size: 64px;
-        margin-bottom: 16px;
+    .archived-status {
+      .icon-large {
+        width: 56px;
+        height: 56px;
       }
 
       h3 {
-        margin: 0 0 16px;
-        font-size: 20px;
-        font-weight: 700;
+        font-size: 18px;
       }
 
       p {
-        margin: 4px 0;
-        font-size: 14px;
-        opacity: 0.9;
+        font-size: 13px;
       }
     }
   }
+
+  .btn {
+    padding: 10px 20px;
+    font-size: 13px;
+
+    &.btn-large {
+      padding: 12px 24px;
+      font-size: 14px;
+    }
+
+    .icon {
+      width: 18px;
+      height: 18px;
+    }
+  }
+
+  .form-group {
+    .form-textarea {
+      padding: 10px 12px;
+      font-size: 13px;
+    }
+  }
+
+  .badge {
+    font-size: 11px;
+    padding: 3px 10px;
+  }
 }

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

@@ -124,7 +124,7 @@ export class StageAftercareComponent implements OnInit {
         await this.wxworkService.initialize(this.cid, 'crm');
         this.currentUser = await this.wxworkService.getCurrentUser();
 
-        const role = this.currentUser?.get('role') || '';
+        const role = this.currentUser?.get('roleName') || '';
         this.canEdit = ['客服', '组长', '管理员'].includes(role);
       }
 

+ 104 - 74
src/modules/project/pages/project-detail/stages/stage-delivery.component.html

@@ -1,7 +1,9 @@
 <!-- 加载中 -->
 @if (loading) {
   <div class="loading-container">
-    <ion-spinner name="crescent"></ion-spinner>
+    <div class="spinner">
+      <div class="spinner-circle"></div>
+    </div>
     <p>加载交付信息...</p>
   </div>
 }
@@ -10,38 +12,43 @@
 @if (!loading) {
   <div class="stage-delivery-container">
     <!-- 完成进度卡片 -->
-    <ion-card class="progress-card">
-      <ion-card-content>
+    <div class="card progress-card">
+      <div class="card-content">
         <div class="progress-info">
           <h3>交付进度</h3>
           <div class="progress-value">{{ completionProgress }}%</div>
         </div>
-        <ion-progress-bar [value]="completionProgress / 100" color="success"></ion-progress-bar>
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width.%]="completionProgress"></div>
+        </div>
         <p class="progress-detail">
           已完成 {{ getApprovedCount() }} / {{ deliverables.length }} 项
         </p>
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 按空间分组的交付物 -->
     @for (group of deliverablesBySpace; track group.spaceName) {
-      <ion-card class="space-deliverables-card">
-        <ion-card-header>
-          <ion-card-title>
-            <ion-icon name="cube-outline"></ion-icon>
+      <div class="card space-deliverables-card">
+        <div class="card-header">
+          <h3 class="card-title">
+            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M448 341.37V170.61A32 32 0 00432.11 143l-152-88.46a47.94 47.94 0 00-48.24 0L79.89 143A32 32 0 0064 170.61v170.76A32 32 0 0079.89 369l152 88.46a48 48 0 0048.24 0l152-88.46A32 32 0 00448 341.37z"/>
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M69 153.99l187 110 187-110m-187 110v223"/>
+            </svg>
             {{ group.spaceName }}
-          </ion-card-title>
-        </ion-card-header>
-        <ion-card-content>
+          </h3>
+        </div>
+        <div class="card-content">
           @for (deliverable of group.items; track deliverable.processType) {
             <div class="deliverable-item">
               <div class="deliverable-header">
-                <ion-badge [color]="getProcessColor(deliverable.processType)">
+                <span class="badge" [class]="'badge-' + getProcessColor(deliverable.processType)">
                   {{ deliverable.processName }}
-                </ion-badge>
-                <ion-badge [color]="getStatusColor(deliverable.status)">
+                </span>
+                <span class="badge" [class]="'badge-' + getStatusColor(deliverable.status)">
                   {{ getStatusText(deliverable.status) }}
-                </ion-badge>
+                </span>
               </div>
 
               <!-- 文件列表 -->
@@ -55,17 +62,22 @@
                         @if (file.type === 'image') {
                           <img [src]="file.url" [alt]="file.name" />
                         } @else {
-                          <ion-icon name="document-outline"></ion-icon>
-                          <span>{{ file.name }}</span>
+                          <div class="file-icon">
+                            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                              <path d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+                              <path d="M256 56v120a32 32 0 0032 32h120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                            </svg>
+                            <span class="file-name">{{ file.name }}</span>
+                          </div>
                         }
                         @if (canEdit && isDesigner && deliverable.status === 'draft') {
-                          <ion-button
-                            fill="clear"
-                            size="small"
-                            color="danger"
+                          <button
+                            class="delete-button"
                             (click)="deleteFile(group.spaceName, deliverable.processType, $index)">
-                            <ion-icon name="close-outline"></ion-icon>
-                          </ion-button>
+                            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144m224 0L144 368"/>
+                            </svg>
+                          </button>
                         }
                       </div>
                     }
@@ -80,15 +92,16 @@
                     [disabled]="uploading"
                     hidden
                     #fileInput />
-                  <ion-button
-                    expand="block"
-                    fill="outline"
-                    size="small"
+                  <button
+                    class="btn btn-outline btn-block"
                     (click)="fileInput.click()"
                     [disabled]="uploading">
-                    <ion-icon name="cloud-upload-outline" slot="start"></ion-icon>
+                    <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                      <path d="M320 367.79h76c55 0 100-29.21 100-83.6s-53-81.47-96-83.6c-8.89-85.06-71-136.8-144-136.8-69 0-113.44 45.79-128 91.2-60 5.7-112 43.88-112 106.4s54 106.4 120 106.4h56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                      <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 255.79l-64-64-64 64m64 192.42V207.79"/>
+                    </svg>
                     上传文件
-                  </ion-button>
+                  </button>
                 }
               </div>
 
@@ -98,33 +111,34 @@
                   <div class="section-header">
                     <h4>质量自查</h4>
                     @if (!deliverable.qualityCheck) {
-                      <ion-button
-                        size="small"
-                        fill="outline"
+                      <button
+                        class="btn btn-outline btn-sm"
                         (click)="performQualityCheck(group.spaceName, deliverable.processType)">
                         开始自查
-                      </ion-button>
+                      </button>
                     }
                   </div>
 
                   @if (deliverable.qualityCheck) {
-                    <ion-list lines="none">
+                    <div class="checklist">
                       @for (item of deliverable.qualityCheck.items; track $index) {
-                        <ion-item>
-                          <ion-checkbox [(ngModel)]="item.passed"></ion-checkbox>
-                          <ion-label>{{ item.label }}</ion-label>
-                        </ion-item>
+                        <label class="checkbox-item">
+                          <input type="checkbox" [(ngModel)]="item.passed" />
+                          <span class="checkbox-label">{{ item.label }}</span>
+                        </label>
                       }
-                    </ion-list>
+                    </div>
 
-                    <ion-button
-                      expand="block"
-                      color="primary"
+                    <button
+                      class="btn btn-primary btn-block"
                       (click)="deliverable.qualityCheck.checked = true; submitForReview(group.spaceName, deliverable.processType)"
                       [disabled]="!isQualityCheckAllPassed(deliverable)">
-                      <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
+                      <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                        <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+                      </svg>
                       提交审核
-                    </ion-button>
+                    </button>
                   }
                 </div>
               }
@@ -133,29 +147,32 @@
               @if (isTeamLeader && deliverable.status === 'submitted') {
                 <div class="review-section">
                   <h4>审核操作</h4>
-                  <ion-item>
-                    <ion-label position="stacked">审核意见</ion-label>
-                    <ion-textarea
+                  <div class="form-group">
+                    <label class="form-label">审核意见</label>
+                    <textarea
+                      class="form-textarea"
                       rows="3"
                       placeholder="请填写审核意见"
-                      #reviewComments></ion-textarea>
-                  </ion-item>
+                      #reviewComments></textarea>
+                  </div>
 
                   <div class="review-buttons">
-                    <ion-button
-                      expand="block"
-                      color="success"
+                    <button
+                      class="btn btn-success"
                       (click)="reviewDeliverable(group.spaceName, deliverable.processType, 'approved', reviewComments.value || '')">
-                      <ion-icon name="checkmark-outline" slot="start"></ion-icon>
+                      <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/>
+                      </svg>
                       通过
-                    </ion-button>
-                    <ion-button
-                      expand="block"
-                      color="danger"
+                    </button>
+                    <button
+                      class="btn btn-danger"
                       (click)="reviewDeliverable(group.spaceName, deliverable.processType, 'rejected', reviewComments.value || '')">
-                      <ion-icon name="close-outline" slot="start"></ion-icon>
+                      <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144m224 0L144 368"/>
+                      </svg>
                       驳回
-                    </ion-button>
+                    </button>
                   </div>
                 </div>
               }
@@ -164,31 +181,44 @@
               @if (deliverable.review) {
                 <div class="review-result">
                   <div class="result-header">
-                    <ion-icon [name]="deliverable.review.result === 'approved' ? 'checkmark-circle' : 'close-circle'"
-                      [color]="deliverable.review.result === 'approved' ? 'success' : 'danger'"></ion-icon>
-                    <span>{{ deliverable.review.reviewedBy.name }}</span>
-                    <span class="time">{{ deliverable.review.reviewTime | date:'yyyy-MM-dd HH:mm' }}</span>
+                    <svg
+                      class="icon result-icon"
+                      [class.success]="deliverable.review.result === 'approved'"
+                      [class.danger]="deliverable.review.result === 'rejected'"
+                      xmlns="http://www.w3.org/2000/svg"
+                      viewBox="0 0 512 512">
+                      @if (deliverable.review.result === 'approved') {
+                        <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+                      } @else {
+                        <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 320L192 192m0 128l128-128"/>
+                      }
+                    </svg>
+                    <span class="reviewer-name">{{ deliverable.review.reviewedBy.name }}</span>
+                    <span class="review-time">{{ deliverable.review.reviewTime | date:'yyyy-MM-dd HH:mm' }}</span>
                   </div>
-                  <p>{{ deliverable.review.comments }}</p>
+                  <p class="review-comments">{{ deliverable.review.comments }}</p>
                 </div>
               }
             </div>
           }
-        </ion-card-content>
-      </ion-card>
+        </div>
+      </div>
     }
 
     <!-- 发起交付按钮 -->
     @if (canEdit && completionProgress === 100) {
-      <ion-button
-        expand="block"
-        color="primary"
-        size="large"
+      <button
+        class="btn btn-primary btn-large btn-block"
         (click)="initiateDelivery()"
         [disabled]="saving">
-        <ion-icon name="rocket-outline" slot="start"></ion-icon>
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+          <path d="M461.81 53.81a4.4 4.4 0 00-3.3-3.39c-54.38-13.3-180 34.09-248.13 102.17a294.9 294.9 0 00-33.09 39.08c-21-1.9-42-.3-59.88 7.5-50.49 22.2-65.18 80.18-69.28 105.07a9 9 0 009.8 10.4l81.07-8.9a180.29 180.29 0 001.1 18.3 18.15 18.15 0 005.3 11.09l31.39 31.39a18.15 18.15 0 0011.1 5.3 179.91 179.91 0 0018.19 1.1l-8.89 81a9 9 0 0010.39 9.79c24.9-4 83-18.69 105.07-69.17 7.8-17.9 9.4-38.79 7.6-59.69a293.91 293.91 0 0039.19-33.09c68.38-68 115.47-190.86 102.37-247.95zM298.66 213.67a42.7 42.7 0 1160.38 0 42.65 42.65 0 01-60.38 0z" fill="currentColor"/>
+          <path d="M109.64 352a45.06 45.06 0 00-26.35 12.84C65.67 382.52 64 448 64 448s65.52-1.67 83.15-19.31A44.73 44.73 0 00160 402.32" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+        </svg>
         完成交付
-      </ion-button>
+      </button>
     }
   </div>
 }

+ 816 - 48
src/modules/project/pages/project-detail/stages/stage-delivery.component.scss

@@ -1,5 +1,22 @@
-// 交付执行阶段样式
+// 交付执行阶段样式 - 纯 div+scss 实现
+
+// CSS 变量定义
+:host {
+  --primary-color: #3880ff;
+  --primary-rgb: 56, 128, 255;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #2dd36f;
+  --warning-color: #ffc409;
+  --danger-color: #eb445a;
+  --dark-color: #222428;
+  --medium-color: #92949c;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+  --white: #ffffff;
+}
 
+// 加载容器
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -8,95 +25,170 @@
   min-height: 50vh;
   padding: 20px;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
+  .spinner {
+    width: 48px;
+    height: 48px;
     margin-bottom: 16px;
+    position: relative;
+
+    .spinner-circle {
+      width: 100%;
+      height: 100%;
+      border: 4px solid var(--light-shade);
+      border-top-color: var(--primary-color);
+      border-radius: 50%;
+      animation: spin 0.8s linear infinite;
+    }
   }
 
   p {
-    color: var(--ion-color-medium);
+    color: var(--medium-color);
+    margin: 0;
+    font-size: 14px;
   }
 }
 
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+// 交付容器
 .stage-delivery-container {
-  ion-card {
-    margin-bottom: 16px;
+  // 通用卡片样式
+  .card {
+    background: white;
+    border-radius: 12px;
     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    margin-bottom: 16px;
+    overflow: hidden;
+
+    .card-header {
+      padding: 16px;
+      border-bottom: 1px solid var(--light-shade);
+
+      .card-title {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin: 0;
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--dark-color);
+
+        .icon {
+          width: 20px;
+          height: 20px;
+          color: var(--primary-color);
+          flex-shrink: 0;
+        }
+      }
+    }
+
+    .card-content {
+      padding: 16px;
+    }
   }
 
+  // 进度卡片
   .progress-card {
-    background: linear-gradient(135deg, var(--ion-color-primary), var(--ion-color-secondary));
+    background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
     color: white;
 
+    .card-content {
+      padding: 20px;
+    }
+
     .progress-info {
       display: flex;
       justify-content: space-between;
       align-items: center;
-      margin-bottom: 12px;
+      margin-bottom: 16px;
 
       h3 {
         margin: 0;
-        font-size: 16px;
+        font-size: 17px;
         font-weight: 600;
       }
 
       .progress-value {
-        font-size: 24px;
+        font-size: 28px;
         font-weight: 700;
+        text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
       }
     }
 
-    ion-progress-bar {
-      --background: rgba(255, 255, 255, 0.3);
-      --progress-background: white;
-      height: 8px;
-      border-radius: 4px;
+    // 纯 CSS 进度条
+    .progress-bar {
+      position: relative;
+      width: 100%;
+      height: 10px;
+      background-color: rgba(255, 255, 255, 0.3);
+      border-radius: 5px;
+      overflow: hidden;
+      box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
+
+      .progress-fill {
+        height: 100%;
+        background: linear-gradient(90deg, rgba(255, 255, 255, 0.9), white);
+        border-radius: 5px;
+        transition: width 0.4s ease;
+        box-shadow: 0 0 8px rgba(255, 255, 255, 0.6);
+      }
     }
 
     .progress-detail {
-      margin: 8px 0 0;
-      font-size: 13px;
-      opacity: 0.9;
+      margin: 12px 0 0;
+      font-size: 14px;
+      opacity: 0.95;
+      font-weight: 500;
     }
   }
 
+  // 空间交付物卡片
   .space-deliverables-card {
     .deliverable-item {
       padding: 16px;
-      background-color: var(--ion-color-light);
+      background-color: var(--light-color);
       border-radius: 8px;
       margin-bottom: 16px;
+      transition: all 0.3s;
 
       &:last-child {
         margin-bottom: 0;
       }
 
+      &:hover {
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+      }
+
+      // 交付物头部
       .deliverable-header {
         display: flex;
         gap: 8px;
         margin-bottom: 12px;
-
-        ion-badge {
-          font-size: 11px;
-          padding: 4px 10px;
-        }
+        flex-wrap: wrap;
       }
 
+      // 文件区域
       .files-section {
         margin-bottom: 16px;
 
         .empty-text {
-          color: var(--ion-color-medium);
+          color: var(--medium-color);
           font-size: 13px;
           font-style: italic;
-          margin: 8px 0;
+          margin: 12px 0;
+          text-align: center;
+          padding: 20px;
+          background: rgba(var(--medium-color), 0.05);
+          border-radius: 8px;
         }
 
         .files-grid {
           display: grid;
           grid-template-columns: repeat(3, 1fr);
-          gap: 8px;
+          gap: 10px;
           margin-bottom: 12px;
 
           .file-item {
@@ -105,97 +197,501 @@
             border-radius: 8px;
             overflow: hidden;
             background-color: white;
+            border: 1px solid var(--light-shade);
+            transition: all 0.3s;
+
+            &:hover {
+              box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+              transform: translateY(-2px);
+            }
 
+            // 图片样式
             img {
               width: 100%;
               height: 100%;
               object-fit: cover;
             }
 
-            ion-icon {
-              font-size: 48px;
-              color: var(--ion-color-medium);
+            // 文件图标
+            .file-icon {
+              width: 100%;
+              height: 100%;
+              display: flex;
+              flex-direction: column;
+              align-items: center;
+              justify-content: center;
+              gap: 6px;
+              padding: 8px;
+
+              .icon {
+                width: 40px;
+                height: 40px;
+                color: var(--primary-color);
+                flex-shrink: 0;
+              }
+
+              .file-name {
+                font-size: 11px;
+                color: var(--dark-color);
+                text-align: center;
+                word-break: break-all;
+                line-height: 1.3;
+                max-height: 36px;
+                overflow: hidden;
+                display: -webkit-box;
+                -webkit-line-clamp: 2;
+                -webkit-box-orient: vertical;
+              }
             }
 
-            ion-button {
+            // 删除按钮
+            .delete-button {
               position: absolute;
               top: 4px;
               right: 4px;
+              width: 28px;
+              height: 28px;
+              border: none;
+              background-color: rgba(235, 68, 90, 0.9);
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              cursor: pointer;
+              transition: all 0.3s;
+              padding: 0;
+              box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+
+              &:hover {
+                background-color: var(--danger-color);
+                transform: scale(1.1);
+              }
+
+              &:active {
+                transform: scale(0.95);
+              }
+
+              .icon {
+                width: 16px;
+                height: 16px;
+                color: white;
+              }
             }
           }
         }
       }
 
+      // 质量自查区域
       .quality-check-section,
       .review-section {
         padding-top: 16px;
-        border-top: 1px solid var(--ion-color-light-shade);
+        border-top: 1px solid var(--light-shade);
+        margin-top: 16px;
 
         .section-header {
           display: flex;
           justify-content: space-between;
           align-items: center;
           margin-bottom: 12px;
+
+          h4 {
+            margin: 0;
+            font-size: 15px;
+            font-weight: 600;
+            color: var(--dark-color);
+          }
         }
 
         h4 {
           margin: 0 0 12px;
-          font-size: 14px;
+          font-size: 15px;
           font-weight: 600;
-          color: var(--ion-color-dark);
+          color: var(--dark-color);
         }
 
-        ion-list {
+        // 复选框列表
+        .checklist {
           background: white;
           border-radius: 8px;
-          padding: 8px;
+          padding: 12px;
           margin-bottom: 12px;
 
-          ion-item {
-            --padding-start: 8px;
+          .checkbox-item {
+            display: flex;
+            align-items: center;
+            gap: 12px;
+            padding: 10px 8px;
+            cursor: pointer;
+            transition: background-color 0.3s;
+            border-radius: 6px;
+
+            &:hover {
+              background-color: var(--light-color);
+            }
+
+            input[type="checkbox"] {
+              width: 20px;
+              height: 20px;
+              cursor: pointer;
+              accent-color: var(--success-color);
+              flex-shrink: 0;
+            }
+
+            .checkbox-label {
+              flex: 1;
+              font-size: 14px;
+              color: var(--dark-color);
+              line-height: 1.5;
+            }
           }
         }
 
+        // 审核按钮
         .review-buttons {
           display: grid;
           grid-template-columns: 1fr 1fr;
           gap: 12px;
+          margin-top: 12px;
         }
       }
 
+      // 审核结果
       .review-result {
-        padding: 12px;
+        padding: 14px;
         background-color: white;
         border-radius: 8px;
         margin-top: 16px;
+        border: 1px solid var(--light-shade);
 
         .result-header {
           display: flex;
           align-items: center;
-          gap: 8px;
-          margin-bottom: 8px;
+          gap: 10px;
+          margin-bottom: 10px;
+
+          .result-icon {
+            width: 26px;
+            height: 26px;
+            flex-shrink: 0;
 
-          ion-icon {
-            font-size: 24px;
+            &.success {
+              color: var(--success-color);
+            }
+
+            &.danger {
+              color: var(--danger-color);
+            }
           }
 
-          .time {
+          .reviewer-name {
+            font-size: 14px;
+            font-weight: 600;
+            color: var(--dark-color);
+          }
+
+          .review-time {
             margin-left: auto;
             font-size: 11px;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
+            white-space: nowrap;
           }
         }
 
-        p {
+        .review-comments {
           margin: 0;
           font-size: 13px;
-          color: var(--ion-color-medium);
+          color: var(--dark-color);
+          line-height: 1.6;
+          padding: 8px 12px;
+          background: var(--light-color);
+          border-radius: 6px;
         }
       }
     }
   }
 }
 
+// Badge 组件
+.badge {
+  display: inline-block;
+  padding: 5px 11px;
+  border-radius: 12px;
+  font-size: 11px;
+  font-weight: 600;
+  white-space: nowrap;
+
+  &.badge-primary {
+    background: var(--primary-color);
+    color: white;
+  }
+
+  &.badge-secondary {
+    background: var(--secondary-color);
+    color: white;
+  }
+
+  &.badge-tertiary {
+    background: var(--tertiary-color);
+    color: white;
+  }
+
+  &.badge-success {
+    background: var(--success-color);
+    color: white;
+  }
+
+  &.badge-warning {
+    background: var(--warning-color);
+    color: var(--dark-color);
+  }
+
+  &.badge-danger {
+    background: var(--danger-color);
+    color: white;
+  }
+
+  &.badge-medium {
+    background: var(--medium-color);
+    color: white;
+  }
+
+  &.badge-light {
+    background: var(--light-color);
+    color: var(--dark-color);
+  }
+}
+
+// 按钮样式
+.btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 11px 20px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: none;
+  outline: none;
+  text-decoration: none;
+  white-space: nowrap;
+
+  .icon {
+    width: 18px;
+    height: 18px;
+    flex-shrink: 0;
+  }
+
+  &.btn-primary {
+    background: var(--primary-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #2f6ce5;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-secondary {
+    background: var(--secondary-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #0bb8d4;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(12, 209, 232, 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-success {
+    background: var(--success-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #28ba62;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(45, 211, 111, 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-danger {
+    background: var(--danger-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #d33850;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(235, 68, 90, 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-outline {
+    background: white;
+    color: var(--primary-color);
+    border: 2px solid var(--primary-color);
+
+    &:hover:not(:disabled) {
+      background: var(--primary-color);
+      color: white;
+    }
+
+    &:active:not(:disabled) {
+      transform: scale(0.98);
+    }
+  }
+
+  &.btn-light {
+    background: var(--light-color);
+    color: var(--dark-color);
+
+    &:hover:not(:disabled) {
+      background: var(--light-shade);
+    }
+
+    &:active:not(:disabled) {
+      transform: scale(0.98);
+    }
+  }
+
+  &.btn-sm {
+    padding: 6px 12px;
+    font-size: 12px;
+
+    .icon {
+      width: 16px;
+      height: 16px;
+    }
+  }
+
+  &.btn-large {
+    padding: 14px 28px;
+    font-size: 16px;
+
+    .icon {
+      width: 22px;
+      height: 22px;
+    }
+  }
+
+  &.btn-block {
+    display: flex;
+    width: 100%;
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    pointer-events: none;
+  }
+}
+
+// 表单样式
+.form-group {
+  margin-bottom: 16px;
+
+  .form-label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--dark-color);
+
+    .required {
+      color: var(--danger-color);
+      margin-left: 4px;
+    }
+  }
+
+  .form-input,
+  .form-textarea,
+  .form-select {
+    width: 100%;
+    padding: 12px 16px;
+    border: 1px solid var(--light-shade);
+    border-radius: 8px;
+    font-size: 14px;
+    color: var(--dark-color);
+    background: white;
+    transition: all 0.3s;
+    font-family: inherit;
+
+    &:focus {
+      outline: none;
+      border-color: var(--primary-color);
+      box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
+    }
+
+    &::placeholder {
+      color: var(--medium-color);
+    }
+
+    &:disabled {
+      background: var(--light-color);
+      cursor: not-allowed;
+    }
+  }
+
+  .form-textarea {
+    min-height: 80px;
+    resize: vertical;
+    line-height: 1.5;
+  }
+
+  .form-help {
+    margin-top: 6px;
+    font-size: 12px;
+    color: var(--medium-color);
+  }
+
+  .form-error {
+    margin-top: 6px;
+    font-size: 12px;
+    color: var(--danger-color);
+  }
+}
+
+// 图标样式
+.icon {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+
+  &.icon-sm {
+    width: 14px;
+    height: 14px;
+  }
+
+  &.icon-md {
+    width: 24px;
+    height: 24px;
+  }
+
+  &.icon-lg {
+    width: 32px;
+    height: 32px;
+  }
+}
+
+// 响应式适配
 @media (min-width: 768px) {
   .stage-delivery-container {
     .space-deliverables-card {
@@ -203,6 +699,278 @@
         .files-section {
           .files-grid {
             grid-template-columns: repeat(4, 1fr);
+            gap: 12px;
+          }
+        }
+      }
+    }
+  }
+}
+
+@media (min-width: 1024px) {
+  .stage-delivery-container {
+    .space-deliverables-card {
+      .deliverable-item {
+        .files-section {
+          .files-grid {
+            grid-template-columns: repeat(5, 1fr);
+          }
+        }
+      }
+    }
+  }
+}
+
+// 移动端优化
+@media (max-width: 480px) {
+  .loading-container {
+    .spinner {
+      width: 40px;
+      height: 40px;
+    }
+
+    p {
+      font-size: 13px;
+    }
+  }
+
+  .stage-delivery-container {
+    .card {
+      border-radius: 10px;
+
+      .card-header {
+        padding: 12px;
+
+        .card-title {
+          font-size: 15px;
+
+          .icon {
+            width: 18px;
+            height: 18px;
+          }
+        }
+      }
+
+      .card-content {
+        padding: 12px;
+      }
+    }
+
+    .progress-card {
+      .card-content {
+        padding: 16px;
+      }
+
+      .progress-info {
+        margin-bottom: 12px;
+
+        h3 {
+          font-size: 15px;
+        }
+
+        .progress-value {
+          font-size: 24px;
+        }
+      }
+
+      .progress-bar {
+        height: 8px;
+      }
+
+      .progress-detail {
+        margin-top: 10px;
+        font-size: 13px;
+      }
+    }
+
+    .space-deliverables-card {
+      .deliverable-item {
+        padding: 12px;
+
+        .files-section {
+          .files-grid {
+            grid-template-columns: repeat(2, 1fr);
+            gap: 8px;
+
+            .file-item {
+              .file-icon {
+                gap: 4px;
+                padding: 6px;
+
+                .icon {
+                  width: 32px;
+                  height: 32px;
+                }
+
+                .file-name {
+                  font-size: 10px;
+                }
+              }
+
+              .delete-button {
+                width: 24px;
+                height: 24px;
+
+                .icon {
+                  width: 14px;
+                  height: 14px;
+                }
+              }
+            }
+          }
+        }
+
+        .quality-check-section,
+        .review-section {
+          padding-top: 12px;
+          margin-top: 12px;
+
+          .section-header h4,
+          h4 {
+            font-size: 14px;
+          }
+
+          .checklist {
+            padding: 10px;
+
+            .checkbox-item {
+              padding: 8px 6px;
+
+              input[type="checkbox"] {
+                width: 18px;
+                height: 18px;
+              }
+
+              .checkbox-label {
+                font-size: 13px;
+              }
+            }
+          }
+
+          .review-buttons {
+            gap: 8px;
+          }
+        }
+
+        .review-result {
+          padding: 12px;
+
+          .result-header {
+            gap: 8px;
+            margin-bottom: 8px;
+
+            .result-icon {
+              width: 22px;
+              height: 22px;
+            }
+
+            .reviewer-name {
+              font-size: 13px;
+            }
+
+            .review-time {
+              font-size: 10px;
+            }
+          }
+
+          .review-comments {
+            font-size: 12px;
+            padding: 6px 10px;
+          }
+        }
+      }
+    }
+
+    .badge {
+      font-size: 10px;
+      padding: 4px 9px;
+    }
+
+    .btn {
+      padding: 9px 16px;
+      font-size: 13px;
+
+      &.btn-sm {
+        padding: 5px 10px;
+        font-size: 11px;
+      }
+
+      &.btn-large {
+        padding: 12px 24px;
+        font-size: 15px;
+      }
+
+      .icon {
+        width: 16px;
+        height: 16px;
+      }
+    }
+
+    .form-group {
+      .form-input,
+      .form-textarea,
+      .form-select {
+        padding: 10px 12px;
+        font-size: 13px;
+      }
+
+      .form-textarea {
+        min-height: 70px;
+      }
+
+      .form-label {
+        font-size: 13px;
+      }
+    }
+  }
+}
+
+// 超小屏幕优化
+@media (max-width: 360px) {
+  .stage-delivery-container {
+    .space-deliverables-card {
+      .deliverable-item {
+        .deliverable-header {
+          gap: 6px;
+        }
+
+        .files-section {
+          .files-grid {
+            gap: 6px;
+
+            .file-item {
+              .file-icon {
+                padding: 4px;
+
+                .icon {
+                  width: 28px;
+                  height: 28px;
+                }
+
+                .file-name {
+                  font-size: 9px;
+                }
+              }
+
+              .delete-button {
+                width: 22px;
+                height: 22px;
+                top: 2px;
+                right: 2px;
+
+                .icon {
+                  width: 12px;
+                  height: 12px;
+                }
+              }
+            }
+          }
+        }
+
+        .quality-check-section,
+        .review-section {
+          .review-buttons {
+            gap: 6px;
           }
         }
       }

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

@@ -154,7 +154,7 @@ export class StageDeliveryComponent implements OnInit {
       }
 
       // 设置用户角色和权限
-      this.role = this.currentUser?.get('role') || '';
+      this.role = this.currentUser?.get('roleName') || '';
       this.isDesigner = this.role === '组员';
       this.isTeamLeader = this.role === '组长';
       this.canEdit = ['组员', '组长', '管理员'].includes(this.role);

+ 165 - 127
src/modules/project/pages/project-detail/stages/stage-order.component.html

@@ -1,7 +1,9 @@
 <!-- 加载中 -->
 @if (loading) {
   <div class="loading-container">
-    <ion-spinner name="crescent"></ion-spinner>
+    <div class="spinner">
+      <div class="spinner-circle"></div>
+    </div>
     <p>加载订单信息...</p>
   </div>
 }
@@ -10,112 +12,127 @@
 @if (!loading) {
   <div class="stage-order-container">
     <!-- 1. 客户信息卡片 -->
-    <ion-card class="customer-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="person-outline"></ion-icon>
+    <div class="card customer-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M258.9 48C141.92 46.42 46.42 141.92 48 258.9c1.56 112.19 92.91 203.54 205.1 205.1 117 1.6 212.48-93.9 210.88-210.88C462.44 140.91 371.09 49.56 258.9 48zM385.32 375.25a4 4 0 01-6.14-.32 124.27 124.27 0 00-32.35-29.59C321.37 329 289.11 320 256 320s-65.37 9-90.83 25.34a124.24 124.24 0 00-32.35 29.58 4 4 0 01-6.14.32A175.32 175.32 0 0180 259c-1.63-97.31 78.22-178.76 175.57-179S432 158.81 432 256a175.32 175.32 0 01-46.68 119.25z"/><path fill="currentColor" d="M256 144c-19.72 0-37.55 7.39-50.22 20.82s-19 32-17.57 51.93C191.11 256 221.52 288 256 288s64.83-32 67.79-71.24c1.48-19.74-4.8-38.14-17.68-51.82C293.39 151.44 275.59 144 256 144z"/>
+          </svg>
           客户信息
-        </ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <ion-list lines="none">
-          <ion-item>
-            <ion-label>
-              <p>客户姓名</p>
-              <h3>{{ customer?.get('name') }}</h3>
-            </ion-label>
-          </ion-item>
-          <ion-item>
-            <ion-label>
-              <p>来源渠道</p>
-              <h3>{{ customer?.get('source') }}</h3>
-            </ion-label>
-          </ion-item>
+        </h3>
+      </div>
+      <div class="card-content">
+        <div class="info-list">
+          <div class="info-item">
+            <div class="info-text">
+              <p class="info-label">客户姓名</p>
+              <h4 class="info-value">{{ customer?.get('name') }}</h4>
+            </div>
+          </div>
+          <div class="info-item">
+            <div class="info-text">
+              <p class="info-label">来源渠道</p>
+              <h4 class="info-value">{{ customer?.get('source') }}</h4>
+            </div>
+          </div>
           @if (customer?.get('data')?.preferences) {
-            <ion-item>
-              <ion-label>
-                <p>风格偏好</p>
+            <div class="info-item">
+              <div class="info-text">
+                <p class="info-label">风格偏好</p>
                 <div class="preference-tags">
                   @for (style of customer?.get('data')?.preferences?.style || []; track style) {
-                    <ion-badge color="tertiary">{{ style }}</ion-badge>
+                    <span class="badge badge-tertiary">{{ style }}</span>
                   }
                 </div>
-              </ion-label>
-            </ion-item>
+              </div>
+            </div>
           }
-        </ion-list>
-      </ion-card-content>
-    </ion-card>
+        </div>
+      </div>
+    </div>
 
     <!-- 2. 项目基本信息 -->
-    <ion-card class="project-info-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="document-text-outline"></ion-icon>
+    <div class="card project-info-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z"/><path fill="currentColor" d="M256 56v120a32 32 0 0032 32h120"/>
+          </svg>
           项目基本信息
-        </ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <ion-list lines="full">
+        </h3>
+      </div>
+      <div class="card-content">
+        <div class="form-list">
           <!-- 项目名称 -->
-          <ion-item>
-            <ion-label position="stacked">项目名称 <span class="required">*</span></ion-label>
-            <ion-input
+          <div class="form-group">
+            <label class="form-label">项目名称 <span class="required">*</span></label>
+            <input
+              class="form-input"
+              type="text"
               [(ngModel)]="projectInfo.title"
               [disabled]="!canEdit"
-              placeholder="请输入项目名称"></ion-input>
-          </ion-item>
+              placeholder="请输入项目名称" />
+          </div>
 
           <!-- 项目类型 -->
-          <ion-item>
-            <ion-label position="stacked">项目类型 <span class="required">*</span></ion-label>
-            <ion-select
+          <div class="form-group">
+            <label class="form-label">项目类型 <span class="required">*</span></label>
+            <select
+              class="form-select"
               [(ngModel)]="projectInfo.type"
-              [disabled]="!canEdit"
-              placeholder="请选择项目类型">
-              <ion-select-option value="整屋设计">整屋设计</ion-select-option>
-              <ion-select-option value="局部改造">局部改造</ion-select-option>
-              <ion-select-option value="软装设计">软装设计</ion-select-option>
-            </ion-select>
-          </ion-item>
+              [disabled]="!canEdit">
+              <option value="">请选择项目类型</option>
+              <option value="整屋设计">整屋设计</option>
+              <option value="局部改造">局部改造</option>
+              <option value="软装设计">软装设计</option>
+            </select>
+          </div>
 
           <!-- 交付期限 -->
-          <ion-item>
-            <ion-label position="stacked">交付期限 <span class="required">*</span></ion-label>
-            <ion-input
+          <div class="form-group">
+            <label class="form-label">交付期限 <span class="required">*</span></label>
+            <input
+              class="form-input"
               type="date"
               [(ngModel)]="projectInfo.deadline"
               [disabled]="!canEdit"
-              placeholder="请选择交付期限"></ion-input>
-          </ion-item>
+              placeholder="请选择交付期限" />
+          </div>
 
           <!-- 项目描述 -->
-          <ion-item>
-            <ion-label position="stacked">项目描述</ion-label>
-            <ion-textarea
+          <div class="form-group">
+            <label class="form-label">项目描述</label>
+            <textarea
+              class="form-textarea"
               [(ngModel)]="projectInfo.description"
               [disabled]="!canEdit"
               rows="3"
-              placeholder="请输入项目描述"></ion-textarea>
-          </ion-item>
-        </ion-list>
-      </ion-card-content>
-    </ion-card>
+              placeholder="请输入项目描述"></textarea>
+          </div>
+        </div>
+      </div>
+    </div>
 
     <!-- 3. 报价明细 -->
-    <ion-card class="quotation-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="calculator-outline"></ion-icon>
+    <div class="card quotation-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <rect x="112" y="128" width="288" height="320" rx="48" ry="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 128V80a32 32 0 0132-32h96a32 32 0 0132 32v48"/>
+            <path fill="currentColor" d="M320 304h-32v-24a8 8 0 00-8-8h-48a8 8 0 00-8 8v24h-32a8 8 0 00-8 8v16a8 8 0 008 8h32v24a8 8 0 008 8h48a8 8 0 008-8v-24h32a8 8 0 008-8v-16a8 8 0 00-8-8z"/>
+          </svg>
           报价明细
-        </ion-card-title>
-        <ion-card-subtitle>按空间和工序配置价格</ion-card-subtitle>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+        <p class="card-subtitle">按空间和工序配置价格</p>
+      </div>
+      <div class="card-content">
         @for (space of quotation.spaces; track space.name) {
           <div class="space-section">
             <div class="space-header">
-              <ion-icon name="cube-outline"></ion-icon>
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                <path fill="currentColor" d="M440 432H72a40 40 0 01-40-40V120a40 40 0 0140-40h75.89a40 40 0 0122.19 6.72l27.84 18.56a40 40 0 0022.19 6.72H440a40 40 0 0140 40v240a40 40 0 01-40 40z"/>
+              </svg>
               <h3>{{ space.name }}</h3>
             </div>
 
@@ -125,37 +142,48 @@
                   class="process-item"
                   [class.enabled]="isProcessEnabled(space, processType.key)">
                   <div class="process-header" (click)="canEdit && toggleProcess(space, processType.key)">
-                    <ion-checkbox
-                      [checked]="isProcessEnabled(space, processType.key)"
-                      [disabled]="!canEdit"></ion-checkbox>
-                    <ion-badge [color]="processType.color">
+                    <label class="checkbox-wrapper">
+                      <input
+                        type="checkbox"
+                        class="checkbox-input"
+                        [checked]="isProcessEnabled(space, processType.key)"
+                        [disabled]="!canEdit" />
+                      <span class="checkbox-custom"></span>
+                    </label>
+                    <span class="badge" [attr.data-color]="processType.color">
                       {{ processType.name }}
-                    </ion-badge>
+                    </span>
                   </div>
 
                   @if (isProcessEnabled(space, processType.key)) {
                     <div class="process-inputs">
-                      <ion-item lines="none">
-                        <ion-label position="stacked">单价</ion-label>
-                        <ion-input
-                          type="number"
-                          [ngModel]="getProcessPrice(space, processType.key)"
-                          (ngModelChange)="setProcessPrice(space, processType.key, $event); onProcessChange()"
-                          [disabled]="!canEdit"
-                          placeholder="0"></ion-input>
-                        <ion-note slot="end">元/{{ getProcessUnit(space, processType.key) }}</ion-note>
-                      </ion-item>
+                      <div class="input-group">
+                        <label class="input-label">单价</label>
+                        <div class="input-with-note">
+                          <input
+                            class="input-field"
+                            type="number"
+                            [ngModel]="getProcessPrice(space, processType.key)"
+                            (ngModelChange)="setProcessPrice(space, processType.key, $event); onProcessChange()"
+                            [disabled]="!canEdit"
+                            placeholder="0" />
+                          <span class="input-note">元/{{ getProcessUnit(space, processType.key) }}</span>
+                        </div>
+                      </div>
 
-                      <ion-item lines="none">
-                        <ion-label position="stacked">数量</ion-label>
-                        <ion-input
-                          type="number"
-                          [ngModel]="getProcessQuantity(space, processType.key)"
-                          (ngModelChange)="setProcessQuantity(space, processType.key, $event); onProcessChange()"
-                          [disabled]="!canEdit"
-                          placeholder="0"></ion-input>
-                        <ion-note slot="end">{{ getProcessUnit(space, processType.key) }}</ion-note>
-                      </ion-item>
+                      <div class="input-group">
+                        <label class="input-label">数量</label>
+                        <div class="input-with-note">
+                          <input
+                            class="input-field"
+                            type="number"
+                            [ngModel]="getProcessQuantity(space, processType.key)"
+                            (ngModelChange)="setProcessQuantity(space, processType.key, $event); onProcessChange()"
+                            [disabled]="!canEdit"
+                            placeholder="0" />
+                          <span class="input-note">{{ getProcessUnit(space, processType.key) }}</span>
+                        </div>
+                      </div>
 
                       <div class="process-subtotal">
                         小计: ¥{{ calculateProcessSubtotal(space, processType.key).toFixed(2) }}
@@ -173,22 +201,26 @@
           <div class="total-label">报价总额</div>
           <div class="total-amount">¥{{ quotation.total.toFixed(2) }}</div>
         </div>
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 4. 设计师分配 -->
-    <ion-card class="designer-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="people-outline"></ion-icon>
+    <div class="card designer-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M402 168c-2.93 40.67-33.1 72-66 72s-63.12-31.32-66-72c-3-42.31 26.37-72 66-72s69 30.46 66 72z"/><path fill="currentColor" d="M336 304c-65.17 0-127.84 32.37-143.54 95.41-2.08 8.34 3.15 16.59 11.72 16.59h263.65c8.57 0 13.77-8.25 11.72-16.59C463.85 335.36 401.18 304 336 304z"/><path fill="currentColor" d="M200 185.94c-2.34 32.48-26.72 58.06-53 58.06s-50.7-25.57-53-58.06C91.61 152.15 115.34 128 147 128s55.39 24.77 53 57.94z"/><path fill="currentColor" d="M206 306c-18.05-8.27-37.93-11.45-59-11.45-52 0-102.1 25.85-114.65 76.2-1.65 6.66 2.53 13.25 9.37 13.25H154"/>
+          </svg>
           设计师分配
-        </ion-card-title>
-        <ion-card-subtitle>选择负责的组员</ion-card-subtitle>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+        <p class="card-subtitle">选择负责的组员</p>
+      </div>
+      <div class="card-content">
         @if (designers.length === 0) {
           <div class="empty-state">
-            <ion-icon name="person-outline"></ion-icon>
+            <svg class="icon-large" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path fill="currentColor" d="M258.9 48C141.92 46.42 46.42 141.92 48 258.9c1.56 112.19 92.91 203.54 205.1 205.1 117 1.6 212.48-93.9 210.88-210.88C462.44 140.91 371.09 49.56 258.9 48zM385.32 375.25a4 4 0 01-6.14-.32 124.27 124.27 0 00-32.35-29.59C321.37 329 289.11 320 256 320s-65.37 9-90.83 25.34a124.24 124.24 0 00-32.35 29.58 4 4 0 01-6.14.32A175.32 175.32 0 0180 259c-1.63-97.31 78.22-178.76 175.57-179S432 158.81 432 256a175.32 175.32 0 01-46.68 119.25z"/>
+            </svg>
             <p>暂无可用设计师</p>
           </div>
         } @else {
@@ -198,47 +230,53 @@
                 class="designer-item"
                 [class.selected]="selectedDesigner?.id === designer.id"
                 (click)="canEdit && selectDesigner(designer)">
-                <ion-avatar>
+                <div class="designer-avatar">
                   @if (designer.get('data')?.avatar) {
                     <img [src]="designer.get('data').avatar" alt="设计师头像" />
                   } @else {
-                    <ion-icon name="person-circle-outline"></ion-icon>
+                    <svg class="icon avatar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                      <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm0 60a60 60 0 11-60 60 60 60 0 0160-60zm0 336c-63.6 0-119.92-36.47-146.39-89.68C109.74 329.09 176.24 296 256 296s146.26 33.09 146.39 58.32C376.92 407.53 319.6 444 256 444z"/>
+                    </svg>
                   }
-                </ion-avatar>
+                </div>
                 <div class="designer-info">
                   <h4>{{ designer.get('name') }}</h4>
                   <p>{{ getDesignerWorkload(designer) }}</p>
                 </div>
                 @if (selectedDesigner?.id === designer.id) {
-                  <ion-icon name="checkmark-circle" class="selected-icon"></ion-icon>
+                  <svg class="icon selected-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                    <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-38 312.38L137.4 280.8a24 24 0 0133.94-33.94l50.2 50.2 95.74-95.74a24 24 0 0133.94 33.94z"/>
+                  </svg>
                 }
               </div>
             }
           </div>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 5. 操作按钮 -->
     @if (canEdit) {
       <div class="action-buttons">
-        <ion-button
-          expand="block"
-          fill="outline"
+        <button
+          class="btn btn-outline"
           (click)="saveDraft()"
           [disabled]="saving">
-          <ion-icon name="save-outline" slot="start"></ion-icon>
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z"/>
+          </svg>
           保存草稿
-        </ion-button>
+        </button>
 
-        <ion-button
-          expand="block"
-          color="primary"
+        <button
+          class="btn btn-primary"
           (click)="submitForApproval()"
           [disabled]="saving">
-          <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
+          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-38 312.38L137.4 280.8a24 24 0 0133.94-33.94l50.2 50.2 95.74-95.74a24 24 0 0133.94 33.94z"/>
+          </svg>
           提交审批
-        </ion-button>
+        </button>
       </div>
     }
   </div>

+ 606 - 110
src/modules/project/pages/project-detail/stages/stage-order.component.scss

@@ -1,4 +1,20 @@
-// 订单分配阶段样式
+// 订单分配阶段样式 - 纯 div+scss 实现
+
+// CSS 变量定义
+:host {
+  --primary-color: #3880ff;
+  --primary-rgb: 56, 128, 255;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #2dd36f;
+  --warning-color: #ffc409;
+  --danger-color: #eb445a;
+  --dark-color: #222428;
+  --medium-color: #92949c;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+  --white: #ffffff;
+}
 
 // 加载容器
 .loading-container {
@@ -9,65 +25,107 @@
   min-height: 50vh;
   padding: 20px;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
+  .spinner {
+    width: 48px;
+    height: 48px;
     margin-bottom: 16px;
+    position: relative;
+
+    .spinner-circle {
+      width: 100%;
+      height: 100%;
+      border: 4px solid var(--light-shade);
+      border-top-color: var(--primary-color);
+      border-radius: 50%;
+      animation: spin 0.8s linear infinite;
+    }
   }
 
   p {
-    color: var(--ion-color-medium);
+    color: var(--medium-color);
+    margin: 0;
+    font-size: 14px;
+  }
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
   }
 }
 
 // 订单分配容器
 .stage-order-container {
+  padding: 0;
+
   // 通用卡片样式
-  ion-card {
-    margin-bottom: 16px;
+  .card {
+    background: var(--white);
+    border-radius: 12px;
     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    margin-bottom: 16px;
+    overflow: hidden;
 
-    ion-card-title {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      font-size: 16px;
-      font-weight: 600;
+    .card-header {
+      padding: 16px;
+      border-bottom: 1px solid var(--light-shade);
 
-      ion-icon {
-        font-size: 20px;
-        color: var(--ion-color-primary);
+      .card-title {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin: 0;
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--dark-color);
+
+        .icon {
+          width: 20px;
+          height: 20px;
+          color: var(--primary-color);
+          flex-shrink: 0;
+        }
+      }
+
+      .card-subtitle {
+        margin: 4px 0 0;
+        font-size: 12px;
+        color: var(--medium-color);
       }
     }
 
-    ion-card-subtitle {
-      color: var(--ion-color-medium);
-      font-size: 12px;
-      margin-top: 4px;
+    .card-content {
+      padding: 16px;
     }
   }
 
   // 必填标记
   .required {
-    color: var(--ion-color-danger);
+    color: var(--danger-color);
     margin-left: 4px;
   }
 
   // 客户信息卡片
   .customer-card {
-    ion-item {
-      --padding-start: 0;
-      margin-bottom: 12px;
+    .info-list {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+    }
 
-      ion-label {
-        p {
-          color: var(--ion-color-medium);
+    .info-item {
+      .info-text {
+        .info-label {
+          color: var(--medium-color);
           font-size: 12px;
-          margin-bottom: 4px;
+          margin: 0 0 4px;
         }
 
-        h3 {
-          color: var(--ion-color-dark);
+        .info-value {
+          color: var(--dark-color);
           font-size: 15px;
           font-weight: 500;
           margin: 0;
@@ -78,11 +136,6 @@
           flex-wrap: wrap;
           gap: 6px;
           margin-top: 6px;
-
-          ion-badge {
-            font-size: 11px;
-            padding: 4px 8px;
-          }
         }
       }
     }
@@ -90,27 +143,67 @@
 
   // 项目基本信息卡片
   .project-info-card {
-    ion-item {
-      margin-bottom: 16px;
+    .form-list {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+    }
 
-      &:last-child {
-        margin-bottom: 0;
+    .form-group {
+      .form-label {
+        display: block;
+        font-weight: 500;
+        color: var(--dark-color);
+        margin-bottom: 8px;
+        font-size: 14px;
       }
-    }
 
-    ion-label[position="stacked"] {
-      font-weight: 500;
-      color: var(--ion-color-dark);
-      margin-bottom: 8px;
-    }
+      .form-input,
+      .form-select,
+      .form-textarea {
+        width: 100%;
+        padding: 12px 16px;
+        background: var(--light-color);
+        border: 1px solid var(--light-shade);
+        border-radius: 8px;
+        font-size: 14px;
+        color: var(--dark-color);
+        transition: all 0.3s;
+        font-family: inherit;
 
-    ion-input,
-    ion-select,
-    ion-textarea {
-      --background: var(--ion-color-light);
-      --padding-start: 12px;
-      --padding-end: 12px;
-      border-radius: 8px;
+        &:focus {
+          outline: none;
+          border-color: var(--primary-color);
+          background: var(--white);
+          box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
+        }
+
+        &::placeholder {
+          color: var(--medium-color);
+        }
+
+        &:disabled {
+          background: var(--light-color);
+          color: var(--medium-color);
+          cursor: not-allowed;
+          opacity: 0.7;
+        }
+      }
+
+      .form-textarea {
+        min-height: 80px;
+        resize: vertical;
+      }
+
+      .form-select {
+        cursor: pointer;
+        appearance: none;
+        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2392949c'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
+        background-repeat: no-repeat;
+        background-position: right 12px center;
+        background-size: 20px;
+        padding-right: 40px;
+      }
     }
   }
 
@@ -119,11 +212,12 @@
     .space-section {
       margin-bottom: 24px;
       padding-bottom: 24px;
-      border-bottom: 1px solid var(--ion-color-light-shade);
+      border-bottom: 1px solid var(--light-shade);
 
       &:last-of-type {
         border-bottom: none;
         margin-bottom: 0;
+        padding-bottom: 0;
       }
 
       .space-header {
@@ -131,13 +225,15 @@
         align-items: center;
         gap: 8px;
         margin-bottom: 16px;
-        padding: 8px 12px;
-        background: linear-gradient(135deg, var(--ion-color-primary) 0%, var(--ion-color-secondary) 100%);
+        padding: 10px 14px;
+        background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
         border-radius: 8px;
         color: white;
 
-        ion-icon {
-          font-size: 20px;
+        .icon {
+          width: 20px;
+          height: 20px;
+          flex-shrink: 0;
         }
 
         h3 {
@@ -153,14 +249,15 @@
 
         .process-item {
           padding: 12px;
-          background-color: var(--ion-color-light);
+          background-color: var(--light-color);
           border-radius: 8px;
           border: 2px solid transparent;
           transition: all 0.3s;
 
           &.enabled {
             background-color: white;
-            border-color: var(--ion-color-primary-tint);
+            border-color: rgba(var(--primary-rgb), 0.3);
+            box-shadow: 0 2px 8px rgba(var(--primary-rgb), 0.1);
           }
 
           .process-header {
@@ -169,45 +266,168 @@
             gap: 8px;
             cursor: pointer;
             margin-bottom: 8px;
-
-            ion-checkbox {
+            user-select: none;
+
+            // 自定义 checkbox
+            .checkbox-wrapper {
+              display: inline-flex;
+              align-items: center;
+              cursor: pointer;
+              position: relative;
               margin-right: 4px;
+
+              .checkbox-input {
+                position: absolute;
+                opacity: 0;
+                cursor: pointer;
+                width: 0;
+                height: 0;
+
+                &:checked + .checkbox-custom {
+                  background-color: var(--primary-color);
+                  border-color: var(--primary-color);
+
+                  &::after {
+                    display: block;
+                  }
+                }
+
+                &:disabled + .checkbox-custom {
+                  opacity: 0.5;
+                  cursor: not-allowed;
+                }
+              }
+
+              .checkbox-custom {
+                position: relative;
+                height: 20px;
+                width: 20px;
+                background-color: var(--white);
+                border: 2px solid var(--medium-color);
+                border-radius: 4px;
+                transition: all 0.3s;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+
+                &::after {
+                  content: '';
+                  display: none;
+                  width: 5px;
+                  height: 10px;
+                  border: solid white;
+                  border-width: 0 2px 2px 0;
+                  transform: rotate(45deg);
+                }
+              }
             }
 
-            ion-badge {
+            .badge {
+              display: inline-block;
               font-size: 12px;
               padding: 4px 10px;
+              border-radius: 12px;
+              font-weight: 600;
+
+              // 根据 data-color 属性设置颜色
+              &[data-color="primary"] {
+                background: var(--primary-color);
+                color: white;
+              }
+
+              &[data-color="secondary"] {
+                background: var(--secondary-color);
+                color: white;
+              }
+
+              &[data-color="tertiary"] {
+                background: var(--tertiary-color);
+                color: white;
+              }
+
+              &[data-color="success"] {
+                background: var(--success-color);
+                color: white;
+              }
+
+              &[data-color="warning"] {
+                background: var(--warning-color);
+                color: var(--dark-color);
+              }
+
+              &[data-color="danger"] {
+                background: var(--danger-color);
+                color: white;
+              }
             }
           }
 
           .process-inputs {
             display: grid;
-            gap: 8px;
+            gap: 10px;
             padding-top: 12px;
-            border-top: 1px solid var(--ion-color-light-shade);
-
-            ion-item {
-              --background: transparent;
-              --padding-start: 0;
-              --inner-padding-end: 0;
+            border-top: 1px solid var(--light-shade);
 
-              ion-label {
+            .input-group {
+              .input-label {
+                display: block;
                 font-size: 12px;
-                color: var(--ion-color-medium);
-                margin-bottom: 4px;
+                color: var(--medium-color);
+                margin-bottom: 6px;
+                font-weight: 500;
               }
 
-              ion-input {
-                --background: var(--ion-color-light);
-                --padding-start: 8px;
-                border-radius: 4px;
-                font-size: 14px;
-              }
-
-              ion-note {
-                font-size: 12px;
-                color: var(--ion-color-medium);
-                margin-left: 8px;
+              .input-with-note {
+                display: flex;
+                align-items: center;
+                gap: 8px;
+
+                .input-field {
+                  flex: 1;
+                  padding: 8px 12px;
+                  background: var(--light-color);
+                  border: 1px solid var(--light-shade);
+                  border-radius: 6px;
+                  font-size: 14px;
+                  color: var(--dark-color);
+                  transition: all 0.3s;
+
+                  &:focus {
+                    outline: none;
+                    border-color: var(--primary-color);
+                    background: var(--white);
+                    box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.1);
+                  }
+
+                  &::placeholder {
+                    color: var(--medium-color);
+                  }
+
+                  &:disabled {
+                    background: var(--light-color);
+                    color: var(--medium-color);
+                    cursor: not-allowed;
+                    opacity: 0.7;
+                  }
+
+                  // 隐藏 number input 的上下箭头
+                  &[type="number"] {
+                    -moz-appearance: textfield;
+
+                    &::-webkit-outer-spin-button,
+                    &::-webkit-inner-spin-button {
+                      -webkit-appearance: none;
+                      margin: 0;
+                    }
+                  }
+                }
+
+                .input-note {
+                  font-size: 12px;
+                  color: var(--medium-color);
+                  white-space: nowrap;
+                  flex-shrink: 0;
+                }
               }
             }
 
@@ -215,8 +435,10 @@
               text-align: right;
               font-size: 13px;
               font-weight: 600;
-              color: var(--ion-color-primary);
+              color: var(--primary-color);
               margin-top: 4px;
+              padding-top: 8px;
+              border-top: 1px dashed var(--light-shade);
             }
           }
         }
@@ -227,10 +449,11 @@
       display: flex;
       justify-content: space-between;
       align-items: center;
-      padding: 16px;
+      padding: 16px 20px;
       margin-top: 20px;
-      background: linear-gradient(135deg, var(--ion-color-success) 0%, var(--ion-color-success-shade) 100%);
-      border-radius: 8px;
+      background: linear-gradient(135deg, var(--success-color) 0%, #28ba62 100%);
+      border-radius: 10px;
+      box-shadow: 0 4px 12px rgba(45, 211, 111, 0.3);
 
       .total-label {
         font-size: 16px;
@@ -242,6 +465,7 @@
         font-size: 24px;
         font-weight: 700;
         color: white;
+        letter-spacing: 0.5px;
       }
     }
   }
@@ -256,15 +480,18 @@
       padding: 40px 20px;
       text-align: center;
 
-      ion-icon {
-        font-size: 64px;
-        color: var(--ion-color-medium);
+      .icon-large {
+        width: 64px;
+        height: 64px;
+        color: var(--medium-color);
         margin-bottom: 16px;
+        opacity: 0.5;
       }
 
       p {
-        color: var(--ion-color-medium);
+        color: var(--medium-color);
         margin: 0;
+        font-size: 14px;
       }
     }
 
@@ -277,30 +504,52 @@
         align-items: center;
         gap: 12px;
         padding: 12px;
-        background-color: var(--ion-color-light);
+        background-color: var(--light-color);
         border-radius: 8px;
         border: 2px solid transparent;
         cursor: pointer;
         transition: all 0.3s;
 
         &:hover {
-          background-color: var(--ion-color-light-shade);
+          background-color: var(--light-shade);
+          transform: translateX(2px);
+        }
+
+        &:active {
+          transform: scale(0.98);
         }
 
         &.selected {
           background-color: white;
-          border-color: var(--ion-color-primary);
-          box-shadow: 0 4px 12px rgba(var(--ion-color-primary-rgb), 0.2);
+          border-color: var(--primary-color);
+          box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.2);
+
+          .designer-info h4 {
+            color: var(--primary-color);
+          }
         }
 
-        ion-avatar {
+        .designer-avatar {
           width: 48px;
           height: 48px;
           flex-shrink: 0;
+          border-radius: 50%;
+          overflow: hidden;
+          background-color: var(--light-shade);
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          img {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
 
-          ion-icon {
-            font-size: 48px;
-            color: var(--ion-color-medium);
+          .avatar-icon {
+            width: 48px;
+            height: 48px;
+            color: var(--medium-color);
           }
         }
 
@@ -312,28 +561,74 @@
             margin: 0 0 4px;
             font-size: 15px;
             font-weight: 600;
-            color: var(--ion-color-dark);
+            color: var(--dark-color);
             white-space: nowrap;
             overflow: hidden;
             text-overflow: ellipsis;
+            transition: color 0.3s;
           }
 
           p {
             margin: 0;
             font-size: 12px;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
           }
         }
 
         .selected-icon {
-          font-size: 28px;
-          color: var(--ion-color-primary);
+          width: 28px;
+          height: 28px;
+          color: var(--primary-color);
           flex-shrink: 0;
         }
       }
     }
   }
 
+  // Badge 组件
+  .badge {
+    display: inline-block;
+    padding: 4px 10px;
+    border-radius: 12px;
+    font-size: 11px;
+    font-weight: 600;
+
+    &.badge-primary {
+      background: var(--primary-color);
+      color: white;
+    }
+
+    &.badge-secondary {
+      background: var(--secondary-color);
+      color: white;
+    }
+
+    &.badge-tertiary {
+      background: var(--tertiary-color);
+      color: white;
+    }
+
+    &.badge-success {
+      background: var(--success-color);
+      color: white;
+    }
+
+    &.badge-warning {
+      background: var(--warning-color);
+      color: var(--dark-color);
+    }
+
+    &.badge-danger {
+      background: var(--danger-color);
+      color: white;
+    }
+
+    &.badge-medium {
+      background: var(--medium-color);
+      color: white;
+    }
+  }
+
   // 操作按钮
   .action-buttons {
     display: grid;
@@ -342,14 +637,61 @@
     padding: 16px 0;
     margin-top: 20px;
 
-    ion-button {
-      margin: 0;
-      --border-radius: 8px;
-      height: 48px;
+    .btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 8px;
+      padding: 12px 24px;
+      border-radius: 8px;
+      font-size: 14px;
       font-weight: 600;
+      cursor: pointer;
+      transition: all 0.3s;
+      border: none;
+      outline: none;
+      height: 48px;
+
+      .icon {
+        width: 20px;
+        height: 20px;
+        flex-shrink: 0;
+      }
+
+      &.btn-primary {
+        background: var(--primary-color);
+        color: white;
+
+        &:hover {
+          background: #2f6ce5;
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
+        }
+
+        &:active {
+          transform: translateY(0);
+        }
+      }
+
+      &.btn-outline {
+        background: white;
+        color: var(--primary-color);
+        border: 2px solid var(--primary-color);
+
+        &:hover {
+          background: var(--primary-color);
+          color: white;
+        }
+
+        &:active {
+          transform: scale(0.98);
+        }
+      }
 
-      ion-icon {
-        font-size: 20px;
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+        pointer-events: none;
       }
     }
   }
@@ -379,7 +721,7 @@
     .quotation-card {
       .space-section {
         .process-grid {
-          grid-template-columns: repeat(4, 1fr);
+          grid-template-columns: repeat(3, 1fr);
         }
       }
     }
@@ -391,3 +733,157 @@
     }
   }
 }
+
+// 移动端优化
+@media (max-width: 480px) {
+  .stage-order-container {
+    .card {
+      border-radius: 10px;
+      margin-bottom: 12px;
+
+      .card-header {
+        padding: 12px;
+
+        .card-title {
+          font-size: 15px;
+
+          .icon {
+            width: 18px;
+            height: 18px;
+          }
+        }
+
+        .card-subtitle {
+          font-size: 11px;
+        }
+      }
+
+      .card-content {
+        padding: 12px;
+      }
+    }
+
+    .quotation-card {
+      .space-section {
+        margin-bottom: 20px;
+        padding-bottom: 20px;
+
+        .space-header {
+          padding: 8px 12px;
+
+          .icon {
+            width: 18px;
+            height: 18px;
+          }
+
+          h3 {
+            font-size: 14px;
+          }
+        }
+
+        .process-grid {
+          gap: 10px;
+
+          .process-item {
+            padding: 10px;
+
+            .process-header {
+              .checkbox-wrapper {
+                .checkbox-custom {
+                  height: 18px;
+                  width: 18px;
+
+                  &::after {
+                    width: 4px;
+                    height: 8px;
+                  }
+                }
+              }
+
+              .badge {
+                font-size: 11px;
+                padding: 3px 8px;
+              }
+            }
+          }
+        }
+      }
+
+      .total-section {
+        padding: 12px 16px;
+
+        .total-label {
+          font-size: 14px;
+        }
+
+        .total-amount {
+          font-size: 20px;
+        }
+      }
+    }
+
+    .designer-card {
+      .designer-grid {
+        .designer-item {
+          padding: 10px;
+
+          .designer-avatar {
+            width: 40px;
+            height: 40px;
+
+            .avatar-icon {
+              width: 40px;
+              height: 40px;
+            }
+          }
+
+          .designer-info {
+            h4 {
+              font-size: 14px;
+            }
+
+            p {
+              font-size: 11px;
+            }
+          }
+
+          .selected-icon {
+            width: 24px;
+            height: 24px;
+          }
+        }
+      }
+    }
+
+    .action-buttons {
+      gap: 10px;
+      padding: 12px 0;
+
+      .btn {
+        padding: 10px 16px;
+        font-size: 13px;
+        height: 44px;
+
+        .icon {
+          width: 18px;
+          height: 18px;
+        }
+      }
+    }
+
+    .form-group {
+      .form-input,
+      .form-select,
+      .form-textarea {
+        padding: 10px 12px;
+        font-size: 13px;
+      }
+
+      .form-select {
+        background-position: right 10px center;
+        background-size: 18px;
+        padding-right: 36px;
+      }
+    }
+  }
+}

+ 2 - 2
src/modules/project/pages/project-detail/stages/stage-order.component.ts

@@ -145,7 +145,7 @@ export class StageOrderComponent implements OnInit {
         const wxwork = new WxworkSDK({ cid: this.cid, appId: 'crm' });
         this.currentUser = await wxwork.getCurrentUser();
 
-        const role = this.currentUser?.get('role') || '';
+        const role = this.currentUser?.get('roleName') || '';
         this.canEdit = ['客服', '组员', '组长', '管理员'].includes(role);
       }
 
@@ -316,7 +316,7 @@ export class StageOrderComponent implements OnInit {
         submitter: {
           id: this.currentUser!.id,
           name: this.currentUser!.get('name'),
-          role: this.currentUser!.get('role')
+          role: this.currentUser!.get('roleName')
         },
         submitTime: new Date(),
         status: 'pending',

+ 229 - 170
src/modules/project/pages/project-detail/stages/stage-requirements.component.html

@@ -1,7 +1,9 @@
 <!-- 加载中 -->
 @if (loading) {
   <div class="loading-container">
-    <ion-spinner name="crescent"></ion-spinner>
+    <div class="spinner">
+      <div class="spinner-circle"></div>
+    </div>
     <p>加载需求信息...</p>
   </div>
 }
@@ -10,31 +12,36 @@
 @if (!loading) {
   <div class="stage-requirements-container">
     <!-- 1. 参考图片 -->
-    <ion-card class="reference-images-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="images-outline"></ion-icon>
+    <div class="card reference-images-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M432 112V96a48.14 48.14 0 00-48-48H64a48.14 48.14 0 00-48 48v256a48.14 48.14 0 0048 48h16v16a48.14 48.14 0 0048 48h320a48.14 48.14 0 0048-48V160a48.14 48.14 0 00-48-48zM96 352H64a16 16 0 01-16-16V96a16 16 0 0116-16h320a16 16 0 0116 16v16H128a48.14 48.14 0 00-48 48zm352 64a16 16 0 01-16 16H128a16 16 0 01-16-16V160a16 16 0 0116-16h304a16 16 0 0116 16z"/>
+            <circle cx="213.33" cy="229.33" r="26.67" fill="currentColor"/>
+            <path fill="currentColor" d="M384 336H213l36.67-73.33 32 48 48-64L384 336z"/>
+          </svg>
           参考图片
-        </ion-card-title>
-        <ion-card-subtitle>上传风格、空间或材质参考图</ion-card-subtitle>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+        <p class="card-subtitle">上传风格、空间或材质参考图</p>
+      </div>
+      <div class="card-content">
         <div class="images-grid">
           @for (image of referenceImages; track $index) {
             <div class="image-item">
               <img [src]="image.url" [alt]="image.name" />
               <div class="image-overlay">
-                <ion-badge [color]="getImageTypeColor(image.type)">
+                <span class="badge" [class]="'badge-' + getImageTypeColor(image.type)">
                   {{ getImageTypeLabel(image.type) }}
-                </ion-badge>
+                </span>
                 @if (canEdit) {
-                  <ion-button
-                    fill="clear"
-                    size="small"
-                    color="danger"
+                  <button
+                    class="btn-icon btn-danger"
                     (click)="deleteReferenceImage($index)">
-                    <ion-icon name="trash-outline"></ion-icon>
-                  </ion-button>
+                    <svg class="icon" viewBox="0 0 512 512">
+                      <path fill="currentColor" d="M296 64h-80a7.91 7.91 0 00-8 8v24h96V72a7.91 7.91 0 00-8-8z"/>
+                      <path fill="currentColor" d="M432 96h-96V72a40 40 0 00-40-40h-80a40 40 0 00-40 40v24H80a16 16 0 000 32h17l19 304.92c1.42 26.85 22 47.08 48 47.08h184c26.13 0 46.3-19.78 48-47l19-305h17a16 16 0 000-32zM192.57 416H192a16 16 0 01-16-15.43l-8-224a16 16 0 1132-1.14l8 224A16 16 0 01192.57 416zM272 400a16 16 0 01-32 0V176a16 16 0 0132 0zm32-304h-96V72a8 8 0 018-8h80a8 8 0 018 8zm32 304.57A16 16 0 01320 416h-.58A16 16 0 01304 399.43l8-224a16 16 0 1132 1.14z"/>
+                    </svg>
+                  </button>
                 }
               </div>
             </div>
@@ -49,55 +56,67 @@
                 [disabled]="uploading"
                 hidden
                 #fileInput />
-              <ion-button
-                fill="outline"
+              <button
+                class="btn btn-outline"
                 (click)="fileInput.click()"
                 [disabled]="uploading">
-                <ion-icon name="add-outline" slot="start"></ion-icon>
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288m144-144H112"/>
+                </svg>
                 上传图片
-              </ion-button>
+              </button>
             </div>
           }
         </div>
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 2. CAD文件 -->
-    <ion-card class="cad-files-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="document-outline"></ion-icon>
+    <div class="card cad-files-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M428 224H288a48 48 0 01-48-48V36a4 4 0 00-4-4h-92a64 64 0 00-64 64v320a64 64 0 0064 64h224a64 64 0 0064-64V228a4 4 0 00-4-4z"/>
+            <path fill="currentColor" d="M419.22 188.59L275.41 44.78a2 2 0 00-3.41 1.41V176a16 16 0 0016 16h129.81a2 2 0 001.41-3.41z"/>
+          </svg>
           CAD文件
-        </ion-card-title>
-        <ion-card-subtitle>上传户型图或施工图纸</ion-card-subtitle>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+        <p class="card-subtitle">上传户型图或施工图纸</p>
+      </div>
+      <div class="card-content">
         @if (cadFiles.length === 0) {
           <div class="empty-state">
-            <ion-icon name="document-outline"></ion-icon>
+            <svg class="icon-large" viewBox="0 0 512 512">
+              <path fill="currentColor" d="M428 224H288a48 48 0 01-48-48V36a4 4 0 00-4-4h-92a64 64 0 00-64 64v320a64 64 0 0064 64h224a64 64 0 0064-64V228a4 4 0 00-4-4z" opacity="0.4"/>
+              <path fill="currentColor" d="M419.22 188.59L275.41 44.78a2 2 0 00-3.41 1.41V176a16 16 0 0016 16h129.81a2 2 0 001.41-3.41z"/>
+            </svg>
             <p>暂无CAD文件</p>
           </div>
         } @else {
-          <ion-list lines="full">
+          <div class="file-list">
             @for (file of cadFiles; track $index) {
-              <ion-item>
-                <ion-icon name="document-text-outline" slot="start"></ion-icon>
-                <ion-label>
-                  <h3>{{ file.name }}</h3>
+              <div class="file-item">
+                <svg class="icon file-icon" viewBox="0 0 512 512">
+                  <path fill="currentColor" d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z"/>
+                  <path fill="currentColor" d="M256 56v120a32 32 0 0032 32h120" opacity="0.4"/>
+                </svg>
+                <div class="file-info">
+                  <h4>{{ file.name }}</h4>
                   <p>{{ formatFileSize(file.size) }} · {{ file.uploadTime | date:'yyyy-MM-dd HH:mm' }}</p>
-                </ion-label>
+                </div>
                 @if (canEdit) {
-                  <ion-button
-                    fill="clear"
-                    color="danger"
-                    slot="end"
+                  <button
+                    class="btn-icon btn-danger"
                     (click)="deleteCAD($index)">
-                    <ion-icon name="trash-outline"></ion-icon>
-                  </ion-button>
+                    <svg class="icon" viewBox="0 0 512 512">
+                      <path fill="currentColor" d="M296 64h-80a7.91 7.91 0 00-8 8v24h96V72a7.91 7.91 0 00-8-8z"/>
+                      <path fill="currentColor" d="M432 96h-96V72a40 40 0 00-40-40h-80a40 40 0 00-40 40v24H80a16 16 0 000 32h17l19 304.92c1.42 26.85 22 47.08 48 47.08h184c26.13 0 46.3-19.78 48-47l19-305h17a16 16 0 000-32zM192.57 416H192a16 16 0 01-16-15.43l-8-224a16 16 0 1132-1.14l8 224A16 16 0 01192.57 416zM272 400a16 16 0 01-32 0V176a16 16 0 0132 0zm32-304h-96V72a8 8 0 018-8h80a8 8 0 018 8zm32 304.57A16 16 0 01320 416h-.58A16 16 0 01304 399.43l8-224a16 16 0 1132 1.14z"/>
+                    </svg>
+                  </button>
                 }
-              </ion-item>
+              </div>
             }
-          </ion-list>
+          </div>
         }
 
         @if (canEdit) {
@@ -108,36 +127,45 @@
             [disabled]="uploading"
             hidden
             #cadInput />
-          <ion-button
-            expand="block"
-            fill="outline"
+          <button
+            class="btn btn-outline btn-block"
             (click)="cadInput.click()"
             [disabled]="uploading">
-            <ion-icon name="cloud-upload-outline" slot="start"></ion-icon>
+            <svg class="icon" viewBox="0 0 512 512">
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 367.79h76c55 0 100-29.21 100-83.6s-53-81.47-96-83.6c-8.89-85.06-71-136.8-144-136.8-69 0-113.44 45.79-128 91.2-60 5.7-112 43.88-112 106.4s54 106.4 120 106.4h56"/>
+              <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 255.79l-64-64-64 64m64 192.42V207.79"/>
+            </svg>
             上传CAD文件
-          </ion-button>
+          </button>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 3. 需求清单 -->
-    <ion-card class="requirements-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="list-outline"></ion-icon>
+    <div class="card requirements-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M160 144h288m-288 96h288m-288 96h288"/>
+            <circle cx="80" cy="144" r="16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+            <circle cx="80" cy="240" r="16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+            <circle cx="80" cy="336" r="16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+          </svg>
           需求清单
-        </ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+      </div>
+      <div class="card-content">
         <!-- 空间信息 -->
         <div class="section">
           <div class="section-header">
             <h3>空间信息</h3>
             @if (canEdit) {
-              <ion-button size="small" fill="outline" (click)="addSpace()">
-                <ion-icon name="add-outline" slot="start"></ion-icon>
+              <button class="btn btn-outline btn-sm" (click)="addSpace()">
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 112v288m144-144H112"/>
+                </svg>
                 添加空间
-              </ion-button>
+              </button>
             }
           </div>
 
@@ -147,45 +175,47 @@
             @for (space of requirements.spaces; track $index) {
               <div class="space-item">
                 <div class="space-header">
-                  <ion-badge color="primary">{{ $index + 1 }}</ion-badge>
+                  <span class="badge badge-primary">{{ $index + 1 }}</span>
                   @if (canEdit) {
-                    <ion-button
-                      fill="clear"
-                      size="small"
-                      color="danger"
+                    <button
+                      class="btn-icon btn-danger"
                       (click)="removeSpace($index)">
-                      <ion-icon name="close-outline"></ion-icon>
-                    </ion-button>
+                      <svg class="icon" viewBox="0 0 512 512">
+                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144m224 0L144 368"/>
+                      </svg>
+                    </button>
                   }
                 </div>
 
-                <ion-list lines="none">
-                  <ion-item>
-                    <ion-label position="stacked">空间名称 <span class="required">*</span></ion-label>
-                    <ion-input
-                      [(ngModel)]="space.name"
-                      [disabled]="!canEdit"
-                      placeholder="如:客厅、主卧等"></ion-input>
-                  </ion-item>
+                <div class="form-group">
+                  <label class="form-label">空间名称 <span class="required">*</span></label>
+                  <input
+                    type="text"
+                    class="form-input"
+                    [(ngModel)]="space.name"
+                    [disabled]="!canEdit"
+                    placeholder="如:客厅、主卧等" />
+                </div>
 
-                  <ion-item>
-                    <ion-label position="stacked">面积(㎡) <span class="required">*</span></ion-label>
-                    <ion-input
-                      type="number"
-                      [(ngModel)]="space.area"
-                      [disabled]="!canEdit"
-                      placeholder="0"></ion-input>
-                  </ion-item>
+                <div class="form-group">
+                  <label class="form-label">面积(㎡) <span class="required">*</span></label>
+                  <input
+                    type="number"
+                    class="form-input"
+                    [(ngModel)]="space.area"
+                    [disabled]="!canEdit"
+                    placeholder="0" />
+                </div>
 
-                  <ion-item>
-                    <ion-label position="stacked">空间描述</ion-label>
-                    <ion-textarea
-                      [(ngModel)]="space.description"
-                      [disabled]="!canEdit"
-                      rows="2"
-                      placeholder="描述空间用途、功能需求等"></ion-textarea>
-                  </ion-item>
-                </ion-list>
+                <div class="form-group">
+                  <label class="form-label">空间描述</label>
+                  <textarea
+                    class="form-textarea"
+                    [(ngModel)]="space.description"
+                    [disabled]="!canEdit"
+                    rows="2"
+                    placeholder="描述空间用途、功能需求等"></textarea>
+                </div>
               </div>
             }
           }
@@ -193,13 +223,15 @@
 
         <!-- 风格偏好 -->
         <div class="section">
-          <ion-item>
-            <ion-label position="stacked">风格偏好 <span class="required">*</span></ion-label>
-            <ion-input
+          <div class="form-group">
+            <label class="form-label">风格偏好 <span class="required">*</span></label>
+            <input
+              type="text"
+              class="form-input"
               [(ngModel)]="requirements.stylePreference"
               [disabled]="!canEdit"
-              placeholder="如:现代简约、北欧、轻奢等"></ion-input>
-          </ion-item>
+              placeholder="如:现代简约、北欧、轻奢等" />
+          </div>
         </div>
 
         <!-- 色彩方案 -->
@@ -207,111 +239,129 @@
           <div class="section-header">
             <h3>色彩方案</h3>
             @if (canEdit) {
-              <ion-button size="small" fill="outline" (click)="openColorAnalysis()">
-                <ion-icon name="color-palette-outline" slot="start"></ion-icon>
+              <button class="btn btn-outline btn-sm" (click)="openColorAnalysis()">
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="currentColor" d="M441 336.2l-.06-.05c-9.93-9.18-22.78-11.34-32.16-12.92l-.69-.12c-9.05-1.49-10.48-2.5-14.58-6.17-2.44-2.17-5.35-5.65-5.35-9.94s2.91-7.77 5.34-9.94l30.28-26.87c25.92-22.91 40.2-53.66 40.2-86.59s-14.25-63.68-40.2-86.6c-35.89-31.59-85-49-138.37-49C223.72 48 162 71.37 116 112.11c-43.87 38.77-68 90.71-68 146.24s24.16 107.47 68 146.23c21.75 19.24 47.49 34.18 76.52 44.42a266.17 266.17 0 0086.87 15h1.81c61 0 119.09-20.57 159.39-56.4 9.7-8.56 15.15-20.83 15.34-34.56.21-14.17-5.37-27.95-14.93-36.84zM112 208a32 32 0 1132 32 32 32 0 01-32-32zm40 135a32 32 0 1132-32 32 32 0 01-32 32zm40-199a32 32 0 1132 32 32 32 0 01-32-32zm64 271a48 48 0 1148-48 48 48 0 01-48 48zm72-239a32 32 0 1132-32 32 32 0 01-32 32zm32 39a32 32 0 1132 32 32 32 0 01-32-32zm68 88a32 32 0 1132-32 32 32 0 01-32 32z"/>
+                </svg>
                 色彩分析
-              </ion-button>
+              </button>
             }
           </div>
 
-          <ion-item>
-            <ion-label position="stacked">色彩氛围</ion-label>
-            <ion-select
+          <div class="form-group">
+            <label class="form-label">色彩氛围</label>
+            <select
+              class="form-select"
               [(ngModel)]="requirements.colorScheme.atmosphere"
-              [disabled]="!canEdit"
-              placeholder="请选择">
-              <ion-select-option value="温馨">温馨</ion-select-option>
-              <ion-select-option value="高级">高级</ion-select-option>
-              <ion-select-option value="简约">简约</ion-select-option>
-              <ion-select-option value="时尚">时尚</ion-select-option>
-            </ion-select>
-          </ion-item>
+              [disabled]="!canEdit">
+              <option value="">请选择</option>
+              <option value="温馨">温馨</option>
+              <option value="高级">高级</option>
+              <option value="简约">简约</option>
+              <option value="时尚">时尚</option>
+            </select>
+          </div>
         </div>
 
         <!-- 预算范围 -->
         <div class="section">
           <h3>预算范围(万元)</h3>
           <div class="budget-range">
-            <ion-item>
-              <ion-label position="stacked">最低</ion-label>
-              <ion-input
+            <div class="form-group">
+              <label class="form-label">最低</label>
+              <input
                 type="number"
+                class="form-input"
                 [(ngModel)]="requirements.budget.min"
                 [disabled]="!canEdit"
-                placeholder="0"></ion-input>
-            </ion-item>
+                placeholder="0" />
+            </div>
             <span class="separator">-</span>
-            <ion-item>
-              <ion-label position="stacked">最高</ion-label>
-              <ion-input
+            <div class="form-group">
+              <label class="form-label">最高</label>
+              <input
                 type="number"
+                class="form-input"
                 [(ngModel)]="requirements.budget.max"
                 [disabled]="!canEdit"
-                placeholder="0"></ion-input>
-            </ion-item>
+                placeholder="0" />
+            </div>
           </div>
         </div>
 
         <!-- 特殊需求 -->
         <div class="section">
-          <ion-item>
-            <ion-label position="stacked">特殊需求</ion-label>
-            <ion-textarea
+          <div class="form-group">
+            <label class="form-label">特殊需求</label>
+            <textarea
+              class="form-textarea"
               [(ngModel)]="requirements.specialRequirements"
               [disabled]="!canEdit"
               rows="3"
-              placeholder="描述任何特殊需求或注意事项"></ion-textarea>
-          </ion-item>
+              placeholder="描述任何特殊需求或注意事项"></textarea>
+          </div>
         </div>
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 4. AI生成方案 -->
-    <ion-card class="ai-solution-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="sparkles-outline"></ion-icon>
+    <div class="card ai-solution-card">
+      <div class="card-header">
+        <h3 class="card-title">
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M208 512a24.84 24.84 0 01-23.34-16l-39.84-103.6a16.06 16.06 0 00-9.19-9.19L32 343.34a25 25 0 010-46.68l103.6-39.84a16.06 16.06 0 009.19-9.19L184.66 144a25 25 0 0146.68 0l39.84 103.6a16.06 16.06 0 009.19 9.19l103 39.63a25.49 25.49 0 0116.63 24.1 24.82 24.82 0 01-16 22.82l-103.6 39.84a16.06 16.06 0 00-9.19 9.19L231.34 496A24.84 24.84 0 01208 512zm66.85-254.84zm-4.56 16.08zm-.21-.12z"/>
+            <path fill="currentColor" d="M88 176a14.67 14.67 0 01-13.69-9.4l-16.86-43.84a7.28 7.28 0 00-4.21-4.21L9.4 101.69a14.67 14.67 0 010-27.38l43.84-16.86a7.31 7.31 0 004.21-4.21L74.16 9.4a14.67 14.67 0 0127.38 0l16.86 43.84a7.31 7.31 0 004.21 4.21l43.84 16.86a14.67 14.67 0 010 27.38l-43.84 16.86a7.28 7.28 0 00-4.21 4.21L101.69 166.6A14.67 14.67 0 0188 176zm312 208a16 16 0 01-14.93-10.26l-22.84-59.37a8 8 0 00-4.6-4.6l-59.37-22.84a16 16 0 010-29.86l59.37-22.84a8 8 0 004.6-4.6l22.67-58.95a16.45 16.45 0 0129.86 0l22.84 59.37a8 8 0 004.6 4.6l59.37 22.84a16 16 0 010 29.86l-59.37 22.84a8 8 0 00-4.6 4.6l-22.84 59.37A16 16 0 01400 384z" opacity="0.4"/>
+          </svg>
           AI设计方案
-        </ion-card-title>
-        <ion-card-subtitle>基于需求智能生成设计方案</ion-card-subtitle>
-      </ion-card-header>
-      <ion-card-content>
+        </h3>
+        <p class="card-subtitle">基于需求智能生成设计方案</p>
+      </div>
+      <div class="card-content">
         @if (!aiSolution) {
           <div class="empty-state">
-            <ion-icon name="bulb-outline"></ion-icon>
+            <svg class="icon-large" viewBox="0 0 512 512">
+              <path fill="currentColor" d="M304 384v-24c0-29 31.54-56.43 52-76 28.84-27.57 44-64.61 44-108 0-80-63.73-144-144-144a143.6 143.6 0 00-144 144c0 41.84 15.81 81.39 44 108 20.35 19.21 52 46.7 52 76v24m16 96h64m-80-32h96m-128-32h160" opacity="0.4"/>
+            </svg>
             <p>尚未生成AI方案</p>
             @if (canEdit) {
-              <ion-button
-                expand="block"
-                color="primary"
+              <button
+                class="btn btn-primary"
                 (click)="generateAISolution()"
                 [disabled]="generating">
                 @if (generating) {
-                  <ion-spinner name="crescent" slot="start"></ion-spinner>
+                  <div class="spinner-small">
+                    <div class="spinner-circle"></div>
+                  </div>
                   生成中...
                 } @else {
-                  <ion-icon name="sparkles-outline" slot="start"></ion-icon>
+                  <svg class="icon" viewBox="0 0 512 512">
+                    <path fill="currentColor" d="M208 512a24.84 24.84 0 01-23.34-16l-39.84-103.6a16.06 16.06 0 00-9.19-9.19L32 343.34a25 25 0 010-46.68l103.6-39.84a16.06 16.06 0 009.19-9.19L184.66 144a25 25 0 0146.68 0l39.84 103.6a16.06 16.06 0 009.19 9.19l103 39.63a25.49 25.49 0 0116.63 24.1 24.82 24.82 0 01-16 22.82l-103.6 39.84a16.06 16.06 0 00-9.19 9.19L231.34 496A24.84 24.84 0 01208 512z"/>
+                  </svg>
                   生成AI方案
                 }
-              </ion-button>
+              </button>
             }
           </div>
         } @else {
           <div class="ai-solution-content">
             <div class="solution-header">
-              <ion-badge color="success">
-                <ion-icon name="checkmark-circle-outline"></ion-icon>
+              <span class="badge badge-success">
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/>
+                </svg>
                 已生成
-              </ion-badge>
+              </span>
               @if (canEdit) {
-                <ion-button
-                  size="small"
-                  fill="outline"
+                <button
+                  class="btn btn-outline btn-sm"
                   (click)="generateAISolution()"
                   [disabled]="generating">
-                  <ion-icon name="refresh-outline" slot="start"></ion-icon>
+                  <svg class="icon" viewBox="0 0 512 512">
+                    <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M320 146s24.36-12-64-12a160 160 0 10160 160"/>
+                    <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/>
+                  </svg>
                   重新生成
-                </ion-button>
+                </button>
               }
             </div>
 
@@ -335,7 +385,7 @@
                     <span class="label">材质选择:</span>
                     <div class="tags">
                       @for (material of space.materials; track material) {
-                        <ion-badge color="tertiary">{{ material }}</ion-badge>
+                        <span class="badge badge-tertiary">{{ material }}</span>
                       }
                     </div>
                   </div>
@@ -344,7 +394,7 @@
                     <span class="label">家具推荐:</span>
                     <div class="tags">
                       @for (item of space.furnitureRecommendations; track item) {
-                        <ion-badge color="secondary">{{ item }}</ion-badge>
+                        <span class="badge badge-secondary">{{ item }}</span>
                       }
                     </div>
                   </div>
@@ -355,7 +405,10 @@
             <!-- 预算与时间线 -->
             <div class="summary">
               <div class="summary-item">
-                <ion-icon name="cash-outline"></ion-icon>
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="currentColor" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" opacity="0.4"/>
+                  <path fill="currentColor" d="M310.4 140.6c-17.8 0-32.4 14.2-32.4 31.7v151.4c0 17.5 14.6 31.7 32.4 31.7M201.6 140.6c17.8 0 32.4 14.2 32.4 31.7v151.4c0 17.5-14.6 31.7-32.4 31.7"/>
+                </svg>
                 <div>
                   <p class="label">预估造价</p>
                   <h3>¥{{ aiSolution.estimatedCost.toLocaleString() }}</h3>
@@ -363,7 +416,10 @@
               </div>
 
               <div class="summary-item">
-                <ion-icon name="time-outline"></ion-icon>
+                <svg class="icon" viewBox="0 0 512 512">
+                  <path fill="currentColor" d="M256 64C150 64 64 150 64 256s86 192 192 192 192-86 192-192S362 64 256 64z" opacity="0.4"/>
+                  <path fill="currentColor" d="M256 128v144h96"/>
+                </svg>
                 <div>
                   <p class="label">项目周期</p>
                   <p>{{ aiSolution.timeline }}</p>
@@ -372,29 +428,32 @@
             </div>
           </div>
         }
-      </ion-card-content>
-    </ion-card>
+      </div>
+    </div>
 
     <!-- 5. 操作按钮 -->
     @if (canEdit) {
       <div class="action-buttons">
-        <ion-button
-          expand="block"
-          fill="outline"
+        <button
+          class="btn btn-outline"
           (click)="saveDraft()"
           [disabled]="saving">
-          <ion-icon name="save-outline" slot="start"></ion-icon>
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z"/>
+          </svg>
           保存草稿
-        </ion-button>
+        </button>
 
-        <ion-button
-          expand="block"
-          color="primary"
+        <button
+          class="btn btn-primary"
           (click)="submitRequirements()"
           [disabled]="saving || !aiSolution">
-          <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
+          <svg class="icon" viewBox="0 0 512 512">
+            <path fill="currentColor" d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" opacity="0.4"/>
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M352 176L217.6 336 160 272"/>
+          </svg>
           确认需求
-        </ion-button>
+        </button>
       </div>
     }
   </div>

+ 581 - 116
src/modules/project/pages/project-detail/stages/stage-requirements.component.scss

@@ -1,4 +1,20 @@
-// 确认需求阶段样式
+// 确认需求阶段样式 - 纯 div+scss 实现
+
+// CSS 变量定义
+:host {
+  --primary-color: #3880ff;
+  --primary-rgb: 56, 128, 255;
+  --secondary-color: #0cd1e8;
+  --tertiary-color: #7044ff;
+  --success-color: #2dd36f;
+  --warning-color: #ffc409;
+  --danger-color: #eb445a;
+  --dark-color: #222428;
+  --medium-color: #92949c;
+  --light-color: #f4f5f8;
+  --light-shade: #d7d8da;
+  --white: #ffffff;
+}
 
 // 加载容器
 .loading-container {
@@ -9,47 +25,101 @@
   min-height: 50vh;
   padding: 20px;
 
-  ion-spinner {
-    --color: var(--ion-color-primary);
-    transform: scale(1.5);
-    margin-bottom: 16px;
+  p {
+    color: var(--medium-color);
+    margin: 16px 0 0;
+    font-size: 14px;
+  }
+}
+
+// Spinner 动画
+.spinner {
+  width: 48px;
+  height: 48px;
+  position: relative;
+
+  .spinner-circle {
+    width: 100%;
+    height: 100%;
+    border: 4px solid var(--light-shade);
+    border-top-color: var(--primary-color);
+    border-radius: 50%;
+    animation: spin 0.8s linear infinite;
   }
+}
 
-  p {
-    color: var(--ion-color-medium);
+.spinner-small {
+  width: 20px;
+  height: 20px;
+  position: relative;
+  display: inline-block;
+
+  .spinner-circle {
+    width: 100%;
+    height: 100%;
+    border: 3px solid rgba(255, 255, 255, 0.3);
+    border-top-color: white;
+    border-radius: 50%;
+    animation: spin 0.8s linear infinite;
+  }
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
   }
 }
 
 // 确认需求容器
 .stage-requirements-container {
+  padding: 0 12px 80px;
+
   // 通用卡片样式
-  ion-card {
-    margin-bottom: 16px;
+  .card {
+    background: white;
+    border-radius: 12px;
     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    margin-bottom: 16px;
+    overflow: hidden;
 
-    ion-card-title {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      font-size: 16px;
-      font-weight: 600;
+    .card-header {
+      padding: 16px;
+      border-bottom: 1px solid var(--light-shade);
+
+      .card-title {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin: 0 0 4px;
+        font-size: 16px;
+        font-weight: 600;
+        color: var(--dark-color);
 
-      ion-icon {
-        font-size: 20px;
-        color: var(--ion-color-primary);
+        .icon {
+          width: 20px;
+          height: 20px;
+          color: var(--primary-color);
+        }
+      }
+
+      .card-subtitle {
+        color: var(--medium-color);
+        font-size: 12px;
+        margin: 0;
       }
     }
 
-    ion-card-subtitle {
-      color: var(--ion-color-medium);
-      font-size: 12px;
-      margin-top: 4px;
+    .card-content {
+      padding: 16px;
     }
   }
 
   // 必填标记
   .required {
-    color: var(--ion-color-danger);
+    color: var(--danger-color);
     margin-left: 4px;
   }
 
@@ -62,15 +132,21 @@
     padding: 40px 20px;
     text-align: center;
 
-    ion-icon {
-      font-size: 64px;
-      color: var(--ion-color-medium);
+    .icon-large {
+      width: 64px;
+      height: 64px;
+      color: var(--medium-color);
       margin-bottom: 16px;
+      opacity: 0.5;
     }
 
     p {
-      color: var(--ion-color-medium);
-      margin-bottom: 16px;
+      color: var(--medium-color);
+      margin: 0 0 16px;
+    }
+
+    .btn {
+      margin-top: 8px;
     }
   }
 
@@ -87,6 +163,7 @@
         border-radius: 8px;
         overflow: hidden;
         cursor: pointer;
+        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 
         img {
           width: 100%;
@@ -107,18 +184,22 @@
           opacity: 1;
           transition: opacity 0.3s;
 
-          ion-badge {
+          .badge {
             height: fit-content;
             font-size: 10px;
             padding: 4px 8px;
           }
 
-          ion-button {
-            --padding-start: 8px;
-            --padding-end: 8px;
+          .btn-icon {
+            width: 32px;
             height: 32px;
+            padding: 4px;
           }
         }
+
+        &:hover .image-overlay {
+          opacity: 1;
+        }
       }
 
       .upload-placeholder {
@@ -126,35 +207,70 @@
         display: flex;
         align-items: center;
         justify-content: center;
-        border: 2px dashed var(--ion-color-light-shade);
+        border: 2px dashed var(--light-shade);
         border-radius: 8px;
-        background-color: var(--ion-color-light);
+        background-color: var(--light-color);
+        transition: all 0.3s;
+
+        &:hover {
+          border-color: var(--primary-color);
+          background-color: rgba(var(--primary-rgb), 0.05);
+        }
       }
     }
   }
 
   // CAD文件卡片
   .cad-files-card {
-    ion-list {
-      ion-item {
-        --padding-start: 0;
-
-        ion-icon[slot="start"] {
-          font-size: 32px;
-          color: var(--ion-color-primary);
-          margin-right: 12px;
-        }
+    .file-list {
+      display: flex;
+      flex-direction: column;
+      gap: 0;
+      margin-bottom: 16px;
+    }
 
-        h3 {
+    .file-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 0;
+      border-bottom: 1px solid var(--light-shade);
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .file-icon {
+        width: 40px;
+        height: 40px;
+        color: var(--primary-color);
+        flex-shrink: 0;
+      }
+
+      .file-info {
+        flex: 1;
+        min-width: 0;
+
+        h4 {
+          margin: 0 0 4px;
+          font-size: 15px;
           font-weight: 600;
-          margin-bottom: 4px;
+          color: var(--dark-color);
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
         }
 
         p {
+          margin: 0;
           font-size: 12px;
-          color: var(--ion-color-medium);
+          color: var(--medium-color);
         }
       }
+
+      .btn-icon {
+        flex-shrink: 0;
+      }
     }
   }
 
@@ -163,11 +279,12 @@
     .section {
       margin-bottom: 24px;
       padding-bottom: 24px;
-      border-bottom: 1px solid var(--ion-color-light-shade);
+      border-bottom: 1px solid var(--light-shade);
 
       &:last-child {
         border-bottom: none;
         margin-bottom: 0;
+        padding-bottom: 0;
       }
 
       .section-header {
@@ -180,7 +297,7 @@
           margin: 0;
           font-size: 15px;
           font-weight: 600;
-          color: var(--ion-color-dark);
+          color: var(--dark-color);
         }
       }
 
@@ -188,18 +305,19 @@
         margin: 0 0 12px;
         font-size: 15px;
         font-weight: 600;
-        color: var(--ion-color-dark);
+        color: var(--dark-color);
       }
 
       .empty-text {
-        color: var(--ion-color-medium);
+        color: var(--medium-color);
         font-size: 13px;
         font-style: italic;
+        margin: 0;
       }
 
       .space-item {
         padding: 16px;
-        background-color: var(--ion-color-light);
+        background-color: var(--light-color);
         border-radius: 8px;
         margin-bottom: 12px;
 
@@ -212,49 +330,20 @@
           justify-content: space-between;
           align-items: center;
           margin-bottom: 12px;
-
-          ion-badge {
-            font-size: 12px;
-            padding: 6px 12px;
-          }
-        }
-
-        ion-item {
-          --background: white;
-          --padding-start: 12px;
-          border-radius: 4px;
-          margin-bottom: 8px;
-
-          &:last-child {
-            margin-bottom: 0;
-          }
-        }
-
-        ion-label[position="stacked"] {
-          font-weight: 500;
-          color: var(--ion-color-dark);
-          font-size: 13px;
-          margin-bottom: 6px;
         }
       }
 
       .budget-range {
         display: grid;
         grid-template-columns: 1fr auto 1fr;
-        align-items: center;
+        align-items: flex-start;
         gap: 12px;
 
         .separator {
           font-size: 20px;
           font-weight: 600;
-          color: var(--ion-color-medium);
-          padding-top: 20px;
-        }
-
-        ion-item {
-          --background: white;
-          --padding-start: 12px;
-          border-radius: 4px;
+          color: var(--medium-color);
+          padding-top: 32px;
         }
       }
     }
@@ -269,17 +358,17 @@
         align-items: center;
         margin-bottom: 20px;
         padding-bottom: 16px;
-        border-bottom: 1px solid var(--ion-color-light-shade);
+        border-bottom: 1px solid var(--light-shade);
 
-        ion-badge {
+        .badge {
           display: flex;
           align-items: center;
           gap: 4px;
           padding: 6px 12px;
-          font-size: 12px;
 
-          ion-icon {
-            font-size: 16px;
+          .icon {
+            width: 16px;
+            height: 16px;
           }
         }
       }
@@ -287,26 +376,26 @@
       .spaces-solution {
         .space-solution-item {
           padding: 16px;
-          background-color: var(--ion-color-light);
+          background-color: var(--light-color);
           border-radius: 8px;
           margin-bottom: 16px;
 
           &:last-child {
-            margin-bottom: 0;
+            margin-bottom: 20px;
           }
 
           h4 {
             margin: 0 0 8px;
             font-size: 16px;
             font-weight: 600;
-            color: var(--ion-color-dark);
+            color: var(--dark-color);
           }
 
           .style-desc {
             margin: 0 0 16px;
             font-size: 13px;
             line-height: 1.6;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
           }
 
           .color-palette,
@@ -324,13 +413,14 @@
             .label {
               font-size: 12px;
               font-weight: 500;
-              color: var(--ion-color-medium);
+              color: var(--medium-color);
               white-space: nowrap;
             }
 
             .colors {
               display: flex;
               gap: 6px;
+              flex-wrap: wrap;
 
               .color-swatch {
                 width: 32px;
@@ -345,11 +435,6 @@
               display: flex;
               flex-wrap: wrap;
               gap: 6px;
-
-              ion-badge {
-                font-size: 11px;
-                padding: 4px 8px;
-              }
             }
           }
         }
@@ -359,27 +444,25 @@
         display: grid;
         grid-template-columns: repeat(2, 1fr);
         gap: 12px;
-        margin-top: 20px;
-        padding-top: 20px;
-        border-top: 1px solid var(--ion-color-light-shade);
 
         .summary-item {
           display: flex;
           align-items: center;
           gap: 12px;
           padding: 16px;
-          background: linear-gradient(135deg, var(--ion-color-primary-tint), var(--ion-color-secondary-tint));
+          background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.1), rgba(12, 209, 232, 0.1));
           border-radius: 8px;
 
-          > ion-icon {
-            font-size: 32px;
-            color: var(--ion-color-primary);
+          > .icon {
+            width: 32px;
+            height: 32px;
+            color: var(--primary-color);
             flex-shrink: 0;
           }
 
           .label {
             font-size: 11px;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
             margin: 0 0 4px;
           }
 
@@ -387,14 +470,14 @@
             margin: 0;
             font-size: 18px;
             font-weight: 700;
-            color: var(--ion-color-primary);
+            color: var(--primary-color);
           }
 
           p {
             margin: 0;
             font-size: 12px;
             line-height: 1.4;
-            color: var(--ion-color-medium);
+            color: var(--medium-color);
           }
         }
       }
@@ -408,16 +491,233 @@
     gap: 12px;
     padding: 16px 0;
     margin-top: 20px;
+  }
+}
 
-    ion-button {
-      margin: 0;
-      --border-radius: 8px;
-      height: 48px;
-      font-weight: 600;
+// 通用图标样式
+.icon {
+  width: 20px;
+  height: 20px;
+  flex-shrink: 0;
+}
 
-      ion-icon {
-        font-size: 20px;
-      }
+// Badge 组件
+.badge {
+  display: inline-block;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 11px;
+  font-weight: 600;
+  white-space: nowrap;
+
+  &.badge-primary {
+    background: var(--primary-color);
+    color: white;
+  }
+
+  &.badge-secondary {
+    background: var(--secondary-color);
+    color: white;
+  }
+
+  &.badge-tertiary {
+    background: var(--tertiary-color);
+    color: white;
+  }
+
+  &.badge-success {
+    background: var(--success-color);
+    color: white;
+  }
+
+  &.badge-warning {
+    background: var(--warning-color);
+    color: var(--dark-color);
+  }
+
+  &.badge-danger {
+    background: var(--danger-color);
+    color: white;
+  }
+
+  &.badge-medium {
+    background: var(--medium-color);
+    color: white;
+  }
+}
+
+// 按钮样式
+.btn {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 12px 24px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.3s;
+  border: none;
+  outline: none;
+  white-space: nowrap;
+
+  .icon {
+    width: 20px;
+    height: 20px;
+  }
+
+  &.btn-primary {
+    background: var(--primary-color);
+    color: white;
+
+    &:hover:not(:disabled) {
+      background: #2f6ce5;
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3);
+    }
+
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+  }
+
+  &.btn-outline {
+    background: white;
+    color: var(--primary-color);
+    border: 2px solid var(--primary-color);
+
+    &:hover:not(:disabled) {
+      background: var(--primary-color);
+      color: white;
+    }
+
+    &:active:not(:disabled) {
+      transform: scale(0.98);
+    }
+  }
+
+  &.btn-sm {
+    padding: 8px 16px;
+    font-size: 12px;
+
+    .icon {
+      width: 16px;
+      height: 16px;
+    }
+  }
+
+  &.btn-block {
+    display: flex;
+    width: 100%;
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+// 图标按钮
+.btn-icon {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 40px;
+  height: 40px;
+  border: none;
+  background: transparent;
+  border-radius: 50%;
+  cursor: pointer;
+  transition: all 0.2s;
+  padding: 8px;
+
+  .icon {
+    width: 24px;
+    height: 24px;
+  }
+
+  &.btn-danger {
+    color: white;
+    background: var(--danger-color);
+
+    &:hover:not(:disabled) {
+      background: #d33939;
+      transform: scale(1.1);
+    }
+
+    &:active:not(:disabled) {
+      transform: scale(0.95);
+    }
+  }
+
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+// 表单样式
+.form-group {
+  margin-bottom: 16px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  .form-label {
+    display: block;
+    margin-bottom: 8px;
+    font-size: 13px;
+    font-weight: 600;
+    color: var(--dark-color);
+  }
+
+  .form-input,
+  .form-textarea,
+  .form-select {
+    width: 100%;
+    padding: 12px 16px;
+    border: 1px solid var(--light-shade);
+    border-radius: 8px;
+    font-size: 14px;
+    color: var(--dark-color);
+    background: white;
+    transition: all 0.3s;
+    font-family: inherit;
+
+    &:focus {
+      outline: none;
+      border-color: var(--primary-color);
+      box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
+    }
+
+    &::placeholder {
+      color: var(--medium-color);
+    }
+
+    &:disabled {
+      background: var(--light-color);
+      cursor: not-allowed;
+      opacity: 0.7;
+    }
+  }
+
+  .form-textarea {
+    min-height: 80px;
+    resize: vertical;
+  }
+
+  .form-select {
+    cursor: pointer;
+    appearance: none;
+    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
+    background-repeat: no-repeat;
+    background-position: right 12px center;
+    padding-right: 40px;
+
+    &:disabled {
+      cursor: not-allowed;
     }
   }
 }
@@ -425,6 +725,10 @@
 // 响应式适配
 @media (min-width: 768px) {
   .stage-requirements-container {
+    max-width: 800px;
+    margin: 0 auto;
+    padding: 0 24px 80px;
+
     .reference-images-card {
       .images-grid {
         grid-template-columns: repeat(3, 1fr);
@@ -443,6 +747,8 @@
 
 @media (min-width: 1024px) {
   .stage-requirements-container {
+    max-width: 1000px;
+
     .reference-images-card {
       .images-grid {
         grid-template-columns: repeat(4, 1fr);
@@ -450,3 +756,162 @@
     }
   }
 }
+
+// 移动端优化
+@media (max-width: 480px) {
+  .stage-requirements-container {
+    padding: 0 8px 70px;
+
+    .card {
+      .card-header {
+        padding: 12px;
+
+        .card-title {
+          font-size: 15px;
+        }
+
+        .card-subtitle {
+          font-size: 11px;
+        }
+      }
+
+      .card-content {
+        padding: 12px;
+      }
+    }
+
+    .reference-images-card {
+      .images-grid {
+        gap: 8px;
+
+        .image-item {
+          .image-overlay {
+            padding: 6px;
+
+            .badge {
+              font-size: 9px;
+              padding: 3px 6px;
+            }
+
+            .btn-icon {
+              width: 28px;
+              height: 28px;
+            }
+          }
+        }
+      }
+    }
+
+    .requirements-card {
+      .section {
+        margin-bottom: 20px;
+        padding-bottom: 20px;
+
+        .space-item {
+          padding: 12px;
+        }
+
+        .budget-range {
+          gap: 8px;
+
+          .separator {
+            font-size: 18px;
+            padding-top: 28px;
+          }
+        }
+      }
+    }
+
+    .ai-solution-card {
+      .ai-solution-content {
+        .spaces-solution {
+          .space-solution-item {
+            padding: 12px;
+
+            h4 {
+              font-size: 15px;
+            }
+
+            .color-palette,
+            .materials,
+            .furniture {
+              flex-direction: column;
+              align-items: flex-start;
+              gap: 8px;
+
+              .label {
+                width: 100%;
+              }
+            }
+          }
+        }
+
+        .summary {
+          grid-template-columns: 1fr;
+
+          .summary-item {
+            padding: 12px;
+
+            > .icon {
+              width: 28px;
+              height: 28px;
+            }
+
+            h3 {
+              font-size: 16px;
+            }
+          }
+        }
+      }
+    }
+
+    .action-buttons {
+      gap: 8px;
+
+      .btn {
+        padding: 10px 16px;
+        font-size: 13px;
+
+        .icon {
+          width: 18px;
+          height: 18px;
+        }
+      }
+    }
+
+    .form-group {
+      margin-bottom: 14px;
+
+      .form-label {
+        font-size: 12px;
+      }
+
+      .form-input,
+      .form-textarea,
+      .form-select {
+        padding: 10px 12px;
+        font-size: 13px;
+      }
+    }
+
+    .btn {
+      padding: 10px 20px;
+      font-size: 13px;
+
+      .icon {
+        width: 18px;
+        height: 18px;
+      }
+    }
+
+    .btn-icon {
+      width: 36px;
+      height: 36px;
+
+      .icon {
+        width: 20px;
+        height: 20px;
+      }
+    }
+  }
+}

+ 2 - 3
src/modules/project/pages/project-detail/stages/stage-requirements.component.ts

@@ -2,7 +2,6 @@ import { Component, OnInit, Input } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
-import { IonicModule } from '@ionic/angular';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { ProjectUploadService } from '../../../services/upload.service';
 import { ProjectAIService } from '../../../services/ai.service';
@@ -23,7 +22,7 @@ const Parse = FmodeParse.with('nova');
 @Component({
   selector: 'app-stage-requirements',
   standalone: true,
-  imports: [CommonModule, IonicModule, FormsModule],
+  imports: [CommonModule, FormsModule],
   templateUrl: './stage-requirements.component.html',
   styleUrls: ['./stage-requirements.component.scss']
 })
@@ -139,7 +138,7 @@ export class StageRequirementsComponent implements OnInit {
         await this.wxworkService.initialize(this.cid, 'crm');
         this.currentUser = await this.wxworkService.getCurrentUser();
 
-        const role = this.currentUser?.get('role') || '';
+        const role = this.currentUser?.get('roleName') || '';
         this.canEdit = ['客服', '组员', '组长', '管理员'].includes(role);
       }
 

+ 4 - 3
src/modules/project/pages/project-loader/project-loader.component.ts

@@ -194,10 +194,11 @@ export class ProjectLoaderComponent implements OnInit {
 
     if (projectPointer) {
       // 有项目,加载项目详情
+      let pid = projectPointer.id || projectPointer.objectId
       try {
         const query = new Parse.Query('Project');
         query.include('customer', 'assignee');
-        this.project = await query.get(projectPointer.id);
+        this.project = await query.get(pid);
 
         wxdebug('找到项目', this.project.toJSON());
 
@@ -278,7 +279,7 @@ export class ProjectLoaderComponent implements OnInit {
     }
 
     // 权限检查
-    const role = this.currentUser!.get('role');
+    const role = this.currentUser!.get('roleName');
     if (!['客服', '组长', '管理员'].includes(role)) {
       alert('您没有权限创建项目');
       return;
@@ -402,7 +403,7 @@ export class ProjectLoaderComponent implements OnInit {
    */
   getCurrentUserRole(): string {
     if (!this.currentUser) return '未知';
-    return this.currentUser.get('role') || '未知';
+    return this.currentUser.get('roleName') || '未知';
   }
 
   /**