Browse Source

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

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

重构项目服务,移除不再使用的模型检查项相关代码
徐福静0235668 23 giờ trước cách đây
mục cha
commit
00a914048f
25 tập tin đã thay đổi với 3717 bổ sung983 xóa
  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);
+  }
+}