Bladeren bron

feat(报价系统): 新增报价审核和智能报价功能

添加报价审核模块和智能报价功能,包括以下改进:
1. 在财务仪表板添加报价审核入口
2. 实现自动报价生成和规则配置
3. 新增报价话术库支持
4. 添加项目阶段"后期"到工作流
5. 优化工时统计模块

重构项目服务,移除不再使用的模型检查项相关代码
徐福静0235668 1 dag geleden
bovenliggende
commit
00a914048f
25 gewijzigde bestanden met toevoegingen van 3717 en 983 verwijderingen
  1. 3 1
      src/app/app.routes.ts
  2. 1 1
      src/app/models/project.model.ts
  3. 220 0
      src/app/models/work-hour.model.ts
  4. 1 0
      src/app/pages/customer-service/project-list/project-list.ts
  5. 1 0
      src/app/pages/designer/personal-board/personal-board.ts
  6. 126 9
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.html
  7. 166 0
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.scss
  8. 216 2
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.ts
  9. 0 323
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.html
  10. 0 198
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.scss
  11. 0 311
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.ts
  12. 93 33
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.html
  13. 126 2
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.scss
  14. 178 50
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.ts
  15. 12 0
      src/app/pages/designer/project-detail/project-detail.html
  16. 243 33
      src/app/pages/designer/project-detail/project-detail.ts
  17. 88 0
      src/app/pages/finance/dashboard/dashboard.html
  18. 230 0
      src/app/pages/finance/dashboard/dashboard.scss
  19. 76 2
      src/app/pages/finance/dashboard/dashboard.ts
  20. 303 0
      src/app/pages/finance/quotation-approval/quotation-approval.component.html
  21. 582 0
      src/app/pages/finance/quotation-approval/quotation-approval.component.scss
  22. 274 0
      src/app/pages/finance/quotation-approval/quotation-approval.component.ts
  23. 3 18
      src/app/services/project.service.ts
  24. 435 0
      src/app/services/quotation-approval.service.ts
  25. 340 0
      src/app/services/work-hour.service.ts

+ 3 - 1
src/app/app.routes.ts

@@ -36,6 +36,7 @@ import { Dashboard as FinanceDashboard } from './pages/finance/dashboard/dashboa
 import { ProjectRecords } from './pages/finance/project-records/project-records';
 import { Reconciliation } from './pages/finance/reconciliation/reconciliation';
 import { Reports } from './pages/finance/reports/reports';
+import { QuotationApprovalComponent } from './pages/finance/quotation-approval/quotation-approval.component';
 
 // 人事/行政页面
 import { HrLayout } from './pages/hr/hr-layout/hr-layout';
@@ -116,7 +117,8 @@ export const routes: Routes = [
       { path: 'dashboard', component: FinanceDashboard, title: '财务工作台' },
       { path: 'project-records', component: ProjectRecords, title: '项目流水' },
       { path: 'reconciliation', component: Reconciliation, title: '对账与结算' },
-      { path: 'reports', component: Reports, title: '财务报表' }
+      { path: 'reports', component: Reports, title: '财务报表' },
+      { path: 'quotation-approval', component: QuotationApprovalComponent, title: '报价审核' }
     ]
   },
 

+ 1 - 1
src/app/models/project.model.ts

@@ -49,7 +49,7 @@ export interface CustomerTag {
 export type ProjectStatus = '进行中' | '已完成' | '已暂停' | '已延期';
 
 // 项目阶段
-export type ProjectStage = '订单创建' | '需求沟通' | '方案确认' | '建模' | '软装' | '渲染' | '尾款结算' | '客户评价' | '投诉处理';
+export type ProjectStage = '订单创建' | '需求沟通' | '方案确认' | '建模' | '软装' | '渲染' | '后期' | '尾款结算' | '客户评价' | '投诉处理';
 
 // 任务模型
 // 修复 Task 接口,将 completedAt 改为 completedDate(与实际代码保持一致)

+ 220 - 0
src/app/models/work-hour.model.ts

@@ -0,0 +1,220 @@
+// 工时统计模块模型定义
+
+// 项目阶段类型
+export type ProjectPhase = '建模' | '渲染' | '后期' | '软装' | '对图' | '修改';
+
+// 项目空间类型
+export type SpaceType = '客餐厅' | '卧室' | '厨房' | '卫生间' | '书房' | '阳台' | '玄关' | '别墅';
+
+// 项目风格类型
+export type ProjectStyle = '现代简约' | '新中式' | '北欧' | '工业风' | '轻奢' | '美式' | '欧式' | '日式';
+
+// 绩效等级
+export type PerformanceLevel = 'S' | 'A' | 'B' | 'C';
+
+// 工时记录状态
+export type WorkHourStatus = '工作中' | '停滞' | '等待' | '完成';
+
+// 难度系数配置
+export interface DifficultyCoefficient {
+  spaceType: SpaceType;
+  spaceCoefficient: number; // 空间复杂度系数
+  styleType: ProjectStyle;
+  styleCoefficient: number; // 风格难度系数
+}
+
+// 工时记录
+export interface WorkHourRecord {
+  id: string;
+  projectId: string;
+  projectName: string;
+  designerId: string;
+  designerName: string;
+  phase: ProjectPhase;
+  startTime: Date;
+  endTime?: Date;
+  actualHours: number; // 实际工作时间
+  status: WorkHourStatus;
+  pauseReason?: string; // 停滞原因(如"客户3天未反馈"、"等待渲染1天")
+  pauseHours?: number; // 停滞时间
+  spaceType: SpaceType;
+  projectStyle: ProjectStyle;
+  difficultyCoefficient: number; // 综合难度系数
+  effectiveHours: number; // 有效工时 = 实际工作时间 × 难度系数
+  notes?: string;
+}
+
+// 月度工时统计
+export interface MonthlyWorkHourStats {
+  designerId: string;
+  designerName: string;
+  month: string; // YYYY-MM格式
+  totalActualHours: number; // 总实际工时
+  totalEffectiveHours: number; // 总有效工时
+  totalPauseHours: number; // 总停滞时间
+  completedProjects: number; // 完成项目数
+  averageCustomerSatisfaction: number; // 平均客户满意度
+  performanceLevel: PerformanceLevel; // 绩效等级
+  onTimeCompletionRate: number; // 按时完成率
+  excellentWorkRate: number; // 优秀作品率
+  records: WorkHourRecord[]; // 详细记录
+}
+
+// 绩效等级标准
+export interface PerformanceCriteria {
+  level: PerformanceLevel;
+  timeCompletionRate: number; // 时间完成率阈值(如提前20%以上为S级)
+  customerSatisfactionMin: number; // 客户满意度最低要求
+  description: string;
+  bonusMultiplier: number; // 奖金系数
+}
+
+// 报价规则配置
+export interface PricingRule {
+  spaceType: SpaceType;
+  basePrice: number; // 基础价格(元/人天)
+  styleType: ProjectStyle;
+  styleMultiplier: number; // 风格系数
+  finalPrice: number; // 最终价格 = 基础价格 × 风格系数
+  serviceIncludes: string[]; // 包含服务内容
+}
+
+// 报价话术
+export interface PricingScript {
+  id: string;
+  category: string; // 话术分类(如"高端报价解释"、"价格构成说明")
+  title: string;
+  content: string;
+  applicableScenarios: string[]; // 适用场景
+  tags: string[];
+}
+
+// 自动报价结果
+export interface AutoQuotationResult {
+  id: string;
+  projectId?: string;
+  spaceType: SpaceType;
+  projectStyle: ProjectStyle;
+  estimatedWorkDays: number; // 预估工作天数
+  basePrice: number;
+  styleMultiplier: number;
+  finalPrice: number;
+  serviceIncludes: string[];
+  createdAt: Date;
+  createdBy: string; // 客服ID
+  adjustments?: PriceAdjustment[]; // 价格调整记录
+  status: 'draft' | 'sent' | 'approved' | 'rejected';
+}
+
+// 价格调整记录
+export interface PriceAdjustment {
+  id: string;
+  originalPrice: number;
+  adjustedPrice: number;
+  reason: string;
+  adjustedBy: string; // 调整人ID
+  adjustedAt: Date;
+  approvedBy?: string; // 审批人ID
+  approvedAt?: Date;
+}
+
+// 工时统计仪表板数据
+export interface WorkHourDashboardData {
+  totalDesigners: number;
+  totalProjects: number;
+  averageEffectiveHours: number;
+  performanceDistribution: {
+    S: number;
+    A: number;
+    B: number;
+    C: number;
+  };
+  monthlyTrends: {
+    month: string;
+    totalHours: number;
+    effectiveHours: number;
+    efficiency: number; // 效率 = 有效工时 / 总工时
+  }[];
+  topPerformers: {
+    designerId: string;
+    designerName: string;
+    effectiveHours: number;
+    performanceLevel: PerformanceLevel;
+    efficiency: number;
+  }[];
+}
+
+// 默认难度系数配置
+export const DEFAULT_DIFFICULTY_COEFFICIENTS: DifficultyCoefficient[] = [
+  // 空间类型系数
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '卧室', spaceCoefficient: 0.8, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '厨房', spaceCoefficient: 0.9, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '卫生间', spaceCoefficient: 0.7, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '书房', spaceCoefficient: 0.8, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '阳台', spaceCoefficient: 0.6, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '玄关', spaceCoefficient: 0.5, styleType: '现代简约', styleCoefficient: 1.0 },
+  { spaceType: '别墅', spaceCoefficient: 1.5, styleType: '现代简约', styleCoefficient: 1.0 },
+  
+  // 风格类型系数
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '新中式', styleCoefficient: 1.2 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '北欧', styleCoefficient: 1.0 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '工业风', styleCoefficient: 1.1 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '轻奢', styleCoefficient: 1.3 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '美式', styleCoefficient: 1.2 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '欧式', styleCoefficient: 1.4 },
+  { spaceType: '客餐厅', spaceCoefficient: 1.0, styleType: '日式', styleCoefficient: 1.1 }
+];
+
+// 默认绩效等级标准
+export const DEFAULT_PERFORMANCE_CRITERIA: PerformanceCriteria[] = [
+  {
+    level: 'S',
+    timeCompletionRate: 120, // 提前20%以上完成
+    customerSatisfactionMin: 5.0,
+    description: '提前20%以上完成,客户满意度5分',
+    bonusMultiplier: 1.5
+  },
+  {
+    level: 'A',
+    timeCompletionRate: 100, // 标准时间内完成
+    customerSatisfactionMin: 4.0,
+    description: '标准时间内完成,客户满意度4-5分',
+    bonusMultiplier: 1.2
+  },
+  {
+    level: 'B',
+    timeCompletionRate: 80, // 超时但客户满意度达标
+    customerSatisfactionMin: 4.0,
+    description: '超时但客户满意度4分以上',
+    bonusMultiplier: 1.0
+  },
+  {
+    level: 'C',
+    timeCompletionRate: 0, // 多次修改且耗时过长
+    customerSatisfactionMin: 0,
+    description: '多次修改且耗时过长,客户满意度<4分',
+    bonusMultiplier: 0.8
+  }
+];
+
+// 默认报价规则
+export const DEFAULT_PRICING_RULES: PricingRule[] = [
+  // 客餐厅
+  { spaceType: '客餐厅', basePrice: 600, styleType: '现代简约', styleMultiplier: 1.0, finalPrice: 600, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  { spaceType: '客餐厅', basePrice: 600, styleType: '新中式', styleMultiplier: 1.2, finalPrice: 720, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  { spaceType: '客餐厅', basePrice: 600, styleType: '轻奢', styleMultiplier: 1.5, finalPrice: 900, serviceIncludes: ['建模', '渲染', '1次小图修改', '4K高清交付'] },
+  
+  // 卧室
+  { spaceType: '卧室', basePrice: 400, styleType: '现代简约', styleMultiplier: 1.0, finalPrice: 400, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  { spaceType: '卧室', basePrice: 400, styleType: '新中式', styleMultiplier: 1.2, finalPrice: 480, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  { spaceType: '卧室', basePrice: 400, styleType: '轻奢', styleMultiplier: 1.5, finalPrice: 600, serviceIncludes: ['建模', '渲染', '1次小图修改', '4K高清交付'] },
+  
+  // 厨房
+  { spaceType: '厨房', basePrice: 500, styleType: '现代简约', styleMultiplier: 1.0, finalPrice: 500, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  { spaceType: '厨房', basePrice: 500, styleType: '新中式', styleMultiplier: 1.2, finalPrice: 600, serviceIncludes: ['建模', '渲染', '1次小图修改'] },
+  
+  // 别墅
+  { spaceType: '别墅', basePrice: 1200, styleType: '现代简约', styleMultiplier: 1.0, finalPrice: 1200, serviceIncludes: ['建模', '渲染', '2次修改', '全景6K交付'] },
+  { spaceType: '别墅', basePrice: 1200, styleType: '新中式', styleMultiplier: 1.5, finalPrice: 1800, serviceIncludes: ['建模', '渲染', '2次修改', '全景6K交付', '专属设计师'] }
+];

+ 1 - 0
src/app/pages/customer-service/project-list/project-list.ts

@@ -243,6 +243,7 @@ export class ProjectList implements OnInit, OnDestroy {
         '建模': 40,
         '软装': 60,
         '渲染': 80,
+        '后期': 85,
         '尾款结算': 90,
         '投诉处理': 100,
         订单创建: 0,

+ 1 - 0
src/app/pages/designer/personal-board/personal-board.ts

@@ -322,6 +322,7 @@ export class PersonalBoard implements OnInit, AfterViewInit {
       '建模': 30,
       '软装': 0,
       '渲染': 50,
+      '后期': 20,
       '尾款结算': 0,
       '客户评价': 0,
       '投诉处理': 0

+ 126 - 9
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.html

@@ -3,6 +3,9 @@
     <h3>订单创建信息</h3>
     <span class="required-note">* 为必填项</span>
     <div class="header-actions">
+      <button type="button" class="btn-primary" (click)="showPricingRules()">
+        报价规则配置
+      </button>
       <button type="button" class="btn-secondary" (click)="toggleStagnantFilter()">
         @if (hideStagnantProjects) { 隐藏停滞项目:已开启 } @else { 隐藏停滞项目:未开启 }
       </button>
@@ -10,6 +13,63 @@
   </div>
 
   <form [formGroup]="orderForm" class="order-form">
+    <!-- 自动报价生成 -->
+    <div class="form-group">
+      <label class="form-label">自动报价生成</label>
+      <div class="auto-quotation-section">
+        <div class="quotation-selectors">
+          <div class="selector-group">
+            <label for="spaceType">空间类型</label>
+            <select id="spaceType" class="form-select" [(ngModel)]="selectedSpaceType">
+              <option value="">请选择空间类型</option>
+              @for (space of spaceTypeConfig(); track space.type) {
+                <option [value]="space.type">{{ space.type }} ({{ space.basePrice }}元/人天)</option>
+              }
+            </select>
+          </div>
+          <div class="selector-group">
+            <label for="styleLevel">风格等级</label>
+            <select id="styleLevel" class="form-select" [(ngModel)]="selectedStyleLevel">
+              <option value="">请选择风格等级</option>
+              @for (style of styleLevelConfig(); track style.level) {
+                <option [value]="style.level">{{ style.name }} ({{ style.multiplier }}x)</option>
+              }
+            </select>
+          </div>
+        </div>
+        <div class="quotation-actions">
+          <button type="button" class="btn-primary" (click)="generateAutoQuotation()">
+            生成报价
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 生成的报价清单 -->
+    @if (generatedQuotation().length > 0) {
+      <div class="form-group">
+        <label class="form-label">生成的报价清单</label>
+        <div class="quotation-result">
+          @for (rule of generatedQuotation(); track rule.spaceType) {
+            <div class="quotation-item">
+              <div class="item-header">
+                <h4>{{ rule.spaceType }} - {{ rule.finalPrice }}元</h4>
+                <div class="item-actions">
+                  <button type="button" class="btn-text" (click)="showAdjustModal = true">调整价格</button>
+                </div>
+              </div>
+              <div class="item-details">
+                <p><strong>基础价格:</strong>{{ rule.basePrice }}元/人天</p>
+                <p><strong>风格系数:</strong>{{ rule.styleMultiplier }}x</p>
+                <p><strong>服务内容:</strong>{{ rule.description }}</p>
+                <p><strong>包含服务:</strong>{{ getServiceDescription(rule.spaceType, selectedStyleLevel()) }}</p>
+              </div>
+            </div>
+          }
+        </div>
+      </div>
+    }
+
     <!-- 订单金额 -->
     <div class="form-group">
       <label for="orderAmount" class="form-label required">订单金额</label>
@@ -58,13 +118,70 @@
       }
     </div>
 
-    <!-- 报价明细 -->
-    <div class="form-group">
-      <app-quotation-details
-        [initialData]="quotationData"
-        (dataChange)="onQuotationDataChange($event)"
-      ></app-quotation-details>
-    </div>
-
+    <!-- 报价明细组件 -->
+    <app-quotation-details 
+      [projectData]="{ customerInfo: {}, requirementInfo: {} }"
+      (dataChange)="onQuotationDataChange($event)">
+    </app-quotation-details>
   </form>
-</div>
+</div>
+
+<!-- 报价规则配置弹窗 -->
+@if (showPricingRulesModal()) {
+  <div class="modal-overlay" (click)="hidePricingRules()">
+    <div class="modal-content pricing-rules-modal" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>报价规则配置</h3>
+        <button type="button" class="close-btn" (click)="hidePricingRules()">×</button>
+      </div>
+      
+      <div class="modal-body">
+        <!-- 空间类型配置 -->
+        <div class="config-section">
+          <div class="section-header">
+            <h4>空间类型价格配置</h4>
+            <button type="button" class="btn-secondary" (click)="addSpaceType()">添加空间类型</button>
+          </div>
+          <div class="config-list">
+            @for (space of spaceTypeConfig(); track $index; let i = $index) {
+              <div class="config-item">
+                <div class="config-fields">
+                  <input type="text" [(ngModel)]="space.type" placeholder="空间类型" class="field-input">
+                  <input type="number" [(ngModel)]="space.basePrice" placeholder="基础价格" class="field-input">
+                  <input type="text" [(ngModel)]="space.description" placeholder="描述" class="field-input">
+                  <button type="button" class="btn-danger" (click)="removeSpaceType(i)">删除</button>
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 风格等级配置 -->
+        <div class="config-section">
+          <div class="section-header">
+            <h4>风格等级系数配置</h4>
+            <button type="button" class="btn-secondary" (click)="addStyleLevel()">添加风格等级</button>
+          </div>
+          <div class="config-list">
+            @for (style of styleLevelConfig(); track $index; let i = $index) {
+              <div class="config-item">
+                <div class="config-fields">
+                  <input type="text" [(ngModel)]="style.level" placeholder="等级代码" class="field-input">
+                  <input type="text" [(ngModel)]="style.name" placeholder="等级名称" class="field-input">
+                  <input type="number" [(ngModel)]="style.multiplier" step="0.1" placeholder="系数" class="field-input">
+                  <input type="text" [(ngModel)]="style.description" placeholder="描述" class="field-input">
+                  <button type="button" class="btn-danger" (click)="removeStyleLevel(i)">删除</button>
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+      </div>
+      
+      <div class="modal-footer">
+        <button type="button" class="btn-secondary" (click)="hidePricingRules()">取消</button>
+        <button type="button" class="btn-primary" (click)="savePricingRules()">保存配置</button>
+      </div>
+    </div>
+  </div>
+}

+ 166 - 0
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.scss

@@ -76,4 +76,170 @@
       }
     }
   }
+}
+
+/* 报价规则配置弹窗样式 */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  width: 90%;
+  max-height: 80vh;
+  overflow-y: auto;
+
+  &.pricing-rules-modal {
+    max-width: 900px;
+  }
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px;
+  border-bottom: 1px solid #eee;
+
+  h3 {
+    margin: 0;
+    color: #333;
+    font-size: 18px;
+    font-weight: 600;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 24px;
+    color: #999;
+    cursor: pointer;
+    padding: 0;
+    width: 30px;
+    height: 30px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    &:hover {
+      color: #666;
+    }
+  }
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px;
+  border-top: 1px solid #eee;
+
+  .btn-primary {
+    background: #007aff;
+    color: white;
+    border: none;
+    padding: 10px 20px;
+    border-radius: 6px;
+    cursor: pointer;
+    font-weight: 500;
+
+    &:hover {
+      background: #0056cc;
+    }
+  }
+
+  .btn-secondary {
+    background: #f8f9fa;
+    color: #666;
+    border: 1px solid #ddd;
+    padding: 10px 20px;
+    border-radius: 6px;
+    cursor: pointer;
+    font-weight: 500;
+
+    &:hover {
+      background: #e9ecef;
+    }
+  }
+}
+
+.config-section {
+  margin-bottom: 30px;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    h4 {
+      margin: 0;
+      color: #333;
+      font-size: 16px;
+      font-weight: 600;
+    }
+  }
+}
+
+.config-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.config-item {
+  border: 1px solid #eee;
+  border-radius: 8px;
+  padding: 16px;
+  background: #fafafa;
+
+  .config-fields {
+    display: grid;
+    grid-template-columns: 1fr 1fr 2fr auto;
+    gap: 12px;
+    align-items: center;
+
+    .field-input {
+      padding: 8px 12px;
+      border: 1px solid #ddd;
+      border-radius: 6px;
+      font-size: 14px;
+
+      &:focus {
+        outline: none;
+        border-color: #007aff;
+        box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+      }
+    }
+
+    .btn-danger {
+      background: #dc3545;
+      color: white;
+      border: none;
+      padding: 8px 16px;
+      border-radius: 6px;
+      cursor: pointer;
+      font-size: 12px;
+
+      &:hover {
+        background: #c82333;
+      }
+    }
+  }
 }

+ 216 - 2
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.ts

@@ -1,6 +1,7 @@
-import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnInit, signal } from '@angular/core';
 import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
 import { QuotationDetailsComponent, QuotationData } from '../quotation-details/quotation-details.component';
 
 export interface OrderCreationData {
@@ -9,10 +10,34 @@ export interface OrderCreationData {
   quotationData?: QuotationData;
 }
 
+// 报价规则接口
+export interface PricingRule {
+  spaceType: string;
+  basePrice: number; // 基础价格(元/人天)
+  styleMultiplier: number; // 风格系数
+  finalPrice: number; // 最终价格
+  description: string; // 服务内容描述
+}
+
+// 空间类型配置
+export interface SpaceTypeConfig {
+  type: string;
+  basePrice: number; // 元/人天
+  description: string;
+}
+
+// 风格等级配置
+export interface StyleLevelConfig {
+  level: string;
+  name: string;
+  multiplier: number;
+  description: string;
+}
+
 @Component({
   selector: 'app-order-creation',
   standalone: true,
-  imports: [CommonModule, ReactiveFormsModule, QuotationDetailsComponent],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, QuotationDetailsComponent],
   templateUrl: './order-creation.component.html',
   styleUrls: ['./order-creation.component.scss']
 })
@@ -26,6 +51,34 @@ export class OrderCreationComponent implements OnInit {
   // 替换为停滞项目筛选的本地状态
   hideStagnantProjects = false;
 
+  // 报价规则配置
+  showPricingRulesModal = signal(false);
+  showAdjustModal = false; // 添加调整价格模态框状态
+  
+  // 空间类型配置
+  spaceTypeConfig = signal<SpaceTypeConfig[]>([
+    { type: '客餐厅', basePrice: 600, description: '客厅餐厅一体化设计' },
+    { type: '卧室', basePrice: 400, description: '主卧/次卧设计' },
+    { type: '厨房', basePrice: 500, description: '厨房空间设计' },
+    { type: '卫生间', basePrice: 350, description: '卫生间设计' },
+    { type: '书房', basePrice: 450, description: '书房/工作区设计' },
+    { type: '阳台', basePrice: 300, description: '阳台/露台设计' }
+  ]);
+
+  // 风格等级配置
+  styleLevelConfig = signal<StyleLevelConfig[]>([
+    { level: 'standard', name: '标准', multiplier: 1.0, description: '基础设计方案' },
+    { level: 'premium', name: '高端', multiplier: 1.5, description: '精装设计方案,4K单张,6K全景' },
+    { level: 'luxury', name: '奢华', multiplier: 2.0, description: '顶级设计方案,8K渲染' }
+  ]);
+
+  // 当前选择的报价配置
+  selectedSpaceType = signal<string>('');
+  selectedStyleLevel = signal<string>('');
+  
+  // 生成的报价清单
+  generatedQuotation = signal<PricingRule[]>([]);
+
   constructor(private fb: FormBuilder) {
     this.orderForm = this.fb.group({
       orderAmount: ['', [
@@ -53,6 +106,163 @@ export class OrderCreationComponent implements OnInit {
     this.validityChange.emit(this.isFormValid());
   }
 
+  // 显示报价规则配置
+  showPricingRules(): void {
+    this.showPricingRulesModal.set(true);
+  }
+
+  // 隐藏报价规则配置
+  hidePricingRules(): void {
+    this.showPricingRulesModal.set(false);
+  }
+
+  // 添加空间类型
+  addSpaceType(): void {
+    const current = this.spaceTypeConfig();
+    current.push({ type: '', basePrice: 400, description: '' });
+    this.spaceTypeConfig.set([...current]);
+  }
+
+  // 删除空间类型
+  removeSpaceType(index: number): void {
+    const current = this.spaceTypeConfig();
+    current.splice(index, 1);
+    this.spaceTypeConfig.set([...current]);
+  }
+
+  // 添加风格等级
+  addStyleLevel(): void {
+    const current = this.styleLevelConfig();
+    current.push({ level: '', name: '', multiplier: 1.0, description: '' });
+    this.styleLevelConfig.set([...current]);
+  }
+
+  // 删除风格等级
+  removeStyleLevel(index: number): void {
+    const current = this.styleLevelConfig();
+    current.splice(index, 1);
+    this.styleLevelConfig.set([...current]);
+  }
+
+  // 保存报价规则配置
+  savePricingRules(): void {
+    // 这里应该调用服务保存配置到后端
+    console.log('保存报价规则配置:', {
+      spaceTypes: this.spaceTypeConfig(),
+      styleLevels: this.styleLevelConfig()
+    });
+    this.hidePricingRules();
+  }
+
+  // 生成自动报价
+  generateAutoQuotation(): void {
+    if (!this.selectedSpaceType() || !this.selectedStyleLevel()) {
+      alert('请先选择空间类型和风格等级');
+      return;
+    }
+
+    const spaceConfig = this.spaceTypeConfig().find(s => s.type === this.selectedSpaceType());
+    const styleConfig = this.styleLevelConfig().find(s => s.level === this.selectedStyleLevel());
+
+    if (!spaceConfig || !styleConfig) {
+      alert('配置信息不完整');
+      return;
+    }
+
+    const finalPrice = spaceConfig.basePrice * styleConfig.multiplier;
+    const rule: PricingRule = {
+      spaceType: spaceConfig.type,
+      basePrice: spaceConfig.basePrice,
+      styleMultiplier: styleConfig.multiplier,
+      finalPrice: finalPrice,
+      description: `${styleConfig.name}${spaceConfig.type}项目 - ${spaceConfig.description},${styleConfig.description}`
+    };
+
+    this.generatedQuotation.set([rule]);
+    
+    // 更新订单金额
+    this.orderForm.patchValue({
+      orderAmount: finalPrice
+    });
+
+    // 生成报价数据
+    this.quotationData = {
+      items: [{
+        id: '1',
+        category: '设计服务', // 添加category属性
+        name: `${styleConfig.name}${spaceConfig.type}设计`,
+        quantity: 1,
+        unit: '项',
+        unitPrice: finalPrice,
+        totalPrice: finalPrice,
+        description: rule.description
+      }],
+      totalAmount: finalPrice,
+      materialCost: 0,
+      laborCost: finalPrice,
+      designFee: finalPrice,
+      managementFee: 0
+    };
+
+    this.emitDataChange();
+  }
+
+  // 调整报价
+  adjustQuotation(newPrice: number, reason: string): void {
+    if (this.generatedQuotation().length === 0) {
+      alert('请先生成报价');
+      return;
+    }
+
+    const currentRule = this.generatedQuotation()[0];
+    const adjustedRule: PricingRule = {
+      ...currentRule,
+      finalPrice: newPrice,
+      description: `${currentRule.description} (调整原因: ${reason})`
+    };
+
+    this.generatedQuotation.set([adjustedRule]);
+    
+    // 更新订单金额
+    this.orderForm.patchValue({
+      orderAmount: newPrice
+    });
+
+    // 更新报价数据
+    if (this.quotationData) {
+      this.quotationData.items[0].unitPrice = newPrice;
+      this.quotationData.items[0].totalPrice = newPrice;
+      this.quotationData.totalAmount = newPrice;
+      this.quotationData.laborCost = newPrice;
+      this.quotationData.designFee = newPrice;
+    }
+
+    this.emitDataChange();
+    
+    // 记录调整日志(应该发送到后端)
+    console.log('报价调整记录:', {
+      originalPrice: currentRule.finalPrice,
+      adjustedPrice: newPrice,
+      reason: reason,
+      timestamp: new Date(),
+      spaceType: currentRule.spaceType
+    });
+  }
+
+  // 获取服务内容说明
+  getServiceDescription(spaceType: string, styleLevel: string): string {
+    const spaceConfig = this.spaceTypeConfig().find(s => s.type === spaceType);
+    const styleConfig = this.styleLevelConfig().find(s => s.level === styleLevel);
+    
+    if (!spaceConfig || !styleConfig) return '';
+    
+    const baseServices = ['建模', '渲染', '1次小图修改'];
+    const premiumServices = styleLevel === 'premium' ? ['4K单张交付', '6K全景交付'] : [];
+    const luxuryServices = styleLevel === 'luxury' ? ['8K渲染', '无限次修改'] : [];
+    
+    return [...baseServices, ...premiumServices, ...luxuryServices].join('、');
+  }
+
   // 处理报价明细数据变化
   onQuotationDataChange(data: QuotationData) {
     this.quotationData = data;
@@ -91,6 +301,9 @@ export class OrderCreationComponent implements OnInit {
   resetForm() {
     this.orderForm.reset();
     this.quotationData = undefined;
+    this.generatedQuotation.set([]);
+    this.selectedSpaceType.set('');
+    this.selectedStyleLevel.set('');
   }
 
   // 验证表单
@@ -98,6 +311,7 @@ export class OrderCreationComponent implements OnInit {
     this.orderForm.markAllAsTouched();
     return this.isFormValid();
   }
+
   // 切换停滞项目筛选(仅本地UI状态)
   toggleStagnantFilter() {
     this.hideStagnantProjects = !this.hideStagnantProjects;

+ 0 - 323
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.html

@@ -1,323 +0,0 @@
-<div class="quotation-detail-container">
-  <div class="section-header">
-    <h3>报价明细</h3>
-    <div class="header-actions">
-      @if (orderAmount && orderAmount > 0) {
-        <button 
-          class="ai-generate-btn" 
-          [disabled]="isAiGenerating"
-          (click)="generateAiQuotation()"
-        >
-          @if (isAiGenerating) {
-            <span class="loading-spinner"></span>
-            <span>AI生成中...</span>
-          } @else {
-            <span class="ai-icon">🤖</span>
-            <span>AI辅助生成</span>
-          }
-        </button>
-      }
-      <button class="reset-btn" (click)="resetQuotation()">重置</button>
-    </div>
-  </div>
-
-  <!-- 金额对比提示 -->
-  @if (orderAmount && orderAmount > 0) {
-    <div class="amount-comparison" [class]="getDifferenceStatus()">
-      <div class="comparison-item">
-        <span class="label">订单金额:</span>
-        <span class="amount">{{ orderAmount | number:'1.2-2' }} 元</span>
-      </div>
-      <div class="comparison-item">
-        <span class="label">报价总计:</span>
-        <span class="amount">{{ quotationData.totalAmount | number:'1.2-2' }} 元</span>
-      </div>
-      <div class="difference-indicator">
-        @if (getDifferenceStatus() === 'match') {
-          <span class="match">✓ 金额匹配</span>
-        } @else if (getDifferenceStatus() === 'over') {
-          <span class="over">⚠ 超出 {{ getAmountDifference() | number:'1.2-2' }} 元</span>
-        } @else {
-          <span class="under">⚠ 不足 {{ Math.abs(getAmountDifference()) | number:'1.2-2' }} 元</span>
-        }
-      </div>
-    </div>
-  }
-
-  <!-- 必填空间报价 -->
-  <div class="required-spaces-section">
-    <h4 class="section-title">
-      <span class="title-text">必填空间报价</span>
-      <span class="required-badge">必填</span>
-    </h4>
-    
-    <div class="quotation-items">
-      @for (item of quotationData.items; track item.id) {
-        @if (item.isRequired) {
-          <div class="quotation-item required-item">
-            <div class="item-header">
-              <div class="item-name">
-                <span class="name">{{ item.name }}</span>
-                @if (item.spaceType === 'bedroom' && item.roomIndex && item.roomIndex > 1) {
-                  <span class="room-index">(卧室{{ item.roomIndex }})</span>
-                }
-                <span class="required-indicator">*</span>
-              </div>
-              @if (item.spaceType === 'bedroom' && item.roomIndex && item.roomIndex > 1) {
-                <button class="remove-btn" (click)="removeItem(item.id)">
-                  <span class="remove-icon">×</span>
-                </button>
-              }
-            </div>
-            
-            <div class="item-content">
-              <div class="amount-input-wrapper">
-                <input
-                  type="number"
-                  [value]="item.amount"
-                  (input)="updateItemAmount(item.id, +$any($event.target).value)"
-                  class="amount-input"
-                  placeholder="0"
-                  min="0"
-                  step="0.01"
-                />
-                <span class="currency-unit">元</span>
-              </div>
-              
-              @if (item.description) {
-                <div class="item-description">{{ item.description }}</div>
-              }
-            </div>
-          </div>
-        }
-      }
-    </div>
-
-    <!-- 添加卧室按钮 -->
-    @if (getBedroomCount() < 3) {
-      <div class="add-bedroom-section">
-        <button class="add-bedroom-btn" (click)="addNewBedroom()">
-          <span class="add-icon">+</span>
-          <span>新增卧室</span>
-        </button>
-      </div>
-    }
-  </div>
-
-  <!-- 可选空间报价 -->
-  <div class="optional-spaces-section">
-    <h4 class="section-title">
-      <span class="title-text">可选空间报价</span>
-      <span class="optional-badge">可选</span>
-    </h4>
-    
-    <div class="quotation-items">
-      @for (item of quotationData.items; track item.id) {
-        @if (!item.isRequired && item.category === 'space') {
-          <div class="quotation-item optional-item">
-            <div class="item-header">
-              <div class="item-name">
-                <span class="name">{{ item.name }}</span>
-              </div>
-              <button class="remove-btn" (click)="removeItem(item.id)">
-                <span class="remove-icon">×</span>
-              </button>
-            </div>
-            
-            <div class="item-content">
-              <div class="amount-input-wrapper">
-                <input
-                  type="number"
-                  [value]="item.amount"
-                  (input)="updateItemAmount(item.id, +$any($event.target).value)"
-                  class="amount-input"
-                  placeholder="0"
-                  min="0"
-                  step="0.01"
-                />
-                <span class="currency-unit">元</span>
-              </div>
-              
-              @if (item.description) {
-                <div class="item-description">{{ item.description }}</div>
-              }
-            </div>
-          </div>
-        }
-      }
-    </div>
-
-    <!-- 添加可选空间 -->
-    @if (getAvailableOptionalSpaces().length > 0) {
-      <div class="add-optional-section">
-        @if (!showAddOptionalSpace) {
-          <button class="add-optional-btn" (click)="showAddOptionalSpace = true">
-            <span class="add-icon">+</span>
-            <span>添加空间</span>
-          </button>
-        } @else {
-          <div class="optional-space-selector">
-            <div class="selector-header">
-              <h5>选择要添加的空间</h5>
-              <button class="close-btn" (click)="showAddOptionalSpace = false">×</button>
-            </div>
-            <div class="space-options">
-              @for (space of getAvailableOptionalSpaces(); track space.id) {
-                <button 
-                  class="space-option-btn" 
-                  (click)="addOptionalSpace(space); showAddOptionalSpace = false"
-                >
-                  {{ space.name }}
-                </button>
-              }
-            </div>
-          </div>
-        }
-      </div>
-    }
-  </div>
-
-  <!-- 自定义项目 -->
-  @if (hasCustomItems()) {
-    <div class="custom-items-section">
-      <h4 class="section-title">
-        <span class="title-text">自定义项目</span>
-      </h4>
-      
-      <div class="quotation-items">
-        @for (item of quotationData.items; track item.id) {
-          @if (item.isCustom) {
-            <div class="quotation-item custom-item">
-              <div class="item-header">
-                <div class="item-name">
-                  <span class="name">{{ item.name }}</span>
-                  <span class="custom-tag">自定义</span>
-                </div>
-                <button class="remove-btn" (click)="removeItem(item.id)">
-                  <span class="remove-icon">×</span>
-                </button>
-              </div>
-              
-              <div class="item-content">
-                <div class="amount-input-wrapper">
-                  <input
-                    type="number"
-                    [value]="item.amount"
-                    (input)="updateItemAmount(item.id, +$any($event.target).value)"
-                    class="amount-input"
-                    placeholder="0"
-                    min="0"
-                    step="0.01"
-                  />
-                  <span class="currency-unit">元</span>
-                </div>
-                
-                @if (item.description) {
-                  <div class="item-description">{{ item.description }}</div>
-                }
-              </div>
-            </div>
-          }
-        }
-      </div>
-    </div>
-  }
-
-  <!-- 添加自定义项目 -->
-  <div class="add-custom-section">
-    @if (!showAddCustomItem) {
-      <button class="add-custom-btn" (click)="showAddCustomItem = true">
-        <span class="add-icon">+</span>
-        <span>添加自定义项目</span>
-      </button>
-    } @else {
-      <div class="custom-item-form">
-        <div class="form-header">
-          <h4>添加自定义项目</h4>
-          <button class="close-btn" (click)="showAddCustomItem = false">×</button>
-        </div>
-        
-        <div class="form-content">
-          <div class="form-group">
-            <label>项目名称 *</label>
-            <input
-              type="text"
-              [(ngModel)]="newCustomItem.name"
-              placeholder="请输入项目名称"
-              class="form-input"
-            />
-          </div>
-          
-          <div class="form-group">
-            <label>金额</label>
-            <div class="amount-input-wrapper">
-              <input
-                type="number"
-                [(ngModel)]="newCustomItem.amount"
-                placeholder="0"
-                class="form-input"
-                min="0"
-                step="0.01"
-              />
-              <span class="currency-unit">元</span>
-            </div>
-          </div>
-          
-          <div class="form-group">
-            <label>描述</label>
-            <textarea
-              [(ngModel)]="newCustomItem.description"
-              placeholder="项目描述(可选)"
-              class="form-textarea"
-              rows="2"
-            ></textarea>
-          </div>
-          
-          <div class="form-actions">
-            <button class="cancel-btn" (click)="showAddCustomItem = false">取消</button>
-            <button class="confirm-btn" (click)="addCustomItem()">添加</button>
-          </div>
-        </div>
-      </div>
-    }
-  </div>
-
-
-
-  <!-- 空间统计信息 -->
-  <div class="space-statistics-section">
-    <h4 class="section-title">空间分配统计</h4>
-    <div class="statistics-grid">
-      <div class="statistic-item">
-        <span class="stat-label">客餐厅占比:</span>
-        <span class="stat-value">{{ getSpaceAllocationRatios().livingDiningRatio | number:'1.0-2' }}%</span>
-      </div>
-      <div class="statistic-item">
-        <span class="stat-label">卧室占比:</span>
-        <span class="stat-value">{{ getSpaceAllocationRatios().bedroomRatio | number:'1.0-2' }}%</span>
-      </div>
-      <div class="statistic-item">
-        <span class="stat-label">其他空间占比:</span>
-        <span class="stat-value">{{ getSpaceAllocationRatios().otherSpacesRatio | number:'1.0-2' }}%</span>
-      </div>
-      <div class="statistic-item">
-        <span class="stat-label">卧室数量:</span>
-        <span class="stat-value">{{ getSpaceStatistics().bedroomCount }} 间</span>
-      </div>
-    </div>
-  </div>
-
-  <!-- 总计显示 -->
-  <div class="total-section">
-    <div class="total-row">
-      <span class="total-label">报价总计:</span>
-      <span class="total-amount">{{ quotationData.totalAmount | number:'1.2-2' }} 元</span>
-    </div>
-    @if (quotationData.aiGenerated) {
-      <div class="ai-generated-note">
-        <span class="ai-icon">🤖</span>
-        <span>此报价由AI辅助生成,请根据实际情况调整</span>
-      </div>
-    }
-  </div>
-</div>

+ 0 - 198
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.scss

@@ -1,198 +0,0 @@
-/* 报价详情组件样式 */
-.quotation-detail-container {
-  padding: 20px;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
-
-.quotation-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid #eee;
-
-  h3 {
-    margin: 0;
-    color: #333;
-    font-size: 18px;
-    font-weight: 600;
-  }
-
-  .total-amount {
-    font-size: 20px;
-    font-weight: 700;
-    color: #007aff;
-  }
-}
-
-.quotation-items {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-.quotation-item {
-  padding: 16px;
-  border: 1px solid #eee;
-  border-radius: 8px;
-  background: #fafafa;
-
-  .item-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 12px;
-
-    .item-name {
-      font-weight: 600;
-      color: #333;
-      font-size: 16px;
-    }
-
-    .item-price {
-      font-size: 16px;
-      font-weight: 600;
-      color: #007aff;
-    }
-  }
-
-  .item-details {
-    display: grid;
-    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-    gap: 12px;
-    font-size: 14px;
-    color: #666;
-
-    .detail-item {
-      display: flex;
-      justify-content: space-between;
-
-      .label {
-        font-weight: 500;
-      }
-
-      .value {
-        color: #333;
-      }
-    }
-  }
-}
-
-.empty-state {
-  text-align: center;
-  padding: 40px;
-  color: #999;
-  font-size: 14px;
-}
-
-.quotation-summary {
-  margin-top: 20px;
-  padding: 16px;
-  background: #f8f9fa;
-  border-radius: 8px;
-  border: 1px solid #dee2e6;
-
-  .summary-row {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 8px;
-
-    &:last-child {
-      margin-bottom: 0;
-      padding-top: 8px;
-      border-top: 1px solid #dee2e6;
-      font-weight: 600;
-      font-size: 16px;
-    }
-
-    .label {
-      color: #666;
-    }
-
-    .value {
-      color: #333;
-      font-weight: 500;
-    }
-  }
-}
-
-.form-group {
-  margin-bottom: 16px;
-
-  label {
-    display: block;
-    margin-bottom: 8px;
-    font-weight: 600;
-    color: #333;
-    font-size: 14px;
-  }
-
-  input, select, textarea {
-    width: 100%;
-    padding: 12px;
-    border: 1px solid #ddd;
-    border-radius: 8px;
-    font-size: 14px;
-    transition: border-color 0.2s ease;
-
-    &:focus {
-      outline: none;
-      border-color: #007aff;
-      box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
-    }
-  }
-
-  textarea {
-    min-height: 100px;
-    resize: vertical;
-  }
-}
-
-.form-actions {
-  display: flex;
-  gap: 12px;
-  justify-content: flex-end;
-  margin-top: 20px;
-
-  button {
-    padding: 12px 24px;
-    border: none;
-    border-radius: 8px;
-    font-size: 14px;
-    font-weight: 600;
-    cursor: pointer;
-    transition: all 0.2s ease;
-
-    &.primary {
-      background: #007aff;
-      color: white;
-
-      &:hover {
-        background: #0056cc;
-      }
-    }
-
-    &.secondary {
-      background: #f8f9fa;
-      color: #666;
-      border: 1px solid #ddd;
-
-      &:hover {
-        background: #e9ecef;
-      }
-    }
-
-    &.danger {
-      background: #dc3545;
-      color: white;
-
-      &:hover {
-        background: #c82333;
-      }
-    }
-  }
-}

+ 0 - 311
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.ts

@@ -1,311 +0,0 @@
-import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
-import { CommonModule, DecimalPipe } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-
-export interface QuotationItem {
-  id: string;
-  name: string;
-  amount: number;
-  description?: string;
-  isCustom?: boolean;
-  isRequired?: boolean; // 是否为必填项
-  category?: 'space' | 'service' | 'other'; // 项目类别
-  spaceType?: 'living-dining' | 'bedroom' | 'kitchen' | 'bathroom' | 'study' | 'balcony' | 'other'; // 空间类型
-  roomIndex?: number; // 卧室序号(用于区分多个卧室)
-}
-
-export interface QuotationDetailData {
-  items: QuotationItem[];
-  totalAmount: number;
-  aiGenerated: boolean;
-  projectGroupId?: string; // 关联的项目组ID
-  projectGroupName?: string; // 关联的项目组名称
-}
-
-@Component({
-  selector: 'app-quotation-detail',
-  standalone: true,
-  imports: [CommonModule, FormsModule],
-  templateUrl: './quotation-detail.component.html',
-  styleUrls: ['./quotation-detail.component.scss']
-})
-export class QuotationDetailComponent implements OnInit {
-  @Input() initialData?: QuotationDetailData;
-  @Input() orderAmount?: number;
-  @Output() dataChange = new EventEmitter<QuotationDetailData>();
-
-  quotationData: QuotationDetailData = {
-    items: [],
-    totalAmount: 0,
-    aiGenerated: false
-  };
-
-  // 预设的空间类型(必填项)
-  defaultSpaceTypes = [
-    { id: 'living-dining', name: '客餐厅', amount: 0, isRequired: true, category: 'space' as const, spaceType: 'living-dining' as const },
-    { id: 'bedroom-main', name: '主卧', amount: 0, isRequired: true, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 1 }
-  ];
-
-  // 可选空间类型
-  optionalSpaceTypes = [
-    { id: 'bedroom-secondary', name: '次卧', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 2 },
-    { id: 'bedroom-third', name: '第三卧室', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 3 },
-    { id: 'kitchen', name: '厨房', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'kitchen' as const },
-    { id: 'bathroom', name: '卫生间', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bathroom' as const },
-    { id: 'study', name: '书房', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'study' as const },
-    { id: 'balcony', name: '阳台', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'balcony' as const }
-  ];
-
-  isAiGenerating = false;
-  showAddCustomItem = false;
-  showAddOptionalSpace = false; // 控制添加可选空间的显示
-  newCustomItem = { name: '', amount: 0, description: '' };
-
-  // 暴露Math对象给模板使用
-  Math = Math;
-
-  constructor() {}
-
-  ngOnInit() {
-    if (this.initialData) {
-      this.quotationData = { ...this.initialData };
-    } else {
-      // 初始化必填空间类型
-      this.quotationData.items = this.defaultSpaceTypes.map(space => ({
-        id: space.id,
-        name: space.name,
-        amount: space.amount,
-        isCustom: false,
-        isRequired: space.isRequired,
-        category: space.category
-      }));
-    }
-    this.calculateTotal();
-  }
-
-  // AI辅助生成报价
-  generateAiQuotation() {
-    if (!this.orderAmount || this.orderAmount <= 0) {
-      alert('请先填写订单金额');
-      return;
-    }
-
-    this.isAiGenerating = true;
-    
-    // 模拟AI生成报价(实际项目中应该调用AI服务)
-    setTimeout(() => {
-      const totalAmount = this.orderAmount!;
-      const itemCount = this.quotationData.items.length;
-      
-      // 简单的分配算法:按比例分配
-      const baseAmounts = {
-        'living-dining': 0.60, // 客餐厅占60%
-        'bedroom-main': 0.40   // 主卧占40%
-      };
-
-      this.quotationData.items.forEach(item => {
-        if (!item.isCustom) {
-          const ratio = baseAmounts[item.id as keyof typeof baseAmounts] || 0;
-          item.amount = Math.round(totalAmount * ratio);
-        }
-      });
-
-      this.quotationData.aiGenerated = true;
-      this.calculateTotal();
-      this.isAiGenerating = false;
-      this.emitDataChange();
-    }, 2000);
-  }
-
-  // 更新单项金额
-  updateItemAmount(itemId: string, amount: number) {
-    const item = this.quotationData.items.find(i => i.id === itemId);
-    if (item) {
-      item.amount = amount || 0;
-      this.calculateTotal();
-      this.emitDataChange();
-    }
-  }
-
-  // 添加可选空间
-  addOptionalSpace(spaceType: any) {
-    const existingItem = this.quotationData.items.find(item => item.id === spaceType.id);
-    if (existingItem) {
-      alert('该空间已存在');
-      return;
-    }
-
-    const optionalSpace: QuotationItem = {
-      id: spaceType.id,
-      name: spaceType.name,
-      amount: 0,
-      isCustom: false,
-      isRequired: false,
-      category: spaceType.category
-    };
-
-    this.quotationData.items.push(optionalSpace);
-    this.calculateTotal();
-    this.emitDataChange();
-  }
-
-  // 获取可添加的可选空间
-  getAvailableOptionalSpaces() {
-    return this.optionalSpaceTypes.filter(space => 
-      !this.quotationData.items.some(item => item.id === space.id)
-    );
-  }
-
-  // 添加新的卧室(支持无限添加)
-  addNewBedroom() {
-    const existingBedrooms = this.quotationData.items.filter(item => 
-      item.spaceType === 'bedroom'
-    );
-    
-    const nextRoomIndex = existingBedrooms.length + 1;
-    const bedroomId = `bedroom-${nextRoomIndex}`;
-    
-    const newBedroom: QuotationItem = {
-      id: bedroomId,
-      name: `卧室${nextRoomIndex}`,
-      amount: 0,
-      isCustom: false,
-      isRequired: false,
-      category: 'space',
-      spaceType: 'bedroom',
-      roomIndex: nextRoomIndex
-    };
-
-    this.quotationData.items.push(newBedroom);
-    this.calculateTotal();
-    this.emitDataChange();
-  }
-
-  // 获取空间统计信息
-  getSpaceStatistics() {
-    const spaceItems = this.quotationData.items.filter(item => item.category === 'space');
-    
-    const stats = {
-      totalSpaces: spaceItems.length,
-      livingDiningAmount: spaceItems
-        .filter(item => item.spaceType === 'living-dining')
-        .reduce((sum, item) => sum + (item.amount || 0), 0),
-      bedroomAmount: spaceItems
-        .filter(item => item.spaceType === 'bedroom')
-        .reduce((sum, item) => sum + (item.amount || 0), 0),
-      otherSpacesAmount: spaceItems
-        .filter(item => item.spaceType && !['living-dining', 'bedroom'].includes(item.spaceType))
-        .reduce((sum, item) => sum + (item.amount || 0), 0),
-      bedroomCount: spaceItems.filter(item => item.spaceType === 'bedroom').length
-    };
-
-    return stats;
-  }
-
-  // 获取空间分配比例(用于工作量统计)
-  getSpaceAllocationRatios() {
-    const stats = this.getSpaceStatistics();
-    const totalSpaceAmount = stats.livingDiningAmount + stats.bedroomAmount + stats.otherSpacesAmount;
-    
-    if (totalSpaceAmount === 0) {
-      return {
-        livingDiningRatio: 0,
-        bedroomRatio: 0,
-        otherSpacesRatio: 0
-      };
-    }
-
-    return {
-      livingDiningRatio: (stats.livingDiningAmount / totalSpaceAmount) * 100,
-      bedroomRatio: (stats.bedroomAmount / totalSpaceAmount) * 100,
-      otherSpacesRatio: (stats.otherSpacesAmount / totalSpaceAmount) * 100
-    };
-  }
-
-  // 获取卧室数量
-  getBedroomCount(): number {
-    return this.getSpaceStatistics().bedroomCount;
-  }
-
-  // 添加自定义项目
-  addCustomItem() {
-    if (!this.newCustomItem.name.trim()) {
-      alert('请输入项目名称');
-      return;
-    }
-
-    const customItem: QuotationItem = {
-      id: `custom-${Date.now()}`,
-      name: this.newCustomItem.name,
-      amount: this.newCustomItem.amount || 0,
-      description: this.newCustomItem.description,
-      isCustom: true,
-      isRequired: false,
-      category: 'other'
-    };
-
-    this.quotationData.items.push(customItem);
-    this.calculateTotal();
-    this.emitDataChange();
-
-    // 重置表单
-    this.newCustomItem = { name: '', amount: 0, description: '' };
-    this.showAddCustomItem = false;
-  }
-
-  // 删除项目(只能删除非必填项)
-  removeItem(itemId: string) {
-    const item = this.quotationData.items.find(i => i.id === itemId);
-    if (item && item.isRequired) {
-      alert('必填项目不能删除');
-      return;
-    }
-    
-    this.quotationData.items = this.quotationData.items.filter(item => item.id !== itemId);
-    this.calculateTotal();
-    this.emitDataChange();
-  }
-
-  // 计算总金额
-  calculateTotal() {
-    this.quotationData.totalAmount = this.quotationData.items.reduce((sum, item) => sum + (item.amount || 0), 0);
-  }
-
-  // 发送数据变化事件
-  emitDataChange() {
-    this.dataChange.emit({ ...this.quotationData });
-  }
-
-  // 检查是否有自定义项目
-  hasCustomItems(): boolean {
-    return this.quotationData.items.some(item => item.isCustom);
-  }
-
-  // 重置报价
-  resetQuotation() {
-    // 重置所有项目金额为0
-    this.quotationData.items.forEach(item => {
-      item.amount = 0;
-    });
-    
-    // 移除所有非必填项目(保留必填的客餐厅和主卧)
-    this.quotationData.items = this.quotationData.items.filter(item => item.isRequired);
-    
-    this.quotationData.aiGenerated = false;
-    this.calculateTotal();
-    this.emitDataChange();
-  }
-
-  // 获取金额差异提示
-  getAmountDifference(): number {
-    if (!this.orderAmount) return 0;
-    return this.quotationData.totalAmount - this.orderAmount;
-  }
-
-  // 获取差异状态
-  getDifferenceStatus(): 'match' | 'over' | 'under' {
-    const diff = this.getAmountDifference();
-    if (Math.abs(diff) < 1) return 'match';
-    return diff > 0 ? 'over' : 'under';
-  }
-}

+ 93 - 33
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.html

@@ -2,11 +2,18 @@
   <div class="section-header">
     <h3>报价明细</h3>
     <div class="header-actions">
-      <button type="button" class="btn-secondary" (click)="showAIQuotation()">
+      <button type="button" class="btn-secondary" (click)="showAutoQuotation()">
         <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
           <path d="M8 1L10.5 6H15L11 9.5L12.5 15L8 12L3.5 15L5 9.5L1 6H5.5L8 1Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
         </svg>
-        AI智能报价
+        智能报价
+      </button>
+      <button type="button" class="btn-secondary" (click)="showScriptLibrary()">
+        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M3 3h10a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" stroke="currentColor" stroke-width="1.5" fill="none"/>
+          <path d="M7 8h4M7 10h2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+        </svg>
+        话术库
       </button>
       <button type="button" class="btn-primary" (click)="addQuotationItem()">
         <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -102,7 +109,7 @@
             </svg>
           </div>
           <p>暂无报价项目</p>
-          <p class="empty-hint">点击"添加项目"或"AI智能报价"开始创建报价清单</p>
+          <p class="empty-hint">点击"添加项目"或"智能报价"开始创建报价清单</p>
         </div>
       }
     </div>
@@ -131,62 +138,115 @@
             <span class="summary-value">¥{{ formatAmount(getTotalAmount()) }}</span>
           </div>
         </div>
+        <div class="summary-actions">
+          <button type="button" class="btn-primary" (click)="submitForApproval()" [disabled]="quotationData.totalAmount === 0">
+            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M8 1L10.5 6H15L11 9.5L12.5 15L8 12L3.5 15L5 9.5L1 6H5.5L8 1Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
+            </svg>
+            提交财务审核
+          </button>
+        </div>
       </div>
     }
   </div>
 
-  <!-- AI报价生成弹窗 -->
-  @if (showAIModal) {
-    <div class="modal-overlay" (click)="closeAIModal()">
+
+
+  <!-- 智能报价模态框 -->
+  @if (showAutoQuotationModal) {
+    <div class="modal-overlay" (click)="hideAutoQuotation()">
       <div class="modal-content" (click)="$event.stopPropagation()">
         <div class="modal-header">
-          <h3>AI智能报价</h3>
-          <button type="button" class="btn-close" (click)="closeAIModal()">
-            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-              <path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+          <h4>智能报价生成</h4>
+          <button type="button" class="modal-close" (click)="hideAutoQuotation()">
+            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M15 5L5 15M5 5L15 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
             </svg>
           </button>
         </div>
         <div class="modal-body">
-          <div class="ai-form">
-            <div class="form-group">
-              <label class="form-label">房屋面积(平方米)</label>
-              <input type="number" [(ngModel)]="aiParams.area" class="form-input" placeholder="请输入房屋面积" min="1">
-            </div>
+          <div class="form-grid">
             <div class="form-group">
-              <label class="form-label">装修风格</label>
-              <select [(ngModel)]="aiParams.style" class="form-select">
-                @for (style of styleOptions; track style) {
-                  <option [value]="style">{{ style }}</option>
+              <label class="form-label">空间类型</label>
+              <select [(ngModel)]="autoQuotationParams.spaceType" class="form-select">
+                <option value="">请选择空间类型</option>
+                @for (type of getSpaceTypeOptions(); track type.value) {
+                  <option [value]="type.value">{{ type.label }}</option>
                 }
               </select>
             </div>
             <div class="form-group">
-              <label class="form-label">装修档次</label>
-              <select [(ngModel)]="aiParams.level" class="form-select">
-                @for (level of levelOptions; track level) {
-                  <option [value]="level">{{ level }}</option>
+              <label class="form-label">项目风格</label>
+              <select [(ngModel)]="autoQuotationParams.projectStyle" class="form-select">
+                <option value="">请选择项目风格</option>
+                @for (style of getProjectStyleOptions(); track style.value) {
+                  <option [value]="style.value">{{ style.label }}</option>
                 }
               </select>
             </div>
             <div class="form-group">
-              <label class="form-label">特殊需求</label>
-              <textarea [(ngModel)]="aiParams.specialRequirements" class="form-textarea" placeholder="请输入特殊要求(可选)" rows="3"></textarea>
+              <label class="form-label">预估工作天数</label>
+              <input type="number" [(ngModel)]="autoQuotationParams.estimatedWorkDays" class="form-input" placeholder="请输入预估工作天数" min="1" max="365">
+            </div>
+            <div class="form-group">
+              <label class="form-label">项目面积 (㎡)</label>
+              <input type="number" [(ngModel)]="autoQuotationParams.projectArea" class="form-input" placeholder="请输入项目面积" min="1">
             </div>
           </div>
         </div>
         <div class="modal-footer">
-          <button type="button" class="btn-secondary" (click)="closeAIModal()">取消</button>
-          <button type="button" class="btn-primary" (click)="generateAIQuotation()" [disabled]="aiLoading">
-            @if (aiLoading) {
-              <div class="loading-spinner"></div>
-              生成中...
-            } @else {
-              生成报价
-            }
+          <button type="button" class="btn-secondary" (click)="hideAutoQuotation()">取消</button>
+          <button type="button" class="btn-primary" (click)="generateAutoQuotation()" [disabled]="!autoQuotationParams.spaceType || !autoQuotationParams.projectStyle || !autoQuotationParams.estimatedWorkDays">
+            生成报价
           </button>
         </div>
       </div>
     </div>
   }
+
+  <!-- 话术库模态框 -->
+  @if (showScriptModal) {
+    <div class="modal-overlay" (click)="hideScriptLibrary()">
+      <div class="modal-content script-modal" (click)="$event.stopPropagation()">
+        <div class="modal-header">
+          <h4>报价话术库</h4>
+          <button type="button" class="modal-close" (click)="hideScriptLibrary()">
+            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M15 5L5 15M5 5L15 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+            </svg>
+          </button>
+        </div>
+        <div class="modal-body">
+          <div class="script-categories">
+            @for (script of pricingScripts; track script.id) {
+              <div class="script-item" [class.selected]="selectedScript?.id === script.id" (click)="selectScript(script)">
+                <div class="script-header">
+                  <h5>{{ script.title }}</h5>
+                  <span class="script-category">{{ script.category }}</span>
+                </div>
+                <p class="script-description">{{ script.content.substring(0, 100) }}...</p>
+                @if (selectedScript?.id === script.id) {
+                  <div class="script-content">
+                    <div class="script-text">{{ script.content }}</div>
+                    <div class="script-actions">
+                      <button type="button" class="btn-secondary btn-sm" (click)="copyScript(script); $event.stopPropagation()">
+                        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+                          <path d="M4.5 4.5V2.5C4.5 2.22386 4.72386 2 5 2H11.5C11.7761 2 12 2.22386 12 2.5V9C12 9.27614 11.7761 9.5 11.5 9.5H9.5" stroke="currentColor" stroke-width="1" fill="none"/>
+                          <rect x="2" y="4.5" width="7.5" height="7.5" rx="0.5" stroke="currentColor" stroke-width="1" fill="none"/>
+                        </svg>
+                        复制
+                      </button>
+                    </div>
+                  </div>
+                }
+              </div>
+            }
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn-secondary" (click)="hideScriptLibrary()">关闭</button>
+        </div>
+      </div>
+    </div>
+  }
 </div>

+ 126 - 2
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.scss

@@ -3,7 +3,24 @@
   border-radius: 12px;
   padding: 24px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
+  .summary-actions {
+    margin-top: 16px;
+    padding-top: 16px;
+    border-top: 1px solid #E5E7EB;
+    display: flex;
+    justify-content: flex-end;
+    
+    .btn-primary {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+  }
 
 .section-header {
   display: flex;
@@ -432,4 +449,111 @@
     width: 95%;
     margin: 20px;
   }
-}
+}
+
+// 智能报价和话术库模态框样式
+.script-modal {
+  max-width: 800px;
+  max-height: 80vh;
+  overflow-y: auto;
+}
+
+.script-categories {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  max-height: 500px;
+  overflow-y: auto;
+}
+
+.script-item {
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  padding: 16px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+
+  &:hover {
+    border-color: #3b82f6;
+    background-color: #f8faff;
+  }
+
+  &.selected {
+    border-color: #3b82f6;
+    background-color: #f0f7ff;
+  }
+}
+
+.script-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+
+  h5 {
+    margin: 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #1f2937;
+  }
+}
+
+.script-category {
+  background: #e5e7eb;
+  color: #6b7280;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.script-description {
+  margin: 0 0 12px 0;
+  color: #6b7280;
+  font-size: 14px;
+  line-height: 1.5;
+}
+
+.script-content {
+  border-top: 1px solid #e5e7eb;
+  padding-top: 12px;
+  margin-top: 12px;
+}
+
+.script-text {
+  background: #f9fafb;
+  border: 1px solid #e5e7eb;
+  border-radius: 6px;
+  padding: 12px;
+  font-size: 14px;
+  line-height: 1.6;
+  color: #374151;
+  white-space: pre-wrap;
+  margin-bottom: 12px;
+}
+
+.script-actions {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.btn-sm {
+  padding: 6px 12px;
+  font-size: 12px;
+
+  svg {
+    width: 14px;
+    height: 14px;
+  }
+}
+
+// 表单网格布局
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 16px;
+
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+  }
+}}

+ 178 - 50
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.ts

@@ -1,7 +1,9 @@
 import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { AIQuotationService, AIQuotationParams, AIQuotationResult } from '../../../../../services/ai-quotation.service';
+import { WorkHourService } from '../../../../../services/work-hour.service';
+import { QuotationApprovalService } from '../../../../../services/quotation-approval.service';
+import { SpaceType, ProjectStyle, AutoQuotationResult, PricingScript } from '../../../../../models/work-hour.model';
 
 export interface QuotationItem {
   id: string;
@@ -10,6 +12,7 @@ export interface QuotationItem {
   quantity: number;
   unit: string;
   unitPrice: number;
+  totalPrice?: number; // 添加总价属性
   description: string;
 }
 
@@ -31,6 +34,7 @@ export interface QuotationData {
 })
 export class QuotationDetailsComponent implements OnInit {
   @Input() initialData?: QuotationData;
+  @Input() projectData?: any; // 添加项目数据输入属性
   @Output() dataChange = new EventEmitter<QuotationData>();
 
   quotationData: QuotationData = {
@@ -42,30 +46,32 @@ export class QuotationDetailsComponent implements OnInit {
     managementFee: 0
   };
 
-  // AI报价相关
-  showAIModal = false;
-  aiLoading = false;
-  aiParams: AIQuotationParams = {
-    area: 100,
-    style: '现代简约',
-    level: '舒适型',
-    specialRequirements: ''
+  // 新增:自动报价相关
+  showAutoQuotationModal = false;
+  autoQuotationParams = {
+    spaceType: '客餐厅' as SpaceType,
+    projectStyle: '现代简约' as ProjectStyle,
+    estimatedWorkDays: 3,
+    projectArea: 100
   };
+  
+  // 报价话术库
+  showScriptModal = false;
+  pricingScripts: PricingScript[] = [];
+  selectedScript: PricingScript | null = null;
 
-  // 选项数据
-  styleOptions: string[] = [];
-  levelOptions: string[] = [];
-
-  constructor(private aiQuotationService: AIQuotationService) {}
+  constructor(
+    private workHourService: WorkHourService,
+    private quotationApprovalService: QuotationApprovalService
+  ) {}
 
   ngOnInit() {
     if (this.initialData) {
       this.quotationData = { ...this.initialData };
     }
     
-    // 初始化选项数据
-    this.styleOptions = this.aiQuotationService.getStyleOptions();
-    this.levelOptions = this.aiQuotationService.getLevelOptions();
+    // 加载报价话术库
+    this.loadPricingScripts();
   }
 
   // 添加报价项目
@@ -147,52 +153,174 @@ export class QuotationDetailsComponent implements OnInit {
     this.dataChange.emit({ ...this.quotationData });
   }
 
-  // 显示AI报价模态框
-  showAIQuotation() {
-    this.showAIModal = true;
+
+
+  // 加载报价话术库
+  loadPricingScripts() {
+    this.workHourService.getPricingScripts().subscribe(scripts => {
+      this.pricingScripts = scripts;
+    });
   }
 
-  // 关闭AI报价模态框
-  closeAIModal() {
-    this.showAIModal = false;
-    this.aiLoading = false;
+  // 显示自动报价模态框
+  showAutoQuotation() {
+    this.showAutoQuotationModal = true;
   }
 
-  // 生成AI报价
-  generateAIQuotation() {
-    if (!this.aiParams.area || !this.aiParams.style || !this.aiParams.level) {
-      return;
-    }
+  // 关闭自动报价模态框
+  hideAutoQuotation() {
+    this.showAutoQuotationModal = false;
+  }
 
-    this.aiLoading = true;
-    
-    this.aiQuotationService.generateQuotation(this.aiParams).subscribe({
-      next: (result: AIQuotationResult) => {
-        // 将AI生成的项目转换为报价项目
-        const aiItems: QuotationItem[] = result.items.map(item => ({
-           id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
-           category: item.category,
-           name: item.name,
-           quantity: item.quantity,
-           unit: item.unit,
-           unitPrice: item.unitPrice,
-           description: item.description || ''
-         }));
-
-        // 替换当前报价项目
-        this.quotationData.items = aiItems;
+  // 生成自动报价
+  generateAutoQuotation() {
+    this.workHourService.generateAutoQuotation(
+      this.autoQuotationParams.spaceType,
+      this.autoQuotationParams.projectStyle,
+      this.autoQuotationParams.estimatedWorkDays
+    ).subscribe({
+      next: (result: AutoQuotationResult) => {
+        // 将自动报价结果转换为报价项目
+        const autoItem: QuotationItem = {
+          id: Date.now().toString(),
+          category: result.spaceType,
+          name: `${result.projectStyle}${result.spaceType}设计`,
+          quantity: result.estimatedWorkDays,
+          unit: '人天',
+          unitPrice: result.finalPrice / result.estimatedWorkDays,
+          description: `包含服务:${result.serviceIncludes.join('、')}`
+        };
+
+        // 添加到报价项目列表
+        this.quotationData.items.push(autoItem);
         this.calculateTotal();
         this.emitDataChange();
         
-        this.aiLoading = false;
-        this.closeAIModal();
+        this.hideAutoQuotation();
       },
       error: (error) => {
-        console.error('AI报价生成失败:', error);
-        this.aiLoading = false;
+        console.error('自动报价生成失败:', error);
       }
     });
   }
 
+  // 显示话术库模态框
+  showScriptLibrary() {
+    this.showScriptModal = true;
+  }
+
+  // 关闭话术库模态框
+  hideScriptLibrary() {
+    this.showScriptModal = false;
+    this.selectedScript = null;
+  }
+
+  // 选择话术
+  selectScript(script: PricingScript) {
+    this.selectedScript = script;
+    
+    // 记录话术使用
+    if (this.projectData?.customerInfo?.name) {
+      this.quotationApprovalService.recordScriptUsage(
+        'current_quotation', // 这里应该是实际的报价ID
+        script.id,
+        'current_user', // 这里应该是当前用户ID
+        '当前用户' // 这里应该是当前用户名
+      ).subscribe();
+    }
+  }
+
+  // 复制话术内容
+  copyScript(script: PricingScript) {
+    navigator.clipboard.writeText(script.content).then(() => {
+      console.log('话术内容已复制到剪贴板');
+      
+      // 记录话术使用
+      if (this.projectData?.customerInfo?.name) {
+        this.quotationApprovalService.recordScriptUsage(
+          'current_quotation',
+          script.id,
+          'current_user',
+          '当前用户'
+        ).subscribe();
+      }
+    });
+  }
+  
+  copyScriptContent() {
+    if (this.selectedScript) {
+      navigator.clipboard.writeText(this.selectedScript.content).then(() => {
+        console.log('话术内容已复制到剪贴板');
+        
+        // 记录话术使用
+        if (this.projectData?.customerInfo?.name) {
+          this.quotationApprovalService.recordScriptUsage(
+            'current_quotation',
+            this.selectedScript!.id,
+            'current_user',
+            '当前用户'
+          ).subscribe();
+        }
+      });
+    }
+  }
+
+  // 提交报价审核
+  submitForApproval() {
+    if (!this.projectData?.customerInfo?.name || this.quotationData.totalAmount === 0) {
+      console.warn('报价信息不完整,无法提交审核');
+      return;
+    }
+
+    const submissionData = {
+      quotationId: `q${Date.now()}`,
+      projectId: this.projectData.projectId || `p${Date.now()}`,
+      projectName: this.projectData.requirementInfo?.projectName || '未命名项目',
+      customerName: this.projectData.customerInfo.name,
+      submittedBy: '当前用户', // 这里应该是当前用户名
+      totalAmount: this.quotationData.totalAmount,
+      scriptUsed: this.selectedScript?.id
+    };
+
+    this.quotationApprovalService.submitQuotationForApproval(submissionData).subscribe({
+      next: (approval) => {
+        console.log('报价已提交审核:', approval);
+        // 可以显示成功提示
+      },
+      error: (error) => {
+        console.error('提交审核失败:', error);
+        // 可以显示错误提示
+      }
+    });
+  }
+
+  // 获取空间类型选项
+  getSpaceTypeOptions(): { value: SpaceType; label: string }[] {
+    return [
+      { value: '客餐厅', label: '客餐厅' },
+      { value: '卧室', label: '卧室' },
+      { value: '厨房', label: '厨房' },
+      { value: '卫生间', label: '卫生间' },
+      { value: '书房', label: '书房' },
+      { value: '阳台', label: '阳台' },
+      { value: '玄关', label: '玄关' },
+      { value: '别墅', label: '别墅' }
+    ];
+  }
+
+  // 获取项目风格选项
+  getProjectStyleOptions(): { value: ProjectStyle; label: string }[] {
+    return [
+      { value: '现代简约', label: '现代简约' },
+      { value: '北欧', label: '北欧' },
+      { value: '新中式', label: '新中式' },
+      { value: '美式', label: '美式' },
+      { value: '欧式', label: '欧式' },
+      { value: '日式', label: '日式' },
+      { value: '工业风', label: '工业风' },
+      { value: '轻奢', label: '轻奢' }
+    ];
+  }
+
 
 }

+ 12 - 0
src/app/pages/designer/project-detail/project-detail.html

@@ -136,6 +136,18 @@
       </div>
     </div>
 
+  <!-- 隐藏的文件输入元素 -->
+  @for (process of deliveryProcesses; track process.id) {
+    @for (space of process.spaces; track space.id) {
+      <input 
+        type="file" 
+        [id]="'space-file-input-' + process.id + '-' + space.id"
+        accept="image/*" 
+        multiple 
+        (change)="onSpaceFileSelected($event, process.id, space.id)" 
+        style="display: none;" />
+    }
+  }
 </div>
 
 

+ 243 - 33
src/app/pages/designer/project-detail/project-detail.ts

@@ -7,12 +7,12 @@ import { PaymentVoucherRecognitionService } from '../../../services/payment-vouc
 import {
   Project,
   RenderProgress,
-  ModelCheckItem,
   CustomerFeedback,
   DesignerChange,
   Settlement,
   ProjectStage,
-  PanoramicSynthesis
+  PanoramicSynthesis,
+  ModelCheckItem
 } from '../../../models/project.model';
 import { ConsultationOrderPanelComponent } from '../../../shared/components/consultation-order-panel/consultation-order-panel.component';
 import { RequirementsConfirmCardComponent } from '../../../shared/components/requirements-confirm-card/requirements-confirm-card';
@@ -192,6 +192,32 @@ interface ProposalAnalysis {
   };
 }
 
+// 交付执行板块数据结构
+interface DeliverySpace {
+  id: string;
+  name: string; // 空间名称:卧室、餐厅、厨房等
+  isExpanded: boolean; // 是否展开
+  order: number; // 排序
+}
+
+interface DeliveryProcess {
+  id: string;
+  name: string; // 流程名称:建模、软装、渲染、后期
+  type: 'modeling' | 'softDecor' | 'rendering' | 'postProcess';
+  spaces: DeliverySpace[]; // 该流程下的空间列表
+  isExpanded: boolean; // 是否展开
+  content: {
+    [spaceId: string]: {
+      // 每个空间的具体内容
+      images: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected' }>;
+      progress: number; // 进度百分比
+      status: 'pending' | 'in_progress' | 'completed' | 'approved';
+      notes: string; // 备注
+      lastUpdated: Date;
+    };
+  };
+}
+
 @Component({
   selector: 'app-project-detail',
   standalone: true,
@@ -204,7 +230,6 @@ export class ProjectDetail implements OnInit, OnDestroy {
   projectId: string = '';
   project: Project | undefined;
   renderProgress: RenderProgress | undefined;
-  modelCheckItems: ModelCheckItem[] = [];
   feedbacks: CustomerFeedback[] = [];
   designerChanges: DesignerChange[] = [];
   settlements: Settlement[] = [];
@@ -244,8 +269,8 @@ export class ProjectDetail implements OnInit, OnDestroy {
   referenceImages: any[] = [];
   cadFiles: any[] = [];
   
-  // 新增:9阶段顺序(串式流程)- 删除后期阶段
-  stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理'];
+  // 新增:9阶段顺序(串式流程)- 包含后期阶段
+  stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理'];
   // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
   expandedStages: Partial<Record<ProjectStage, boolean>> = {
     '订单创建': false,
@@ -254,6 +279,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
     '建模': false,
     '软装': false,
     '渲染': false,
+    '后期': false,
     '尾款结算': false,
     '客户评价': false,
     '投诉处理': false,
@@ -316,6 +342,87 @@ export class ProjectDetail implements OnInit, OnDestroy {
   // 视图上下文:根据路由前缀识别角色视角(客服/设计师/组长)
   roleContext: 'customer-service' | 'designer' | 'team-leader' = 'designer';
 
+  // ============ 模型检查项数据 ============
+  modelCheckItems: ModelCheckItem[] = [
+    { id: 'check-1', name: '户型匹配度检查', isPassed: false, notes: '' },
+    { id: 'check-2', name: '尺寸精度验证', isPassed: false, notes: '' },
+    { id: 'check-3', name: '材质贴图检查', isPassed: false, notes: '' },
+    { id: 'check-4', name: '光影效果验证', isPassed: false, notes: '' },
+    { id: 'check-5', name: '细节完整性检查', isPassed: false, notes: '' }
+  ];
+
+  // ============ 交付执行板块数据 ============
+  deliveryProcesses: DeliveryProcess[] = [
+    {
+      id: 'modeling',
+      name: '建模',
+      type: 'modeling',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'softDecor',
+      name: '软装',
+      type: 'softDecor',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'rendering',
+      name: '渲染',
+      type: 'rendering',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'postProcess',
+      name: '后期',
+      type: 'postProcess',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    }
+  ];
+
+  // 新增空间输入框状态
+  newSpaceName: { [processId: string]: string } = {};
+  showAddSpaceInput: { [processId: string]: boolean } = {};
+
   constructor(
     private route: ActivatedRoute,
     private projectService: ProjectService,
@@ -753,7 +860,6 @@ export class ProjectDetail implements OnInit, OnDestroy {
     if (this.projectId) {
       this.loadProjectDetails();
       this.loadRenderProgress();
-      this.loadModelCheckItems();
       this.loadCustomerFeedbacks();
       this.loadDesignerChanges();
       this.loadSettlements();
@@ -967,6 +1073,28 @@ export class ProjectDetail implements OnInit, OnDestroy {
     const currentSec = this.getSectionKeyForStage(currentStage);
     this.expandedSection = currentSec;
     console.log('展开板块:', currentSec);
+    
+    // 新增:如果当前阶段是建模、软装或渲染,自动展开对应的折叠面板
+    if (currentStage === '建模' || currentStage === '软装' || currentStage === '渲染') {
+      const processTypeMap = {
+        '建模': 'modeling',
+        '软装': 'softDecor', 
+        '渲染': 'rendering'
+      };
+      const processType = processTypeMap[currentStage] as 'modeling' | 'softDecor' | 'rendering';
+      const targetProcess = this.deliveryProcesses.find(p => p.type === processType);
+      if (targetProcess) {
+        // 展开对应的流程面板
+        targetProcess.isExpanded = true;
+        // 展开第一个空间以便用户操作
+        if (targetProcess.spaces.length > 0) {
+          targetProcess.spaces[0].isExpanded = true;
+          // 关闭其他空间
+          targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false);
+        }
+        console.log('自动展开折叠面板:', currentStage, processType);
+      }
+    }
   }
   
   // 整理项目详情
@@ -1004,12 +1132,6 @@ export class ProjectDetail implements OnInit, OnDestroy {
     }, 1000);
   }
 
-  loadModelCheckItems(): void {
-    this.projectService.getModelCheckItems().subscribe(items => {
-      this.modelCheckItems = items;
-    });
-  }
-
   loadCustomerFeedbacks(): void {
     this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
       this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
@@ -1051,12 +1173,6 @@ export class ProjectDetail implements OnInit, OnDestroy {
     });
   }
 
-  updateModelCheckItem(itemId: string, isPassed: boolean): void {
-    this.projectService.updateModelCheckItem(itemId, isPassed).subscribe(() => {
-      this.loadModelCheckItems(); // 重新加载检查项
-    });
-  }
-
   updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
     this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => {
       this.loadCustomerFeedbacks(); // 重新加载反馈
@@ -1091,6 +1207,23 @@ export class ProjectDetail implements OnInit, OnDestroy {
       // 更新板块展开状态
       const nextSection = this.getSectionKeyForStage(next);
       this.expandedSection = nextSection;
+      
+      // 新增:自动展开对应阶段的折叠面板
+      if (next === '软装' || next === '渲染') {
+        const processType = next === '软装' ? 'softDecor' : 'rendering';
+        const targetProcess = this.deliveryProcesses.find(p => p.type === processType);
+        if (targetProcess) {
+          // 展开对应的流程面板
+          targetProcess.isExpanded = true;
+          // 展开第一个空间以便用户操作
+          if (targetProcess.spaces.length > 0) {
+            targetProcess.spaces[0].isExpanded = true;
+            // 关闭其他空间
+            targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false);
+          }
+        }
+      }
+      
       // 触发变更检测以更新导航栏颜色
       this.cdr.detectChanges();
     }
@@ -1140,6 +1273,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
       '建模': '项目执行',
       '软装': '项目执行',
       '渲染': '项目执行',
+      '后期': '项目执行',
       '尾款结算': '收尾验收',
       '客户评价': '收尾验收',
       '投诉处理': '收尾验收'
@@ -1253,9 +1387,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   // 检查是否所有模型检查项都已通过
-  areAllModelChecksPassed(): boolean {
-    return this.modelCheckItems.every(item => item.isPassed);
-  }
+
 
   // 获取技能匹配度警告
   getSkillMismatchWarning(): string | null {
@@ -1578,16 +1710,36 @@ export class ProjectDetail implements OnInit, OnDestroy {
 
   removeImageFromPreview(): void {
     if (this.previewImageData) {
-      // 根据图片类型调用相应的删除方法
-      if (this.whiteModelImages.find(i => i.id === this.previewImageData.id)) {
-        this.removeWhiteModelImage(this.previewImageData.id);
-      } else if (this.softDecorImages.find(i => i.id === this.previewImageData.id)) {
-        this.removeSoftDecorImage(this.previewImageData.id);
-      } else if (this.renderLargeImages.find(i => i.id === this.previewImageData.id)) {
-        this.removeRenderLargeImage(this.previewImageData.id);
-      } else if (this.postProcessImages.find(i => i.id === this.previewImageData.id)) {
-        this.removePostProcessImage(this.previewImageData.id);
+      // 首先检查新的 deliveryProcesses 结构
+      let imageFound = false;
+      
+      for (const process of this.deliveryProcesses) {
+        for (const space of process.spaces) {
+          if (process.content[space.id]?.images) {
+            const imageIndex = process.content[space.id].images.findIndex(img => img.id === this.previewImageData.id);
+            if (imageIndex > -1) {
+              this.removeSpaceImage(process.id, space.id, this.previewImageData.id);
+              imageFound = true;
+              break;
+            }
+          }
+        }
+        if (imageFound) break;
       }
+      
+      // 如果在新结构中没找到,检查旧的图片数组
+      if (!imageFound) {
+        if (this.whiteModelImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeWhiteModelImage(this.previewImageData.id);
+        } else if (this.softDecorImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeSoftDecorImage(this.previewImageData.id);
+        } else if (this.renderLargeImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeRenderLargeImage(this.previewImageData.id);
+        } else if (this.postProcessImages.find(i => i.id === this.previewImageData.id)) {
+          this.removePostProcessImage(this.previewImageData.id);
+        }
+      }
+      
       this.closeImagePreview();
     }
   }
@@ -1690,10 +1842,10 @@ export class ProjectDetail implements OnInit, OnDestroy {
     this.postProcessImages = this.postProcessImages.filter(i => i.id !== id);
   }
 
-  // 新增:后期阶段确认上传并自动进入下一阶段(已删除后期阶段,此方法保留但不会被调用
+  // 新增:后期阶段 确认上传并自动进入下一阶段(尾款结算
   confirmPostProcessUpload(): void {
-    // 由于删除了后期阶段,此方法不再使用
-    console.log('后期阶段已删除,此方法不再使用');
+    if (this.postProcessImages.length === 0) return;
+    this.advanceToNextStage('后期');
   }
 
   // 新增:尾款结算阶段确认并自动进入下一阶段(客户评价)
@@ -1799,6 +1951,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
       case '建模':
       case '软装':
       case '渲染':
+      case '后期':
         return 'delivery';
       case '尾款结算':
       case '客户评价':
@@ -1860,6 +2013,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
       '建模': 'modeling',
       '软装': 'softdecor',
       '渲染': 'render',
+      '后期': 'postprocess',
       '尾款结算': 'settlement',
       '客户评价': 'customer-review',
       '投诉处理': 'complaint'
@@ -2379,6 +2533,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
       '建模': 40,
       '软装': 55,
       '渲染': 70,
+      '后期': 80,
       '尾款结算': 90,
       '客户评价': 95,
       '投诉处理': 100
@@ -3545,4 +3700,59 @@ export class ProjectDetail implements OnInit, OnDestroy {
       alert('评价草稿保存成功!您可以稍后继续完善。');
     }, 500);
   }
+
+  // ============ 缺少的方法实现 ============
+  
+  // 处理空间文件选择
+  onSpaceFileSelected(event: Event, processId: string, spaceId: string): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    
+    const files = Array.from(input.files);
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return;
+    
+    files.forEach(file => {
+      if (/\.(jpg|jpeg|png)$/i.test(file.name)) {
+        const imageItem = this.makeImageItem(file);
+        process.content[spaceId].images.push({
+          id: imageItem.id,
+          name: imageItem.name,
+          url: imageItem.url,
+          size: this.formatFileSize(file.size)
+        });
+      }
+    });
+    
+    // 清空输入
+    input.value = '';
+  }
+
+  // 更新模型检查项状态
+  updateModelCheckItem(itemId: string, isPassed: boolean): void {
+    const item = this.modelCheckItems.find(i => i.id === itemId);
+    if (item) {
+      item.isPassed = isPassed;
+      console.log(`模型检查项 ${item.name} 状态更新为: ${isPassed ? '已通过' : '待处理'}`);
+    }
+  }
+
+  // 删除空间图片
+  removeSpaceImage(processId: string, spaceId: string, imageId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (process && process.content[spaceId]) {
+      const images = process.content[spaceId].images;
+      const imageIndex = images.findIndex(img => img.id === imageId);
+      if (imageIndex > -1) {
+        // 释放URL资源
+        const image = images[imageIndex];
+        if (image.url && image.url.startsWith('blob:')) {
+          URL.revokeObjectURL(image.url);
+        }
+        // 从数组中移除
+        images.splice(imageIndex, 1);
+        console.log(`已删除空间图片: ${processId}/${spaceId}/${imageId}`);
+      }
+    }
+  }
 }

+ 88 - 0
src/app/pages/finance/dashboard/dashboard.html

@@ -19,10 +19,98 @@
       <div class="action-icon generate-report">📊</div>
       <div class="action-label">生成日接单表</div>
     </button>
+    <button class="action-btn" (click)="handleQuickAction('quotationApproval')">
+      <div class="action-icon quotation-approval">✅</div>
+      <div class="action-label">报价审核</div>
+    </button>
+    <button class="action-btn work-hour-btn" (click)="toggleWorkHourModule()">
+      <div class="action-icon work-hour">⏱️</div>
+      <div class="action-label">工时统计</div>
+    </button>
   </div>
 
   <!-- 主内容区域 -->
   <div class="dashboard-content">
+    <!-- 工时统计模块 -->
+    <div class="work-hour-module" *ngIf="showWorkHourModule()">
+      <div class="section-header">
+        <h2>有效工时统计</h2>
+        <button class="close-btn" (click)="toggleWorkHourModule()">×</button>
+      </div>
+      
+      <!-- 工时概览卡片 -->
+      <div class="work-hour-overview" *ngIf="workHourDashboard()">
+        <div class="overview-card">
+          <div class="card-icon">👥</div>
+          <div class="card-content">
+            <div class="card-value">{{ workHourDashboard()!.totalDesigners }}</div>
+            <div class="card-label">设计师总数</div>
+          </div>
+        </div>
+        
+        <div class="overview-card">
+          <div class="card-icon">📋</div>
+          <div class="card-content">
+            <div class="card-value">{{ workHourDashboard()!.totalProjects }}</div>
+            <div class="card-label">项目总数</div>
+          </div>
+        </div>
+        
+        <div class="overview-card">
+          <div class="card-icon">⏰</div>
+          <div class="card-content">
+            <div class="card-value">{{ formatWorkHours(workHourDashboard()!.averageEffectiveHours) }}</div>
+            <div class="card-label">平均有效工时</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 绩效等级分布 -->
+      <div class="performance-distribution" *ngIf="workHourDashboard()">
+        <h3>绩效等级分布</h3>
+        <div class="performance-chart">
+          <div class="performance-item" *ngFor="let level of ['S', 'A', 'B', 'C']">
+            <div class="level-indicator" [style.background-color]="getPerformanceLevelColor(level)">
+              {{ level }}
+            </div>
+            <div class="level-info">
+              <div class="level-count">{{ getPerformanceLevelCount(level) }}人</div>
+              <div class="level-desc">{{ getPerformanceLevelDescription(level) }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 月度统计表格 -->
+      <div class="monthly-stats" *ngIf="monthlyStats().length > 0">
+        <h3>设计师月度统计</h3>
+        <div class="stats-table">
+          <div class="table-header">
+            <div class="header-cell">设计师</div>
+            <div class="header-cell">实际工时</div>
+            <div class="header-cell">有效工时</div>
+            <div class="header-cell">完成项目</div>
+            <div class="header-cell">客户满意度</div>
+            <div class="header-cell">绩效等级</div>
+          </div>
+          <div class="table-body">
+            <div class="table-row" *ngFor="let stat of monthlyStats()">
+              <div class="table-cell">{{ stat.designerName }}</div>
+              <div class="table-cell">{{ formatWorkHours(stat.totalActualHours) }}</div>
+              <div class="table-cell">{{ formatWorkHours(stat.totalEffectiveHours) }}</div>
+              <div class="table-cell">{{ stat.completedProjects }}个</div>
+              <div class="table-cell">{{ stat.averageCustomerSatisfaction.toFixed(1) }}分</div>
+              <div class="table-cell">
+                <span class="performance-badge" [style.background-color]="getPerformanceLevelColor(stat.performanceLevel)">
+                  {{ stat.performanceLevel }}
+                </span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
     <!-- 数据概览卡片 -->
     <div class="stats-cards">
       <div class="stat-card">

+ 230 - 0
src/app/pages/finance/dashboard/dashboard.scss

@@ -318,4 +318,234 @@
 
 .action-btn:nth-child(3) {
   animation-delay: 0.2s;
+}
+
+.action-btn:nth-child(4) {
+  animation-delay: 0.3s;
+}
+
+/* 工时统计模块样式 */
+.work-hour-module {
+  background-color: #ffffff;
+  border-radius: 16px;
+  padding: 24px;
+  margin-bottom: 24px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  animation: slideInUp 0.5s ease-out;
+}
+
+.work-hour-module .section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.work-hour-module h2 {
+  font-size: 20px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin: 0;
+}
+
+.close-btn {
+  background: none;
+  border: none;
+  font-size: 24px;
+  color: #8e8e93;
+  cursor: pointer;
+  padding: 4px 8px;
+  border-radius: 8px;
+  transition: background-color 0.2s ease;
+}
+
+.close-btn:hover {
+  background-color: #f2f2f7;
+}
+
+/* 工时概览卡片 */
+.work-hour-overview {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+}
+
+.overview-card {
+  display: flex;
+  align-items: center;
+  padding: 16px;
+  background-color: #f8f9fa;
+  border-radius: 12px;
+  border: 1px solid #e9ecef;
+}
+
+.card-icon {
+  font-size: 24px;
+  margin-right: 12px;
+}
+
+.card-content {
+  flex: 1;
+}
+
+.card-value {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 4px;
+}
+
+.card-label {
+  font-size: 12px;
+  color: #6c6c70;
+}
+
+/* 绩效等级分布 */
+.performance-distribution {
+  margin-bottom: 24px;
+}
+
+.performance-distribution h3 {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 16px;
+}
+
+.performance-chart {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 12px;
+}
+
+.performance-item {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+}
+
+.level-indicator {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-weight: 600;
+  font-size: 14px;
+  margin-right: 12px;
+}
+
+.level-info {
+  flex: 1;
+}
+
+.level-count {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 2px;
+}
+
+.level-desc {
+  font-size: 12px;
+  color: #6c6c70;
+}
+
+/* 月度统计表格 */
+.monthly-stats h3 {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 16px;
+}
+
+.stats-table {
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.table-header {
+  display: grid;
+  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
+  background-color: #e9ecef;
+  padding: 12px 0;
+}
+
+.header-cell {
+  padding: 0 16px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #495057;
+  text-align: center;
+}
+
+.table-body {
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.table-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
+  padding: 12px 0;
+  border-bottom: 1px solid #dee2e6;
+}
+
+.table-row:last-child {
+  border-bottom: none;
+}
+
+.table-cell {
+  padding: 0 16px;
+  font-size: 14px;
+  color: #495057;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.performance-badge {
+  padding: 4px 8px;
+  border-radius: 12px;
+  color: white;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+/* 工时按钮特殊样式 */
+.work-hour-btn {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+}
+
+.work-hour-btn .action-icon {
+  color: white;
+}
+
+.work-hour-btn .action-label {
+  color: white;
+}
+
+.work-hour-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
+}
+
+/* 动画效果 */
+@keyframes slideInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
 }

+ 76 - 2
src/app/pages/finance/dashboard/dashboard.ts

@@ -1,7 +1,9 @@
 import { Component, OnInit, signal } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { RouterModule } from '@angular/router';
+import { RouterModule, Router } from '@angular/router';
 import { AuthService } from '../../../services/auth.service';
+import { WorkHourService } from '../../../services/work-hour.service';
+import { WorkHourDashboardData, MonthlyWorkHourStats, PerformanceLevel } from '../../../models/work-hour.model';
 
 @Component({
   selector: 'app-dashboard',
@@ -33,11 +35,19 @@ export class Dashboard implements OnInit {
   // 用户角色
   userRole = signal('teamLead'); // teamLead 或 juniorMember
 
-  constructor(private authService: AuthService) {}
+  // 工时统计数据
+  workHourDashboard = signal<WorkHourDashboardData | null>(null);
+  monthlyStats = signal<MonthlyWorkHourStats[]>([]);
+  showWorkHourModule = signal(false);
+
+  constructor(private authService: AuthService, private workHourService: WorkHourService, private router: Router) {}
 
   ngOnInit(): void {
     // 初始化用户角色
     this.initializeUserRole();
+    
+    // 加载工时统计数据
+    this.loadWorkHourData();
   }
   
   // 初始化用户角色
@@ -114,6 +124,9 @@ export class Dashboard implements OnInit {
       case 'generateReport':
         window.location.href = '/finance/reports';
         break;
+      case 'quotationApproval':
+        this.router.navigate(['/finance/quotation-approval']);
+        break;
     }
   }
 
@@ -121,4 +134,65 @@ export class Dashboard implements OnInit {
   formatAmount(amount: number): string {
     return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount);
   }
+
+  // 加载工时统计数据
+  loadWorkHourData(): void {
+    this.workHourService.getDashboardData().subscribe(data => {
+      this.workHourDashboard.set(data);
+    });
+
+    this.workHourService.getMonthlyStats().subscribe(stats => {
+      this.monthlyStats.set(stats);
+    });
+  }
+
+  // 切换工时模块显示
+  toggleWorkHourModule(): void {
+    this.showWorkHourModule.set(!this.showWorkHourModule());
+  }
+
+  // 获取绩效等级颜色
+  getPerformanceLevelColor(level: string): string {
+    const colors: Record<string, string> = {
+      'S': '#ff6b6b',
+      'A': '#4ecdc4', 
+      'B': '#45b7d1',
+      'C': '#96ceb4'
+    };
+    return colors[level] || '#ccc';
+  }
+
+  // 获取绩效等级人数
+  getPerformanceLevelCount(level: string): number {
+    const dashboard = this.workHourDashboard();
+    if (!dashboard) return 0;
+    
+    const distribution = dashboard.performanceDistribution as Record<string, number>;
+    return distribution[level] || 0;
+  }
+
+  // 获取绩效等级描述
+  getPerformanceLevelDescription(level: string): string {
+    const descriptions: Record<string, string> = {
+      'S': '卓越表现',
+      'A': '优秀表现',
+      'B': '良好表现', 
+      'C': '待提升'
+    };
+    return descriptions[level] || '未知';
+  }
+
+  // 格式化工时显示
+  formatWorkHours(hours: number): string {
+    const days = Math.floor(hours / 8);
+    const remainingHours = hours % 8;
+    
+    if (days > 0 && remainingHours > 0) {
+      return `${days}天${remainingHours}小时`;
+    } else if (days > 0) {
+      return `${days}天`;
+    } else {
+      return `${remainingHours}小时`;
+    }
+  }
 }

+ 303 - 0
src/app/pages/finance/quotation-approval/quotation-approval.component.html

@@ -0,0 +1,303 @@
+<div class="quotation-approval-container">
+  <!-- 页面头部 -->
+  <div class="page-header">
+    <div class="header-content">
+      <h2>报价审核管理</h2>
+      <p class="header-description">管理和审核设计师提交的项目报价</p>
+    </div>
+    <div class="header-actions">
+      <button type="button" class="btn-secondary" (click)="showStatistics()">
+        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M2 14V10M8 14V6M14 14V2M2 10L8 6L14 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+        </svg>
+        统计报表
+      </button>
+    </div>
+  </div>
+
+  <!-- 统计卡片 -->
+  <div class="stats-cards">
+    <div class="stat-card">
+      <div class="stat-icon pending">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
+          <path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ approvalStats.pending }}</div>
+        <div class="stat-label">待审核</div>
+      </div>
+    </div>
+    
+    <div class="stat-card">
+      <div class="stat-icon approved">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+          <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ approvalStats.approved }}</div>
+        <div class="stat-label">已通过</div>
+      </div>
+    </div>
+    
+    <div class="stat-card">
+      <div class="stat-icon rejected">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
+          <line x1="15" y1="9" x2="9" y2="15" stroke="currentColor" stroke-width="2"/>
+          <line x1="9" y1="9" x2="15" y2="15" stroke="currentColor" stroke-width="2"/>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ approvalStats.rejected }}</div>
+        <div class="stat-label">已拒绝</div>
+      </div>
+    </div>
+    
+    <div class="stat-card">
+      <div class="stat-icon revision">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+          <path d="M3 3v5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ approvalStats.revisionRequired }}</div>
+        <div class="stat-label">需修改</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 筛选区域 -->
+  <div class="filter-section">
+    <div class="filter-row">
+      <div class="filter-group">
+        <label class="filter-label">状态筛选</label>
+        <select [(ngModel)]="statusFilter" (ngModelChange)="applyFilters()" class="filter-select">
+          <option value="all">全部状态</option>
+          <option value="pending">待审核</option>
+          <option value="approved">已通过</option>
+          <option value="rejected">已拒绝</option>
+          <option value="revision_required">需修改</option>
+        </select>
+      </div>
+      
+      <div class="filter-group">
+        <label class="filter-label">关键词搜索</label>
+        <input type="text" [(ngModel)]="searchKeyword" (ngModelChange)="applyFilters()" 
+               class="filter-input" placeholder="搜索项目名称、客户名称或提交人">
+      </div>
+      
+      <div class="filter-group">
+        <label class="filter-label">提交日期</label>
+        <div class="date-range">
+          <input type="date" [(ngModel)]="dateRange.start" (ngModelChange)="applyFilters()" class="filter-input">
+          <span class="date-separator">至</span>
+          <input type="date" [(ngModel)]="dateRange.end" (ngModelChange)="applyFilters()" class="filter-input">
+        </div>
+      </div>
+      
+      <div class="filter-actions">
+        <button type="button" class="btn-secondary" (click)="resetFilters()">重置</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 审核列表 -->
+  <div class="approval-list">
+    <div class="list-header">
+      <h3>审核列表 ({{ filteredApprovals.length }})</h3>
+    </div>
+    
+    @if (filteredApprovals.length === 0) {
+      <div class="empty-state">
+        <div class="empty-icon">
+          <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M24 4L28.5 18H42L32 26.5L36.5 40L24 32L11.5 40L16 26.5L6 18H19.5L24 4Z" stroke="#D1D5DB" stroke-width="2" fill="none"/>
+          </svg>
+        </div>
+        <p>暂无符合条件的审核项目</p>
+        <p class="empty-hint">调整筛选条件或等待新的报价提交</p>
+      </div>
+    } @else {
+      <div class="approval-table">
+        <div class="table-header">
+          <div class="table-cell">项目信息</div>
+          <div class="table-cell">客户信息</div>
+          <div class="table-cell">报价金额</div>
+          <div class="table-cell">提交时间</div>
+          <div class="table-cell">状态</div>
+          <div class="table-cell">操作</div>
+        </div>
+        
+        @for (approval of filteredApprovals; track approval.quotationId) {
+          <div class="table-row">
+            <div class="table-cell">
+              <div class="project-info">
+                <div class="project-name">{{ approval.projectName }}</div>
+                <div class="project-id">ID: {{ approval.quotationId }}</div>
+              </div>
+            </div>
+            <div class="table-cell">
+              <div class="customer-info">
+                <div class="customer-name">{{ approval.customerName }}</div>
+                <div class="submitted-by">提交人: {{ approval.submittedBy }}</div>
+              </div>
+            </div>
+            <div class="table-cell">
+              <div class="amount">¥{{ formatAmount(approval.totalAmount) }}</div>
+            </div>
+            <div class="table-cell">
+              <div class="date">{{ formatDate(approval.submittedAt) }}</div>
+            </div>
+            <div class="table-cell">
+              <span class="status-badge" [class]="getStatusClass(approval.status)">
+                {{ getStatusLabel(approval.status) }}
+              </span>
+            </div>
+            <div class="table-cell">
+              <button type="button" class="btn-primary btn-sm" (click)="viewApprovalDetails(approval)">
+                审核
+              </button>
+            </div>
+          </div>
+        }
+      </div>
+    }
+  </div>
+
+  <!-- 审核详情模态框 -->
+  @if (showApprovalModal && selectedApproval) {
+    <div class="modal-overlay" (click)="closeApprovalModal()">
+      <div class="modal-content approval-modal" (click)="$event.stopPropagation()">
+        <div class="modal-header">
+          <h4>报价审核 - {{ selectedApproval.projectName }}</h4>
+          <button type="button" class="modal-close" (click)="closeApprovalModal()">
+            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M15 5L5 15M5 5L15 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+            </svg>
+          </button>
+        </div>
+        
+        <div class="modal-body">
+          <div class="approval-details">
+            <div class="detail-section">
+              <h5>基本信息</h5>
+              <div class="detail-grid">
+                <div class="detail-item">
+                  <label>项目名称</label>
+                  <span>{{ selectedApproval.projectName }}</span>
+                </div>
+                <div class="detail-item">
+                  <label>客户名称</label>
+                  <span>{{ selectedApproval.customerName }}</span>
+                </div>
+                <div class="detail-item">
+                  <label>提交人</label>
+                  <span>{{ selectedApproval.submittedBy }}</span>
+                </div>
+                <div class="detail-item">
+                  <label>报价金额</label>
+                  <span class="amount-highlight">¥{{ formatAmount(selectedApproval.totalAmount) }}</span>
+                </div>
+                <div class="detail-item">
+                  <label>提交时间</label>
+                  <span>{{ formatDate(selectedApproval.submittedAt) }}</span>
+                </div>
+                <div class="detail-item">
+                  <label>当前状态</label>
+                  <span class="status-badge" [class]="getStatusClass(selectedApproval.status)">
+                    {{ getStatusLabel(selectedApproval.status) }}
+                  </span>
+                </div>
+              </div>
+            </div>
+            
+            @if (selectedApproval.scriptUsed) {
+              <div class="detail-section">
+                <h5>使用话术</h5>
+                <div class="script-info">
+                  <span class="script-id">话术ID: {{ selectedApproval.scriptUsed }}</span>
+                </div>
+              </div>
+            }
+            
+            <div class="detail-section">
+              <h5>审核意见</h5>
+              <textarea [(ngModel)]="newComment" class="comment-textarea" 
+                        placeholder="请输入审核意见..." rows="4"></textarea>
+            </div>
+          </div>
+        </div>
+        
+        <div class="modal-footer">
+          <button type="button" class="btn-secondary" (click)="closeApprovalModal()">取消</button>
+          @if (selectedApproval.status === 'pending') {
+            <button type="button" class="btn-warning" (click)="requestRevision()" 
+                    [disabled]="!newComment.trim()">要求修改</button>
+            <button type="button" class="btn-danger" (click)="rejectQuotation()" 
+                    [disabled]="!newComment.trim()">拒绝</button>
+            <button type="button" class="btn-success" (click)="approveQuotation()">通过</button>
+          }
+        </div>
+      </div>
+    </div>
+  }
+
+  <!-- 统计报表模态框 -->
+  @if (showStatsModal) {
+    <div class="modal-overlay" (click)="closeStatsModal()">
+      <div class="modal-content stats-modal" (click)="$event.stopPropagation()">
+        <div class="modal-header">
+          <h4>话术使用统计</h4>
+          <button type="button" class="modal-close" (click)="closeStatsModal()">
+            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M15 5L5 15M5 5L15 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+            </svg>
+          </button>
+        </div>
+        
+        <div class="modal-body">
+          <div class="stats-content">
+            <div class="stats-summary">
+              <div class="summary-item">
+                <label>平均审核时间</label>
+                <span class="summary-value">{{ approvalStats.averageApprovalTime }} 小时</span>
+              </div>
+              <div class="summary-item">
+                <label>总审核数量</label>
+                <span class="summary-value">{{ approvalStats.total }}</span>
+              </div>
+            </div>
+            
+            @if (scriptUsageStats.length > 0) {
+              <div class="script-stats">
+                <h5>话术使用排行</h5>
+                <div class="stats-list">
+                  @for (stat of scriptUsageStats; track stat.scriptId) {
+                    <div class="stat-item">
+                      <div class="stat-info">
+                        <span class="script-title">{{ stat.scriptTitle }}</span>
+                        <span class="usage-count">使用 {{ stat.usageCount }} 次</span>
+                      </div>
+                      <div class="stat-bar">
+                        <div class="bar-fill" [style.width.%]="(stat.usageCount / scriptUsageStats[0].usageCount) * 100"></div>
+                      </div>
+                    </div>
+                  }
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+        
+        <div class="modal-footer">
+          <button type="button" class="btn-secondary" (click)="closeStatsModal()">关闭</button>
+        </div>
+      </div>
+    </div>
+  }
+</div>

+ 582 - 0
src/app/pages/finance/quotation-approval/quotation-approval.component.scss

@@ -0,0 +1,582 @@
+.quotation-approval-container {
+  padding: 24px;
+  background-color: #F9FAFB;
+  min-height: 100vh;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24px;
+  
+  .header-content {
+    h2 {
+      margin: 0 0 8px 0;
+      font-size: 28px;
+      font-weight: 600;
+      color: #111827;
+    }
+    
+    .header-description {
+      margin: 0;
+      color: #6B7280;
+      font-size: 16px;
+    }
+  }
+  
+  .header-actions {
+    display: flex;
+    gap: 12px;
+  }
+}
+
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+  gap: 20px;
+  margin-bottom: 32px;
+}
+
+.stat-card {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  
+  .stat-icon {
+    width: 48px;
+    height: 48px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    
+    &.pending {
+      background-color: #FEF3C7;
+      color: #D97706;
+    }
+    
+    &.approved {
+      background-color: #D1FAE5;
+      color: #059669;
+    }
+    
+    &.rejected {
+      background-color: #FEE2E2;
+      color: #DC2626;
+    }
+    
+    &.revision {
+      background-color: #E0E7FF;
+      color: #5B21B6;
+    }
+  }
+  
+  .stat-content {
+    .stat-value {
+      font-size: 32px;
+      font-weight: 700;
+      color: #111827;
+      line-height: 1;
+      margin-bottom: 4px;
+    }
+    
+    .stat-label {
+      font-size: 14px;
+      color: #6B7280;
+      font-weight: 500;
+    }
+  }
+}
+
+.filter-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 24px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.filter-row {
+  display: flex;
+  gap: 20px;
+  align-items: flex-end;
+  flex-wrap: wrap;
+}
+
+.filter-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  min-width: 200px;
+  
+  .filter-label {
+    font-size: 14px;
+    font-weight: 500;
+    color: #374151;
+  }
+  
+  .filter-select,
+  .filter-input {
+    padding: 10px 12px;
+    border: 1px solid #D1D5DB;
+    border-radius: 8px;
+    font-size: 14px;
+    
+    &:focus {
+      outline: none;
+      border-color: #3B82F6;
+      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+    }
+  }
+  
+  .date-range {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    
+    .date-separator {
+      color: #6B7280;
+      font-size: 14px;
+    }
+  }
+}
+
+.filter-actions {
+  display: flex;
+  gap: 12px;
+}
+
+.approval-list {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.list-header {
+  padding: 20px 24px;
+  border-bottom: 1px solid #E5E7EB;
+  
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #111827;
+  }
+}
+
+.empty-state {
+  padding: 60px 24px;
+  text-align: center;
+  
+  .empty-icon {
+    margin-bottom: 16px;
+    opacity: 0.5;
+  }
+  
+  p {
+    margin: 8px 0;
+    color: #6B7280;
+    
+    &.empty-hint {
+      font-size: 14px;
+    }
+  }
+}
+
+.approval-table {
+  .table-header {
+    display: grid;
+    grid-template-columns: 2fr 2fr 1.5fr 1.5fr 1fr 1fr;
+    gap: 16px;
+    padding: 16px 24px;
+    background-color: #F9FAFB;
+    border-bottom: 1px solid #E5E7EB;
+    font-weight: 600;
+    font-size: 14px;
+    color: #374151;
+  }
+  
+  .table-row {
+    display: grid;
+    grid-template-columns: 2fr 2fr 1.5fr 1.5fr 1fr 1fr;
+    gap: 16px;
+    padding: 20px 24px;
+    border-bottom: 1px solid #F3F4F6;
+    align-items: center;
+    
+    &:hover {
+      background-color: #F9FAFB;
+    }
+  }
+  
+  .table-cell {
+    font-size: 14px;
+  }
+}
+
+.project-info {
+  .project-name {
+    font-weight: 500;
+    color: #111827;
+    margin-bottom: 4px;
+  }
+  
+  .project-id {
+    font-size: 12px;
+    color: #6B7280;
+  }
+}
+
+.customer-info {
+  .customer-name {
+    font-weight: 500;
+    color: #111827;
+    margin-bottom: 4px;
+  }
+  
+  .submitted-by {
+    font-size: 12px;
+    color: #6B7280;
+  }
+}
+
+.amount {
+  font-weight: 600;
+  color: #059669;
+  font-size: 16px;
+}
+
+.date {
+  color: #6B7280;
+}
+
+.status-badge {
+  display: inline-block;
+  padding: 4px 12px;
+  border-radius: 20px;
+  font-size: 12px;
+  font-weight: 500;
+  
+  &.status-pending {
+    background-color: #FEF3C7;
+    color: #D97706;
+  }
+  
+  &.status-approved {
+    background-color: #D1FAE5;
+    color: #059669;
+  }
+  
+  &.status-rejected {
+    background-color: #FEE2E2;
+    color: #DC2626;
+  }
+  
+  &.status-revision {
+    background-color: #E0E7FF;
+    color: #5B21B6;
+  }
+}
+
+// 模态框样式
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
+  max-height: 90vh;
+  overflow-y: auto;
+  
+  &.approval-modal {
+    width: 90%;
+    max-width: 800px;
+  }
+  
+  &.stats-modal {
+    width: 90%;
+    max-width: 600px;
+  }
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 24px;
+  border-bottom: 1px solid #E5E7EB;
+  
+  h4 {
+    margin: 0;
+    font-size: 20px;
+    font-weight: 600;
+    color: #111827;
+  }
+  
+  .modal-close {
+    background: none;
+    border: none;
+    padding: 8px;
+    cursor: pointer;
+    border-radius: 6px;
+    color: #6B7280;
+    
+    &:hover {
+      background-color: #F3F4F6;
+      color: #374151;
+    }
+  }
+}
+
+.modal-body {
+  padding: 24px;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 24px;
+  border-top: 1px solid #E5E7EB;
+}
+
+.approval-details {
+  .detail-section {
+    margin-bottom: 32px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    h5 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #111827;
+    }
+  }
+  
+  .detail-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+    gap: 16px;
+  }
+  
+  .detail-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    
+    label {
+      font-size: 12px;
+      font-weight: 500;
+      color: #6B7280;
+      text-transform: uppercase;
+      letter-spacing: 0.05em;
+    }
+    
+    span {
+      font-size: 14px;
+      color: #111827;
+      
+      &.amount-highlight {
+        font-weight: 600;
+        color: #059669;
+        font-size: 18px;
+      }
+    }
+  }
+  
+  .script-info {
+    padding: 12px 16px;
+    background-color: #F3F4F6;
+    border-radius: 8px;
+    
+    .script-id {
+      font-size: 14px;
+      color: #6B7280;
+      font-family: monospace;
+    }
+  }
+  
+  .comment-textarea {
+    width: 100%;
+    padding: 12px;
+    border: 1px solid #D1D5DB;
+    border-radius: 8px;
+    font-size: 14px;
+    resize: vertical;
+    
+    &:focus {
+      outline: none;
+      border-color: #3B82F6;
+      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+    }
+  }
+}
+
+.stats-content {
+  .stats-summary {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 20px;
+    margin-bottom: 32px;
+    
+    .summary-item {
+      text-align: center;
+      padding: 20px;
+      background-color: #F9FAFB;
+      border-radius: 8px;
+      
+      label {
+        display: block;
+        font-size: 14px;
+        color: #6B7280;
+        margin-bottom: 8px;
+      }
+      
+      .summary-value {
+        font-size: 24px;
+        font-weight: 700;
+        color: #111827;
+      }
+    }
+  }
+  
+  .script-stats {
+    h5 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #111827;
+    }
+  }
+  
+  .stats-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+  
+  .stat-item {
+    .stat-info {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 8px;
+      
+      .script-title {
+        font-weight: 500;
+        color: #111827;
+      }
+      
+      .usage-count {
+        font-size: 14px;
+        color: #6B7280;
+      }
+    }
+    
+    .stat-bar {
+      height: 8px;
+      background-color: #E5E7EB;
+      border-radius: 4px;
+      overflow: hidden;
+      
+      .bar-fill {
+        height: 100%;
+        background-color: #3B82F6;
+        transition: width 0.3s ease;
+      }
+    }
+  }
+}
+
+// 按钮样式
+.btn-primary,
+.btn-secondary,
+.btn-success,
+.btn-warning,
+.btn-danger {
+  padding: 10px 16px;
+  border: none;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  transition: all 0.2s ease;
+  
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+  
+  &.btn-sm {
+    padding: 6px 12px;
+    font-size: 12px;
+  }
+}
+
+.btn-primary {
+  background-color: #3B82F6;
+  color: white;
+  
+  &:hover:not(:disabled) {
+    background-color: #2563EB;
+  }
+}
+
+.btn-secondary {
+  background-color: #F3F4F6;
+  color: #374151;
+  
+  &:hover:not(:disabled) {
+    background-color: #E5E7EB;
+  }
+}
+
+.btn-success {
+  background-color: #059669;
+  color: white;
+  
+  &:hover:not(:disabled) {
+    background-color: #047857;
+  }
+}
+
+.btn-warning {
+  background-color: #D97706;
+  color: white;
+  
+  &:hover:not(:disabled) {
+    background-color: #B45309;
+  }
+}
+
+.btn-danger {
+  background-color: #DC2626;
+  color: white;
+  
+  &:hover:not(:disabled) {
+    background-color: #B91C1C;
+  }
+}

+ 274 - 0
src/app/pages/finance/quotation-approval/quotation-approval.component.ts

@@ -0,0 +1,274 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { QuotationApprovalService } from '../../../services/quotation-approval.service';
+import { QuotationApproval, QuotationApprovalStatus, CommunicationRecord, ScriptUsageStats } from '../../../services/quotation-approval.service';
+
+@Component({
+  selector: 'app-quotation-approval',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './quotation-approval.component.html',
+  styleUrls: ['./quotation-approval.component.scss']
+})
+export class QuotationApprovalComponent implements OnInit {
+  quotationApprovals: QuotationApproval[] = [];
+  filteredApprovals: QuotationApproval[] = [];
+  scriptUsageStats: ScriptUsageStats[] = [];
+  
+  // 筛选条件
+  statusFilter: QuotationApprovalStatus | 'all' = 'all';
+  searchKeyword = '';
+  dateRange = {
+    start: '',
+    end: ''
+  };
+  
+  // 统计数据
+  approvalStats = {
+    total: 0,
+    pending: 0,
+    approved: 0,
+    rejected: 0,
+    revisionRequired: 0,
+    averageApprovalTime: 0
+  };
+  
+  // 选中的审核项
+  selectedApproval: QuotationApproval | null = null;
+  
+  // 沟通记录
+  communicationRecords: CommunicationRecord[] = [];
+  newComment = '';
+  
+  // 模态框状态
+  showApprovalModal = false;
+  showStatsModal = false;
+
+  constructor(private quotationApprovalService: QuotationApprovalService) {}
+
+  ngOnInit() {
+    this.loadQuotationApprovals();
+    this.loadScriptUsageStats();
+    this.loadApprovalStats();
+  }
+
+  // 加载报价审核列表
+  loadQuotationApprovals() {
+    this.quotationApprovalService.getQuotationApprovals().subscribe({
+      next: (approvals: QuotationApproval[]) => {
+        this.quotationApprovals = approvals;
+        this.applyFilters();
+      },
+      error: (error: any) => {
+        console.error('加载报价审核列表失败:', error);
+      }
+    });
+  }
+
+  // 加载话术使用统计
+  loadScriptUsageStats() {
+    this.quotationApprovalService.getScriptUsageStats().subscribe({
+      next: (stats: ScriptUsageStats[]) => {
+        this.scriptUsageStats = stats;
+      },
+      error: (error: any) => {
+        console.error('加载话术使用统计失败:', error);
+      }
+    });
+  }
+
+  // 加载审核统计
+  loadApprovalStats() {
+    this.quotationApprovalService.getApprovalStats().subscribe({
+      next: (stats: any) => {
+        this.approvalStats = stats;
+      },
+      error: (error: any) => {
+        console.error('加载审核统计失败:', error);
+      }
+    });
+  }
+
+  // 应用筛选条件
+  applyFilters() {
+    this.filteredApprovals = this.quotationApprovals.filter(approval => {
+      // 状态筛选
+      if (this.statusFilter !== 'all' && approval.status !== this.statusFilter) {
+        return false;
+      }
+      
+      // 关键词搜索
+      if (this.searchKeyword) {
+        const keyword = this.searchKeyword.toLowerCase();
+        if (!approval.projectName.toLowerCase().includes(keyword) &&
+            !approval.customerName.toLowerCase().includes(keyword) &&
+            !approval.submittedBy.toLowerCase().includes(keyword)) {
+          return false;
+        }
+      }
+      
+      // 日期范围筛选
+      if (this.dateRange.start) {
+        const startDate = new Date(this.dateRange.start);
+        if (new Date(approval.submittedAt) < startDate) {
+          return false;
+        }
+      }
+      
+      if (this.dateRange.end) {
+        const endDate = new Date(this.dateRange.end);
+        endDate.setHours(23, 59, 59, 999);
+        if (new Date(approval.submittedAt) > endDate) {
+          return false;
+        }
+      }
+      
+      return true;
+    });
+  }
+
+  // 重置筛选条件
+  resetFilters() {
+    this.statusFilter = 'all';
+    this.searchKeyword = '';
+    this.dateRange = { start: '', end: '' };
+    this.applyFilters();
+  }
+
+  // 查看审核详情
+  viewApprovalDetails(approval: QuotationApproval) {
+    this.selectedApproval = approval;
+    this.loadCommunicationRecords(approval.quotationId);
+    this.showApprovalModal = true;
+  }
+
+  // 加载沟通记录
+  loadCommunicationRecords(quotationId: string) {
+    // 这里应该调用服务获取沟通记录
+    // 暂时使用模拟数据
+    this.communicationRecords = [];
+  }
+
+  // 审批报价
+  approveQuotation() {
+    if (!this.selectedApproval) return;
+    
+    this.quotationApprovalService.approveQuotation(
+      this.selectedApproval.quotationId,
+      'current_user',
+      this.newComment
+    ).subscribe({
+      next: (updatedApproval) => {
+        this.updateApprovalInList(updatedApproval);
+        this.closeApprovalModal();
+        this.loadApprovalStats();
+      },
+      error: (error) => {
+        console.error('审批失败:', error);
+      }
+    });
+  }
+
+  // 拒绝报价
+  rejectQuotation() {
+    if (!this.selectedApproval) return;
+    
+    this.quotationApprovalService.rejectQuotation(
+      this.selectedApproval.quotationId,
+      'current_user',
+      this.newComment
+    ).subscribe({
+      next: (updatedApproval) => {
+        this.updateApprovalInList(updatedApproval);
+        this.closeApprovalModal();
+        this.loadApprovalStats();
+      },
+      error: (error) => {
+        console.error('拒绝失败:', error);
+      }
+    });
+  }
+
+  // 要求修改
+  requestRevision() {
+    if (!this.selectedApproval) return;
+    
+    this.quotationApprovalService.requestRevision(
+      this.selectedApproval.quotationId,
+      'current_user',
+      this.newComment
+    ).subscribe({
+      next: (updatedApproval) => {
+        this.updateApprovalInList(updatedApproval);
+        this.closeApprovalModal();
+        this.loadApprovalStats();
+      },
+      error: (error) => {
+        console.error('要求修改失败:', error);
+      }
+    });
+  }
+
+  // 更新列表中的审核项
+  updateApprovalInList(updatedApproval: QuotationApproval) {
+    const index = this.quotationApprovals.findIndex(a => a.quotationId === updatedApproval.quotationId);
+    if (index !== -1) {
+      this.quotationApprovals[index] = updatedApproval;
+      this.applyFilters();
+    }
+  }
+
+  // 关闭审核模态框
+  closeApprovalModal() {
+    this.showApprovalModal = false;
+    this.selectedApproval = null;
+    this.newComment = '';
+    this.communicationRecords = [];
+  }
+
+  // 显示统计模态框
+  showStatistics() {
+    this.showStatsModal = true;
+  }
+
+  // 关闭统计模态框
+  closeStatsModal() {
+    this.showStatsModal = false;
+  }
+
+  // 格式化金额
+  formatAmount(amount: number): string {
+    return amount.toLocaleString('zh-CN', {
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2
+    });
+  }
+
+  // 格式化日期
+  formatDate(date: string | Date): string {
+    return new Date(date).toLocaleString('zh-CN');
+  }
+
+  // 获取状态标签
+  getStatusLabel(status: QuotationApprovalStatus): string {
+    const statusMap = {
+      'pending': '待审核',
+      'approved': '已通过',
+      'rejected': '已拒绝',
+      'revision_required': '需修改'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取状态样式类
+  getStatusClass(status: QuotationApprovalStatus): string {
+    const classMap = {
+      'pending': 'status-pending',
+      'approved': 'status-approved',
+      'rejected': 'status-rejected',
+      'revision_required': 'status-revision'
+    };
+    return classMap[status] || '';
+  }
+}

+ 3 - 18
src/app/services/project.service.ts

@@ -4,7 +4,6 @@ import {
   Project,
   Task,
   RenderProgress,
-  ModelCheckItem,
   CustomerFeedback,
   DesignerChange,
   Settlement,
@@ -164,11 +163,7 @@ export class ProjectService {
     }
   ];
 
-  private modelCheckItems: ModelCheckItem[] = [
-    { id: 'm1', name: '尺寸准确性', isPassed: true },
-    { id: 'm2', name: '比例协调性', isPassed: true },
-    { id: 'm3', name: '户型匹配度', isPassed: false, notes: '需调整沙发位置' }
-  ];
+
 
   private feedbacks: CustomerFeedback[] = [
     {
@@ -627,19 +622,9 @@ export class ProjectService {
     return of(this.renderProgresses.find(rp => rp.projectId === projectId));
   }
 
-  // 获取模型检查清单
-  getModelCheckItems(): Observable<ModelCheckItem[]> {
-    return of(this.modelCheckItems);
-  }
 
-  // 更新模型检查项
-  updateModelCheckItem(itemId: string, isPassed: boolean): Observable<ModelCheckItem> {
-    const item = this.modelCheckItems.find(i => i.id === itemId);
-    if (item) {
-      item.isPassed = isPassed;
-    }
-    return of(item as ModelCheckItem);
-  }
+
+
 
   // 获取客户反馈
   getCustomerFeedbacks(): Observable<CustomerFeedback[]> {

+ 435 - 0
src/app/services/quotation-approval.service.ts

@@ -0,0 +1,435 @@
+import { Injectable, signal } from '@angular/core';
+import { Observable, of, BehaviorSubject } from 'rxjs';
+import { delay, map } from 'rxjs/operators';
+
+// 报价审核状态
+export type QuotationApprovalStatus = 'pending' | 'approved' | 'rejected' | 'revision_required';
+
+// 报价审核记录
+export interface QuotationApproval {
+  id: string;
+  quotationId: string;
+  projectId: string;
+  projectName: string;
+  customerName: string;
+  submittedBy: string; // 提交人
+  submittedAt: Date;
+  totalAmount: number;
+  status: QuotationApprovalStatus;
+  approvedBy?: string; // 审批人
+  approvedAt?: Date;
+  rejectionReason?: string;
+  revisionNotes?: string;
+  scriptUsed?: string; // 使用的话术模板ID
+  communicationLog: CommunicationRecord[];
+}
+
+// 沟通记录
+export interface CommunicationRecord {
+  id: string;
+  timestamp: Date;
+  type: 'script_used' | 'customer_response' | 'internal_note' | 'approval_comment';
+  content: string;
+  scriptId?: string; // 如果是使用话术,记录话术ID
+  userId: string;
+  userName: string;
+}
+
+// 话术使用统计
+export interface ScriptUsageStats {
+  scriptId: string;
+  scriptTitle: string;
+  usageCount: number;
+  successRate: number; // 成功率(客户接受报价的比例)
+  avgResponseTime: number; // 平均响应时间(小时)
+  lastUsed: string;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class QuotationApprovalService {
+  private approvals = signal<QuotationApproval[]>([]);
+  private scriptUsageStats = signal<ScriptUsageStats[]>([]);
+  
+  // 实时通知流
+  private approvalUpdates$ = new BehaviorSubject<QuotationApproval | null>(null);
+
+  constructor() {
+    this.initializeMockData();
+  }
+
+  // 初始化模拟数据
+  private initializeMockData(): void {
+    const mockApprovals: QuotationApproval[] = [
+      {
+        id: 'qa001',
+        quotationId: 'q001',
+        projectId: 'p001',
+        projectName: '现代简约客厅设计',
+        customerName: '张先生',
+        submittedBy: '李设计师',
+        submittedAt: new Date('2024-01-15T10:30:00'),
+        totalAmount: 85000,
+        status: 'pending',
+        scriptUsed: 'script001',
+        communicationLog: [
+          {
+            id: 'comm001',
+            timestamp: new Date('2024-01-15T10:30:00'),
+            type: 'script_used',
+            content: '使用了"高端设计报价说明"话术模板',
+            scriptId: 'script001',
+            userId: 'designer001',
+            userName: '李设计师'
+          }
+        ]
+      },
+      {
+        id: 'qa002',
+        quotationId: 'q002',
+        projectId: 'p002',
+        projectName: '北欧风卧室设计',
+        customerName: '王女士',
+        submittedBy: '陈设计师',
+        submittedAt: new Date('2024-01-14T14:20:00'),
+        totalAmount: 65000,
+        status: 'approved',
+        approvedBy: '财务主管',
+        approvedAt: new Date('2024-01-14T16:45:00'),
+        scriptUsed: 'script002',
+        communicationLog: [
+          {
+            id: 'comm002',
+            timestamp: new Date('2024-01-14T14:20:00'),
+            type: 'script_used',
+            content: '使用了"中档设计报价说明"话术模板',
+            scriptId: 'script002',
+            userId: 'designer002',
+            userName: '陈设计师'
+          },
+          {
+            id: 'comm003',
+            timestamp: new Date('2024-01-14T16:45:00'),
+            type: 'approval_comment',
+            content: '报价合理,符合市场标准,批准通过',
+            userId: 'finance001',
+            userName: '财务主管'
+          }
+        ]
+      }
+    ];
+
+    const mockStats: ScriptUsageStats[] = [
+      {
+        scriptId: 'script001',
+        scriptTitle: '高端设计报价说明',
+        usageCount: 15,
+        successRate: 0.73,
+        avgResponseTime: 2.5,
+        lastUsed: '2024-01-15T10:30:00'
+      },
+      {
+        scriptId: 'script002',
+        scriptTitle: '中档设计报价说明',
+        usageCount: 28,
+        successRate: 0.82,
+        avgResponseTime: 1.8,
+        lastUsed: '2024-01-14T14:20:00'
+      },
+      {
+        scriptId: 'script003',
+        scriptTitle: '基础设计报价说明',
+        usageCount: 35,
+        successRate: 0.89,
+        avgResponseTime: 1.2,
+        lastUsed: '2024-01-13T09:15:00'
+      }
+    ];
+
+    this.approvals.set(mockApprovals);
+    this.scriptUsageStats.set(mockStats);
+  }
+
+  // 获取报价审核列表
+  getQuotationApprovals(): Observable<QuotationApproval[]> {
+    return of(this.approvals()).pipe(delay(300));
+  }
+
+  // 根据状态筛选报价审核记录
+  getApprovalsByStatus(status: QuotationApprovalStatus): Observable<QuotationApproval[]> {
+    return of(this.approvals().filter(approval => approval.status === status)).pipe(delay(300));
+  }
+
+  // 获取单个报价审核记录
+  getApprovalById(id: string): Observable<QuotationApproval | undefined> {
+    return of(this.approvals().find(approval => approval.id === id)).pipe(delay(200));
+  }
+
+  // 提交报价审核
+  submitQuotationForApproval(quotationData: {
+    quotationId: string;
+    projectId: string;
+    projectName: string;
+    customerName: string;
+    submittedBy: string;
+    totalAmount: number;
+    scriptUsed?: string;
+  }): Observable<QuotationApproval> {
+    const newApproval: QuotationApproval = {
+      id: `qa${Date.now()}`,
+      ...quotationData,
+      submittedAt: new Date(),
+      status: 'pending',
+      communicationLog: quotationData.scriptUsed ? [{
+        id: `comm${Date.now()}`,
+        timestamp: new Date(),
+        type: 'script_used',
+        content: `使用了话术模板进行报价说明`,
+        scriptId: quotationData.scriptUsed,
+        userId: 'current_user',
+        userName: quotationData.submittedBy
+      }] : []
+    };
+
+    const updatedApprovals = [...this.approvals(), newApproval];
+    this.approvals.set(updatedApprovals);
+    
+    // 通知更新
+    this.approvalUpdates$.next(newApproval);
+    
+    return of(newApproval).pipe(delay(500));
+  }
+
+  // 审批报价
+  approveQuotation(approvalId: string, approvedBy: string, comment?: string): Observable<QuotationApproval> {
+    const approvals = this.approvals();
+    const approvalIndex = approvals.findIndex(a => a.id === approvalId);
+    
+    if (approvalIndex === -1) {
+      throw new Error('报价审核记录不存在');
+    }
+
+    const updatedApproval = {
+      ...approvals[approvalIndex],
+      status: 'approved' as QuotationApprovalStatus,
+      approvedBy,
+      approvedAt: new Date(),
+      communicationLog: [
+        ...approvals[approvalIndex].communicationLog,
+        {
+          id: `comm${Date.now()}`,
+          timestamp: new Date(),
+          type: 'approval_comment' as const,
+          content: comment || '报价已批准',
+          userId: 'current_user',
+          userName: approvedBy
+        }
+      ]
+    };
+
+    const updatedApprovals = [...approvals];
+    updatedApprovals[approvalIndex] = updatedApproval;
+    this.approvals.set(updatedApprovals);
+    
+    // 通知更新
+    this.approvalUpdates$.next(updatedApproval);
+    
+    return of(updatedApproval).pipe(delay(300));
+  }
+
+  // 拒绝报价
+  rejectQuotation(approvalId: string, rejectedBy: string, reason: string): Observable<QuotationApproval> {
+    const approvals = this.approvals();
+    const approvalIndex = approvals.findIndex(a => a.id === approvalId);
+    
+    if (approvalIndex === -1) {
+      throw new Error('报价审核记录不存在');
+    }
+
+    const updatedApproval = {
+      ...approvals[approvalIndex],
+      status: 'rejected' as QuotationApprovalStatus,
+      approvedBy: rejectedBy,
+      approvedAt: new Date(),
+      rejectionReason: reason,
+      communicationLog: [
+        ...approvals[approvalIndex].communicationLog,
+        {
+          id: `comm${Date.now()}`,
+          timestamp: new Date(),
+          type: 'approval_comment' as const,
+          content: `报价被拒绝:${reason}`,
+          userId: 'current_user',
+          userName: rejectedBy
+        }
+      ]
+    };
+
+    const updatedApprovals = [...approvals];
+    updatedApprovals[approvalIndex] = updatedApproval;
+    this.approvals.set(updatedApprovals);
+    
+    // 通知更新
+    this.approvalUpdates$.next(updatedApproval);
+    
+    return of(updatedApproval).pipe(delay(300));
+  }
+
+  // 要求修订报价
+  requestRevision(approvalId: string, requestedBy: string, notes: string): Observable<QuotationApproval> {
+    const approvals = this.approvals();
+    const approvalIndex = approvals.findIndex(a => a.id === approvalId);
+    
+    if (approvalIndex === -1) {
+      throw new Error('报价审核记录不存在');
+    }
+
+    const updatedApproval = {
+      ...approvals[approvalIndex],
+      status: 'revision_required' as QuotationApprovalStatus,
+      revisionNotes: notes,
+      communicationLog: [
+        ...approvals[approvalIndex].communicationLog,
+        {
+          id: `comm${Date.now()}`,
+          timestamp: new Date(),
+          type: 'approval_comment' as const,
+          content: `要求修订:${notes}`,
+          userId: 'current_user',
+          userName: requestedBy
+        }
+      ]
+    };
+
+    const updatedApprovals = [...approvals];
+    updatedApprovals[approvalIndex] = updatedApproval;
+    this.approvals.set(updatedApprovals);
+    
+    // 通知更新
+    this.approvalUpdates$.next(updatedApproval);
+    
+    return of(updatedApproval).pipe(delay(300));
+  }
+
+  // 记录话术使用
+  recordScriptUsage(approvalId: string, scriptId: string, userId: string, userName: string): Observable<void> {
+    const approvals = this.approvals();
+    const approvalIndex = approvals.findIndex(a => a.id === approvalId);
+    
+    if (approvalIndex !== -1) {
+      const communicationRecord: CommunicationRecord = {
+        id: `comm${Date.now()}`,
+        timestamp: new Date(),
+        type: 'script_used',
+        content: '使用了话术模板进行沟通',
+        scriptId,
+        userId,
+        userName
+      };
+
+      const updatedApproval = {
+        ...approvals[approvalIndex],
+        communicationLog: [...approvals[approvalIndex].communicationLog, communicationRecord]
+      };
+
+      const updatedApprovals = [...approvals];
+      updatedApprovals[approvalIndex] = updatedApproval;
+      this.approvals.set(updatedApprovals);
+      
+      // 更新话术使用统计
+      this.updateScriptUsageStats(scriptId);
+    }
+    
+    return of(void 0).pipe(delay(200));
+  }
+
+  // 记录客户响应
+  recordCustomerResponse(approvalId: string, response: string, userId: string, userName: string): Observable<void> {
+    const approvals = this.approvals();
+    const approvalIndex = approvals.findIndex(a => a.id === approvalId);
+    
+    if (approvalIndex !== -1) {
+      const communicationRecord: CommunicationRecord = {
+        id: `comm${Date.now()}`,
+        timestamp: new Date(),
+        type: 'customer_response',
+        content: response,
+        userId,
+        userName
+      };
+
+      const updatedApproval = {
+        ...approvals[approvalIndex],
+        communicationLog: [...approvals[approvalIndex].communicationLog, communicationRecord]
+      };
+
+      const updatedApprovals = [...approvals];
+      updatedApprovals[approvalIndex] = updatedApproval;
+      this.approvals.set(updatedApprovals);
+    }
+    
+    return of(void 0).pipe(delay(200));
+  }
+
+  // 获取话术使用统计
+  getScriptUsageStats(): Observable<ScriptUsageStats[]> {
+    return of(this.scriptUsageStats()).pipe(delay(300));
+  }
+
+  // 获取实时更新流
+  getApprovalUpdates(): Observable<QuotationApproval | null> {
+    return this.approvalUpdates$.asObservable();
+  }
+
+  // 更新话术使用统计
+  private updateScriptUsageStats(scriptId: string): void {
+    const stats = this.scriptUsageStats();
+    const statIndex = stats.findIndex(s => s.scriptId === scriptId);
+    
+    if (statIndex !== -1) {
+      const updatedStats = [...stats];
+      updatedStats[statIndex] = {
+        ...updatedStats[statIndex],
+        usageCount: updatedStats[statIndex].usageCount + 1,
+        lastUsed: new Date().toISOString()
+      };
+      this.scriptUsageStats.set(updatedStats);
+    }
+  }
+
+  // 获取审核统计数据
+  getApprovalStats(): Observable<{
+    total: number;
+    pending: number;
+    approved: number;
+    rejected: number;
+    revisionRequired: number;
+    avgApprovalTime: number; // 平均审批时间(小时)
+  }> {
+    const approvals = this.approvals();
+    const total = approvals.length;
+    const pending = approvals.filter(a => a.status === 'pending').length;
+    const approved = approvals.filter(a => a.status === 'approved').length;
+    const rejected = approvals.filter(a => a.status === 'rejected').length;
+    const revisionRequired = approvals.filter(a => a.status === 'revision_required').length;
+    
+    // 计算平均审批时间
+    const completedApprovals = approvals.filter(a => a.approvedAt);
+    const avgApprovalTime = completedApprovals.length > 0 
+      ? completedApprovals.reduce((sum, approval) => {
+          const timeDiff = approval.approvedAt!.getTime() - approval.submittedAt.getTime();
+          return sum + (timeDiff / (1000 * 60 * 60)); // 转换为小时
+        }, 0) / completedApprovals.length
+      : 0;
+
+    return of({
+      total,
+      pending,
+      approved,
+      rejected,
+      revisionRequired,
+      avgApprovalTime
+    }).pipe(delay(200));
+  }
+}

+ 340 - 0
src/app/services/work-hour.service.ts

@@ -0,0 +1,340 @@
+import { Injectable } from '@angular/core';
+import { Observable, of, BehaviorSubject } from 'rxjs';
+import { 
+  WorkHourRecord, 
+  MonthlyWorkHourStats, 
+  WorkHourDashboardData, 
+  DifficultyCoefficient,
+  PerformanceCriteria,
+  PricingRule,
+  PricingScript,
+  AutoQuotationResult,
+  PriceAdjustment,
+  PerformanceLevel,
+  SpaceType,
+  ProjectStyle,
+  DEFAULT_DIFFICULTY_COEFFICIENTS,
+  DEFAULT_PERFORMANCE_CRITERIA,
+  DEFAULT_PRICING_RULES
+} from '../models/work-hour.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class WorkHourService {
+  private workHourRecords: WorkHourRecord[] = [];
+  private monthlyStats: MonthlyWorkHourStats[] = [];
+  private difficultyCoefficients = DEFAULT_DIFFICULTY_COEFFICIENTS;
+  private performanceCriteria = DEFAULT_PERFORMANCE_CRITERIA;
+  private pricingRules = DEFAULT_PRICING_RULES;
+  private pricingScripts: PricingScript[] = [];
+  
+  // 数据变更通知
+  private workHourDataSubject = new BehaviorSubject<WorkHourRecord[]>([]);
+  public workHourData$ = this.workHourDataSubject.asObservable();
+
+  constructor() {
+    this.initializeMockData();
+  }
+
+  // 初始化模拟数据
+  private initializeMockData(): void {
+    // 模拟工时记录数据
+    const mockRecords: WorkHourRecord[] = [
+      {
+        id: 'wh001',
+        projectId: 'p001',
+        projectName: '现代简约客厅设计',
+        designerId: 'd001',
+        designerName: '张设计师',
+        phase: '建模',
+        startTime: new Date('2024-01-15T09:00:00'),
+        endTime: new Date('2024-01-17T18:00:00'),
+        actualHours: 16,
+        status: '完成',
+        spaceType: '客餐厅',
+        projectStyle: '现代简约',
+        difficultyCoefficient: 1.0,
+        effectiveHours: 16,
+        notes: '按时完成建模工作'
+      },
+      {
+        id: 'wh002',
+        projectId: 'p002',
+        projectName: '新中式别墅设计',
+        designerId: 'd002',
+        designerName: '李设计师',
+        phase: '建模',
+        startTime: new Date('2024-01-10T09:00:00'),
+        endTime: new Date('2024-01-13T18:00:00'),
+        actualHours: 24,
+        status: '完成',
+        pauseReason: '客户2天未反馈',
+        pauseHours: 16,
+        spaceType: '别墅',
+        projectStyle: '新中式',
+        difficultyCoefficient: 1.8, // 1.5 * 1.2
+        effectiveHours: 43.2, // 24 * 1.8
+        notes: '复杂项目,需要多次沟通'
+      },
+      {
+        id: 'wh003',
+        projectId: 'p003',
+        projectName: '轻奢卧室设计',
+        designerId: 'd001',
+        designerName: '张设计师',
+        phase: '渲染',
+        startTime: new Date('2024-01-18T09:00:00'),
+        endTime: new Date('2024-01-19T15:00:00'),
+        actualHours: 12,
+        status: '完成',
+        spaceType: '卧室',
+        projectStyle: '轻奢',
+        difficultyCoefficient: 1.04, // 0.8 * 1.3
+        effectiveHours: 12.48,
+        notes: '渲染效果优秀'
+      }
+    ];
+
+    this.workHourRecords = mockRecords;
+    this.workHourDataSubject.next(mockRecords);
+
+    // 初始化报价话术库
+    this.pricingScripts = [
+      {
+        id: 'ps001',
+        category: '高端报价解释',
+        title: '高端报价价值说明',
+        content: '我们的高端报价包含更精细的建模工艺、4K高清渲染交付、以及专业的后期处理,确保您获得更好的视觉表现效果。相比标准版本,高端版本在细节处理和最终效果上有显著提升。',
+        applicableScenarios: ['客户质疑高端报价', '解释价格差异'],
+        tags: ['高端', '价值', '效果']
+      },
+      {
+        id: 'ps002',
+        category: '价格构成说明',
+        title: '报价构成详细说明',
+        content: '我们的报价主要包含三个部分:1)建模费用(占40%)- 根据空间复杂度计算;2)渲染费用(占35%)- 包含灯光、材质、后期处理;3)设计服务费(占25%)- 包含方案沟通、修改服务等。',
+        applicableScenarios: ['客户询问价格构成', '报价透明化说明'],
+        tags: ['构成', '透明', '详细']
+      },
+      {
+        id: 'ps003',
+        category: '修改服务说明',
+        title: '修改次数和范围说明',
+        content: '标准报价包含1次小图修改(调整色彩、材质等细节),如需大幅度修改方案或增加修改次数,会根据工作量适当调整费用。我们建议在建模阶段充分沟通需求,以减少后期修改。',
+        applicableScenarios: ['客户询问修改政策', '设定修改预期'],
+        tags: ['修改', '服务', '范围']
+      }
+    ];
+  }
+
+  // 获取工时记录
+  getWorkHourRecords(designerId?: string, projectId?: string): Observable<WorkHourRecord[]> {
+    let records = this.workHourRecords;
+    
+    if (designerId) {
+      records = records.filter(r => r.designerId === designerId);
+    }
+    
+    if (projectId) {
+      records = records.filter(r => r.projectId === projectId);
+    }
+    
+    return of(records);
+  }
+
+  // 添加工时记录
+  addWorkHourRecord(record: Omit<WorkHourRecord, 'id' | 'effectiveHours'>): Observable<WorkHourRecord> {
+    const newRecord: WorkHourRecord = {
+      ...record,
+      id: `wh${Date.now()}`,
+      effectiveHours: this.calculateEffectiveHours(record.actualHours, record.spaceType, record.projectStyle)
+    };
+    
+    this.workHourRecords.push(newRecord);
+    this.workHourDataSubject.next(this.workHourRecords);
+    
+    return of(newRecord);
+  }
+
+  // 计算有效工时
+  calculateEffectiveHours(actualHours: number, spaceType: SpaceType, projectStyle: ProjectStyle): number {
+    const spaceCoeff = this.difficultyCoefficients.find(c => c.spaceType === spaceType)?.spaceCoefficient || 1.0;
+    const styleCoeff = this.difficultyCoefficients.find(c => c.styleType === projectStyle)?.styleCoefficient || 1.0;
+    const totalCoeff = spaceCoeff * styleCoeff;
+    
+    return actualHours * totalCoeff;
+  }
+
+  // 获取月度统计数据
+  getMonthlyStats(designerId?: string, month?: string): Observable<MonthlyWorkHourStats[]> {
+    // 基于工时记录计算月度统计
+    const stats = this.calculateMonthlyStats(designerId, month);
+    return of(stats);
+  }
+
+  // 计算月度统计
+  private calculateMonthlyStats(designerId?: string, month?: string): MonthlyWorkHourStats[] {
+    const records = this.workHourRecords.filter(r => {
+      if (designerId && r.designerId !== designerId) return false;
+      if (month && !r.startTime.toISOString().startsWith(month)) return false;
+      return true;
+    });
+
+    const groupedByDesigner = records.reduce((acc, record) => {
+      const key = record.designerId;
+      if (!acc[key]) {
+        acc[key] = [];
+      }
+      acc[key].push(record);
+      return acc;
+    }, {} as Record<string, WorkHourRecord[]>);
+
+    return Object.entries(groupedByDesigner).map(([designerId, records]) => {
+      const totalActualHours = records.reduce((sum, r) => sum + r.actualHours, 0);
+      const totalEffectiveHours = records.reduce((sum, r) => sum + r.effectiveHours, 0);
+      const totalPauseHours = records.reduce((sum, r) => sum + (r.pauseHours || 0), 0);
+      const completedProjects = new Set(records.map(r => r.projectId)).size;
+      const averageCustomerSatisfaction = 4.5; // 模拟数据
+      
+      const performanceLevel = this.calculatePerformanceLevel(
+        totalEffectiveHours,
+        completedProjects,
+        averageCustomerSatisfaction
+      );
+
+      return {
+        designerId,
+        designerName: records[0].designerName,
+        month: month || new Date().toISOString().substring(0, 7),
+        totalActualHours,
+        totalEffectiveHours,
+        totalPauseHours,
+        completedProjects,
+        averageCustomerSatisfaction,
+        performanceLevel,
+        onTimeCompletionRate: 85, // 模拟数据
+        excellentWorkRate: 78, // 模拟数据
+        records
+      };
+    });
+  }
+
+  // 计算绩效等级
+  private calculatePerformanceLevel(effectiveHours: number, completedProjects: number, satisfaction: number): PerformanceLevel {
+    if (satisfaction >= 5.0 && effectiveHours > 100) return 'S';
+    if (satisfaction >= 4.0 && effectiveHours > 80) return 'A';
+    if (satisfaction >= 4.0) return 'B';
+    return 'C';
+  }
+
+  // 获取仪表板数据
+  getDashboardData(): Observable<WorkHourDashboardData> {
+    const monthlyStats = this.calculateMonthlyStats();
+    
+    const dashboardData: WorkHourDashboardData = {
+      totalDesigners: new Set(this.workHourRecords.map(r => r.designerId)).size,
+      totalProjects: new Set(this.workHourRecords.map(r => r.projectId)).size,
+      averageEffectiveHours: monthlyStats.reduce((sum, s) => sum + s.totalEffectiveHours, 0) / monthlyStats.length || 0,
+      performanceDistribution: {
+        S: monthlyStats.filter(s => s.performanceLevel === 'S').length,
+        A: monthlyStats.filter(s => s.performanceLevel === 'A').length,
+        B: monthlyStats.filter(s => s.performanceLevel === 'B').length,
+        C: monthlyStats.filter(s => s.performanceLevel === 'C').length
+      },
+      monthlyTrends: [
+        { month: '2024-01', totalHours: 320, effectiveHours: 384, efficiency: 120 },
+        { month: '2024-02', totalHours: 280, effectiveHours: 336, efficiency: 120 },
+        { month: '2024-03', totalHours: 360, effectiveHours: 432, efficiency: 120 }
+      ],
+      topPerformers: monthlyStats
+        .sort((a, b) => b.totalEffectiveHours - a.totalEffectiveHours)
+        .slice(0, 5)
+        .map(s => ({
+          designerId: s.designerId,
+          designerName: s.designerName,
+          effectiveHours: s.totalEffectiveHours,
+          performanceLevel: s.performanceLevel,
+          efficiency: Math.round((s.totalEffectiveHours / s.totalActualHours) * 100)
+        }))
+    };
+
+    return of(dashboardData);
+  }
+
+  // 获取报价规则
+  getPricingRules(): Observable<PricingRule[]> {
+    return of(this.pricingRules);
+  }
+
+  // 自动生成报价
+  generateAutoQuotation(spaceType: SpaceType, projectStyle: ProjectStyle, estimatedWorkDays: number = 3): Observable<AutoQuotationResult> {
+    const rule = this.pricingRules.find(r => r.spaceType === spaceType && r.styleType === projectStyle);
+    
+    if (!rule) {
+      throw new Error(`未找到匹配的报价规则: ${spaceType} - ${projectStyle}`);
+    }
+
+    const quotation: AutoQuotationResult = {
+      id: `aq${Date.now()}`,
+      spaceType,
+      projectStyle,
+      estimatedWorkDays,
+      basePrice: rule.basePrice,
+      styleMultiplier: rule.styleMultiplier,
+      finalPrice: rule.finalPrice * estimatedWorkDays,
+      serviceIncludes: [...rule.serviceIncludes],
+      createdAt: new Date(),
+      createdBy: 'current-user', // 应该从认证服务获取
+      status: 'draft'
+    };
+
+    return of(quotation);
+  }
+
+  // 获取报价话术库
+  getPricingScripts(category?: string): Observable<PricingScript[]> {
+    let scripts = this.pricingScripts;
+    
+    if (category) {
+      scripts = scripts.filter(s => s.category === category);
+    }
+    
+    return of(scripts);
+  }
+
+  // 添加价格调整记录
+  addPriceAdjustment(quotationId: string, adjustment: Omit<PriceAdjustment, 'id'>): Observable<PriceAdjustment> {
+    const newAdjustment: PriceAdjustment = {
+      ...adjustment,
+      id: `pa${Date.now()}`
+    };
+    
+    // 在实际应用中,这里应该更新对应的报价记录
+    console.log('价格调整记录已添加:', newAdjustment);
+    
+    return of(newAdjustment);
+  }
+
+  // 获取难度系数配置
+  getDifficultyCoefficients(): Observable<DifficultyCoefficient[]> {
+    return of(this.difficultyCoefficients);
+  }
+
+  // 更新难度系数配置
+  updateDifficultyCoefficients(coefficients: DifficultyCoefficient[]): Observable<boolean> {
+    this.difficultyCoefficients = coefficients;
+    return of(true);
+  }
+
+  // 获取绩效标准配置
+  getPerformanceCriteria(): Observable<PerformanceCriteria[]> {
+    return of(this.performanceCriteria);
+  }
+
+  // 更新绩效标准配置
+  updatePerformanceCriteria(criteria: PerformanceCriteria[]): Observable<boolean> {
+    this.performanceCriteria = criteria;
+    return of(true);
+  }
+}