Jelajahi Sumber

feat:finance-demo01

0235711 18 jam lalu
induk
melakukan
6ec7984aa1

+ 89 - 1
src/app/pages/finance/dashboard/dashboard.html

@@ -1 +1,89 @@
-<p>dashboard works!</p>
+<div class="finance-dashboard">
+  <!-- 顶部标题区域 -->
+  <div class="dashboard-header">
+    <h1 class="dashboard-title">财务工作台</h1>
+    <div class="current-date">{{ today | date: 'yyyy年MM月dd日' }}</div>
+  </div>
+
+  <!-- 快捷操作区域 -->
+  <div class="quick-actions">
+    <button class="action-btn" (click)="handleQuickAction('newQuote')">
+      <div class="action-icon new-quote">📝</div>
+      <div class="action-label">新建报价</div>
+    </button>
+    <button class="action-btn" (click)="handleQuickAction('recordPayment')">
+      <div class="action-icon record-payment">💰</div>
+      <div class="action-label">录入收款</div>
+    </button>
+    <button class="action-btn" (click)="handleQuickAction('generateReport')">
+      <div class="action-icon generate-report">📊</div>
+      <div class="action-label">生成日接单表</div>
+    </button>
+  </div>
+
+  <!-- 主内容区域 -->
+  <div class="dashboard-content">
+    <!-- 数据概览卡片 -->
+    <div class="stats-cards">
+      <div class="stat-card">
+        <div class="stat-icon orders"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 3h18v18H3V3zm6 6v2H7V9h2zm0 4v2H7v-2h2zm0 4v2H7v-2h2zm4-8v2h-2V9h2zm0 4v2h-2v-2h2zm0 4v2h-2v-2h2zm4-8v2h-2V9h2zm0 4v2h-2v-2h2zm0 4v2h-2v-2h2z"/></svg></div>
+        <div class="stat-content">
+          <div class="stat-value">{{ dashboardStats().todayOrders }}</div>
+          <div class="stat-label">今日接单量</div>
+        </div>
+        <button class="view-detail-btn" (click)="handleQuickAction('generateReport')">查看详情</button>
+      </div>
+
+      <div class="stat-card">
+        <div class="stat-icon payment"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 6h-2c0-1.654-1.346-3-3-3H9C7.346 3 6 4.346 6 6H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h16c1.103 0 2-.897 2-2V8c0-1.103-.897-2-2-2zM9 6c0-.551.448-1 1-1h4c.552 0 1 .449 1 1v2H9V6zm10 12H4v-4h16v4z"/></svg></div>
+        <div class="stat-content">
+          <div class="stat-value" *ngIf="userRole() === 'teamLead'; else hiddenValue">{{ formatAmount(dashboardStats().pendingPayment) }}</div>
+          <ng-template #hiddenValue><div class="stat-value">--</div></ng-template>
+          <div class="stat-label">待收款金额</div>
+        </div>
+        <button class="view-detail-btn" (click)="handleQuickAction('recordPayment')">查看详情</button>
+      </div>
+
+      <div class="stat-card">
+        <div class="stat-icon quotes"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 3v3a2 2 0 01-2 2H3m18 0h-3a2 2 0 01-2-2V3m0 18v-3a2 2 0 012-2h3M3 16h3a2 2 0 012 2v3"/></svg></div>
+        <div class="stat-content">
+          <div class="stat-value">{{ dashboardStats().quotedProjects }}</div>
+          <div class="stat-label">已完成报价单数</div>
+        </div>
+        <button class="view-detail-btn" (click)="handleQuickAction('newQuote')">查看详情</button>
+      </div>
+
+      <div class="stat-card">
+        <div class="stat-icon savings"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg></div>
+        <div class="stat-content">
+          <div class="stat-value" *ngIf="userRole() === 'teamLead'; else hiddenSavings">{{ formatAmount(dashboardStats().materialCostSaved) }}</div>
+          <ng-template #hiddenSavings><div class="stat-value">--</div></ng-template>
+          <div class="stat-label">素材成本节约金额</div>
+        </div>
+        <button class="view-detail-btn" (click)="handleQuickAction('recordPayment')">查看详情</button>
+      </div>
+    </div>
+
+    <!-- 待办任务区域 -->
+    <div class="todo-section">
+      <div class="section-header">
+        <h2>待办任务</h2>
+        <div class="task-count">{{ todoTasks().length }} 项任务</div>
+      </div>
+      <div class="task-list">
+        <div class="task-item" *ngFor="let task of todoTasks()" (click)="handleTaskClick(task)" [class.high-priority]="task.priority === 'high'" [class.medium-priority]="task.priority === 'medium'">
+          <div class="task-priority-indicator"></div>
+          <div class="task-content">
+            <div class="task-title">{{ task.title }}</div>
+            <div class="task-meta">
+              <span class="due-time">截止时间: {{ task.dueTime }}</span>
+            </div>
+          </div>
+          <div class="task-action">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

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

@@ -0,0 +1,321 @@
+/* 财务工作台样式 - iOS风格设计 */
+
+.finance-dashboard {
+  min-height: 100vh;
+  background-color: #f2f2f7;
+  padding: 20px;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+}
+
+/* 顶部标题区域 */
+.dashboard-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+}
+
+.dashboard-title {
+  font-size: 28px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin: 0;
+}
+
+.current-date {
+  font-size: 16px;
+  color: #6c6c70;
+}
+
+/* 快捷操作区域 */
+.quick-actions {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+}
+
+.action-btn {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  background-color: #ffffff;
+  border-radius: 16px;
+  border: none;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.action-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+}
+
+.action-icon {
+  font-size: 32px;
+  margin-bottom: 12px;
+}
+
+.action-label {
+  font-size: 16px;
+  font-weight: 500;
+  color: #1c1c1e;
+}
+
+/* 主内容区域 */
+.dashboard-content {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+/* 数据概览卡片 */
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 20px;
+}
+
+.stat-card {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  background-color: #ffffff;
+  border-radius: 16px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+}
+
+.stat-card:hover {
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+}
+
+.stat-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 56px;
+  height: 56px;
+  border-radius: 12px;
+  margin-right: 16px;
+  background-color: #f2f2f7;
+  color: #007aff;
+}
+
+.stat-icon.orders {
+  background-color: #e0f7fa;
+  color: #00bcd4;
+}
+
+.stat-icon.payment {
+  background-color: #e8f5e9;
+  color: #4caf50;
+}
+
+.stat-icon.quotes {
+  background-color: #fff3e0;
+  color: #ff9800;
+}
+
+.stat-icon.savings {
+  background-color: #ede7f6;
+  color: #9c27b0;
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: 700;
+  color: #1c1c1e;
+  margin-bottom: 4px;
+}
+
+.stat-label {
+  font-size: 14px;
+  color: #6c6c70;
+}
+
+.view-detail-btn {
+  padding: 8px 16px;
+  background-color: #f2f2f7;
+  border: none;
+  border-radius: 12px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #007aff;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.view-detail-btn:hover {
+  background-color: #e5e5ea;
+}
+
+/* 待办任务区域 */
+.todo-section {
+  background-color: #ffffff;
+  border-radius: 16px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  padding: 20px;
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.section-header h2 {
+  font-size: 20px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin: 0;
+}
+
+.task-count {
+  font-size: 14px;
+  color: #6c6c70;
+}
+
+.task-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.task-item {
+  display: flex;
+  align-items: center;
+  padding: 16px;
+  background-color: #f9f9f9;
+  border-radius: 12px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  position: relative;
+  overflow: hidden;
+}
+
+.task-item:hover {
+  background-color: #f2f2f7;
+  transform: translateX(4px);
+}
+
+.task-priority-indicator {
+  width: 4px;
+  height: 24px;
+  border-radius: 2px;
+  background-color: #8e8e93;
+  margin-right: 12px;
+}
+
+.task-item.high-priority .task-priority-indicator {
+  background-color: #ff3b30;
+}
+
+.task-item.medium-priority .task-priority-indicator {
+  background-color: #ff9500;
+}
+
+.task-content {
+  flex: 1;
+}
+
+.task-title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #1c1c1e;
+  margin-bottom: 4px;
+}
+
+.task-meta {
+  display: flex;
+  gap: 16px;
+}
+
+.due-time {
+  font-size: 14px;
+  color: #6c6c70;
+}
+
+.task-action {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  color: #6c6c70;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .finance-dashboard {
+    padding: 16px;
+  }
+  
+  .dashboard-title {
+    font-size: 24px;
+  }
+  
+  .quick-actions {
+    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+    gap: 12px;
+  }
+  
+  .stats-cards {
+    grid-template-columns: 1fr;
+    gap: 16px;
+  }
+  
+  .stat-card {
+    padding: 16px;
+  }
+  
+  .todo-section {
+    padding: 16px;
+  }
+}
+
+/* iOS风格动画效果 */
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.stat-card,
+.action-btn,
+.task-item {
+  animation: fadeIn 0.5s ease-out;
+}
+
+.stat-card:nth-child(2) {
+  animation-delay: 0.1s;
+}
+
+.stat-card:nth-child(3) {
+  animation-delay: 0.2s;
+}
+
+.stat-card:nth-child(4) {
+  animation-delay: 0.3s;
+}
+
+.action-btn:nth-child(2) {
+  animation-delay: 0.1s;
+}
+
+.action-btn:nth-child(3) {
+  animation-delay: 0.2s;
+}

+ 65 - 3
src/app/pages/finance/dashboard/dashboard.ts

@@ -1,11 +1,73 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
 
 @Component({
   selector: 'app-dashboard',
-  imports: [],
+  imports: [CommonModule, RouterModule],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 })
-export class Dashboard {
+export class Dashboard implements OnInit {
+  // 添加today属性用于模板中的日期显示
+  today = new Date();
+  
+  // 待办任务数据
+  todoTasks = signal([
+    { id: 1, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '10:30' },
+    { id: 2, title: '报价待审核', priority: 'medium', source: 'project-records', dueTime: '14:00' },
+    { id: 3, title: '素材成本待录入', priority: 'low', source: 'reconciliation', dueTime: '16:30' },
+    { id: 4, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '11:00' },
+    { id: 5, title: '销售监管提醒', priority: 'high', source: 'project-records', dueTime: '09:30' }
+  ]);
 
+  // 数据概览数据
+  dashboardStats = signal({
+    todayOrders: 12,
+    pendingPayment: 156800,
+    quotedProjects: 28,
+    materialCostSaved: 8900
+  });
+
+  // 用户角色(模拟数据,实际应该从认证服务获取)
+  userRole = signal('teamLead'); // teamLead 或 juniorMember
+
+  constructor() {}
+
+  ngOnInit(): void {
+    // 初始化数据加载逻辑
+  }
+
+  // 处理待办任务点击
+  handleTaskClick(task: any) {
+    // 根据任务来源跳转到对应页面
+    switch(task.source) {
+      case 'reconciliation':
+        window.location.href = '/finance/reconciliation';
+        break;
+      case 'project-records':
+        window.location.href = '/finance/project-records';
+        break;
+    }
+  }
+
+  // 处理快捷操作点击
+  handleQuickAction(action: string) {
+    switch(action) {
+      case 'newQuote':
+        window.location.href = '/finance/project-records';
+        break;
+      case 'recordPayment':
+        window.location.href = '/finance/reconciliation';
+        break;
+      case 'generateReport':
+        window.location.href = '/finance/reports';
+        break;
+    }
+  }
+
+  // 格式化金额显示
+  formatAmount(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount);
+  }
 }

+ 199 - 1
src/app/pages/finance/project-records/project-records.html

@@ -1 +1,199 @@
-<p>project-records works!</p>
+<div class="finance-project-records">
+  <!-- 顶部标题区 -->
+  <header class="page-header">
+    <div class="header-content">
+      <h1>项目财务档案</h1>
+      <p class="subtitle">管理所有项目的财务状态和报价信息</p>
+    </div>
+  </header>
+
+  <!-- 搜索和筛选区 -->
+  <div class="search-filter-section">
+    <div class="search-box">
+      <input type="text" placeholder="搜索项目名称、客户名称或项目编号" class="search-input">
+      <i class="search-icon">🔍</i>
+    </div>
+    <div class="filter-buttons">
+      <button class="filter-btn active">全部</button>
+      <button class="filter-btn">待报价</button>
+      <button class="filter-btn">待收款</button>
+      <button class="filter-btn">已结算</button>
+    </div>
+  </div>
+
+  <!-- 项目列表 -->
+  <div class="project-list">
+    <div class="list-header">
+      <div class="header-item">项目信息</div>
+      <div class="header-item">财务状态</div>
+      <div class="header-item">价格</div>
+      <div class="header-item">操作</div>
+    </div>
+    
+    <div class="project-items">
+      <div *ngFor="let project of projects()" class="project-item">
+        <div class="project-info">
+          <div class="project-id">{{ project.id }}</div>
+          <div class="project-main">
+            <div class="project-name">{{ project.name }}</div>
+            <div class="project-meta">
+              <span class="client-name">{{ project.clientName }}</span>
+              <span class="date">{{ formatDate(project.updatedAt) }}</span>
+            </div>
+            <div class="project-details">
+              <span>{{ getProjectType(project) }}</span>
+              <span>{{ getTechnicalType(project) }}</span>
+            </div>
+          </div>
+          <div *ngIf="getQuoteTypeLabel(project)" class="quote-type-tag {{ getQuoteTypeClass(project) }}">
+            {{ getQuoteTypeLabel(project) }}
+          </div>
+        </div>
+        
+        <div class="project-status">
+          <span class="status-badge {{ getStatusClass(project.financialStatus) }}">
+            {{ getStatusText(project.financialStatus) }}
+          </span>
+        </div>
+        
+        <div class="project-price">
+          <div class="total-price">{{ formatAmount(project.price) }}</div>
+          <div class="cost-info">
+            <span>成本: {{ formatAmount(project.cost) }}</span>
+            <span>利润率: {{ project.profitRate.toFixed(1) }}%</span>
+          </div>
+        </div>
+        
+        <div class="project-actions">
+          <button *ngIf="project.financialStatus === 'pendingQuote'" (click)="openQuoteModal(project)" class="action-btn quote-btn">
+            生成报价
+          </button>
+          
+          <button *ngIf="project.financialStatus === 'quoted'" (click)="updateFinancialStatus(project, 'pendingPayment')" class="action-btn status-btn">
+            标记为待收款
+          </button>
+          
+          <button *ngIf="project.financialStatus === 'pendingPayment'" (click)="updateFinancialStatus(project, 'partialPayment')" class="action-btn status-btn">
+            部分收款
+          </button>
+          
+          <button *ngIf="project.financialStatus === 'partialPayment'" (click)="updateFinancialStatus(project, 'fullPayment')" class="action-btn status-btn">
+            全款到账
+          </button>
+          
+          <button *ngIf="project.financialStatus === 'fullPayment'" (click)="updateFinancialStatus(project, 'pendingSettlement')" class="action-btn status-btn">
+            待结算
+          </button>
+          
+          <button *ngIf="project.financialStatus === 'pendingSettlement'" (click)="updateFinancialStatus(project, 'settled')" class="action-btn status-btn">
+            完成结算
+          </button>
+          
+          <button (click)="openCommunicationLogModal(project)" class="action-btn log-btn">
+            沟通日志
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 报价弹窗 -->
+  <div *ngIf="showQuoteModal()" class="modal-overlay">
+    <div class="modal-content quote-modal">
+      <div class="modal-header">
+        <h2>生成项目报价</h2>
+        <button (click)="showQuoteModal.set(false)" class="close-btn">×</button>
+      </div>
+      
+      <div class="modal-body">
+        <form [formGroup]="quoteForm" (ngSubmit)="calculateQuote()" class="quote-form">
+          <div class="form-row">
+            <div class="form-group">
+              <label>项目编号</label>
+              <input formControlName="projectId" type="text" readonly>
+            </div>
+            
+            <div class="form-group">
+              <label>方案类型</label>
+              <input formControlName="方案类型" type="text" readonly>
+            </div>
+          </div>
+          
+          <div class="form-row">
+            <div class="form-group">
+              <label>技术类型</label>
+              <select formControlName="技术类型">
+                <option value="建模">建模</option>
+                <option value="渲染">渲染</option>
+                <option value="后期">后期</option>
+                <option value="建模/渲染">建模/渲染</option>
+                <option value="建模/渲染/后期">建模/渲染/后期</option>
+              </select>
+            </div>
+            
+            <div class="form-group">
+              <label>面积 (m²)</label>
+              <input formControlName="area" type="number" min="1">
+            </div>
+          </div>
+          
+          <div class="form-row">
+            <div class="form-group">
+              <label>利润率 (%)</label>
+              <input formControlName="profitRate" type="range" min="0" max="100">
+              <div class="profit-rate-display">{{ quoteForm.value.profitRate }}%</div>
+            </div>
+          </div>
+          
+          <div class="form-actions">
+            <button type="button" (click)="showQuoteModal.set(false)" class="btn-cancel">取消</button>
+            <button type="submit" class="btn-primary">计算并生成报价</button>
+          </div>
+        </form>
+      </div>
+    </div>
+  </div>
+
+  <!-- 沟通日志弹窗 -->
+  <div *ngIf="showCommunicationLogModal()" class="modal-overlay">
+    <div class="modal-content communication-log-modal">
+      <div class="modal-header">
+        <h2>销售沟通日志</h2>
+        <button (click)="showCommunicationLogModal.set(false)" class="close-btn">×</button>
+      </div>
+      
+      <div class="modal-body">
+        <div class="log-header" *ngIf="selectedProject()">
+          <div class="project-name">{{ selectedProject()!.name }}</div>
+          <div class="client-info">客户: {{ selectedProject()!.clientName }}</div>
+          <div class="sales-info">销售: {{ selectedProject()!.salesPerson }}</div>
+        </div>
+        
+        <div class="log-list" *ngIf="selectedProject()">
+          <div *ngIf="selectedProject()!.communicationLog.length === 0" class="no-logs">
+            暂无沟通记录
+          </div>
+          
+          <div *ngFor="let log of selectedProject()!.communicationLog" class="log-item">
+            <div class="log-content" [class.has-red-line]="log.hasRedLineContent">
+              {{ log.content }}
+            </div>
+            <div class="log-time">
+              {{ formatDate(log.timestamp) }}
+            </div>
+          </div>
+        </div>
+        
+        <div class="add-log-section">
+          <textarea placeholder="添加沟通记录..." rows="3" class="log-input"></textarea>
+          <div class="log-actions">
+            <label class="checkbox-label">
+              <input type="checkbox"> 包含红线内容
+            </label>
+            <button class="btn-add-log">添加记录</button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 579 - 0
src/app/pages/finance/project-records/project-records.scss

@@ -0,0 +1,579 @@
+/* 项目财务档案页面样式 - iOS风格 */
+
+.finance-project-records {
+  padding: 16px;
+  max-width: 1200px;
+  margin: 0 auto;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  color: #333;
+}
+
+/* 顶部标题区 */
+.page-header {
+  margin-bottom: 24px;
+  .header-content {
+    h1 {
+      font-size: 28px;
+      font-weight: 600;
+      margin-bottom: 8px;
+      color: #111;
+    }
+    .subtitle {
+      font-size: 14px;
+      color: #888;
+    }
+  }
+}
+
+/* 搜索和筛选区 */
+.search-filter-section {
+  margin-bottom: 24px;
+  .search-box {
+    position: relative;
+    margin-bottom: 16px;
+    .search-input {
+      width: 100%;
+      padding: 12px 40px 12px 16px;
+      border: 1px solid #e0e0e0;
+      border-radius: 12px;
+      font-size: 14px;
+      background-color: #f9f9f9;
+      transition: all 0.2s ease;
+      &:focus {
+        outline: none;
+        border-color: #007aff;
+        background-color: #fff;
+        box-shadow: 0 2px 8px rgba(0, 122, 255, 0.15);
+      }
+    }
+    .search-icon {
+      position: absolute;
+      right: 16px;
+      top: 50%;
+      transform: translateY(-50%);
+      color: #888;
+      font-size: 16px;
+    }
+  }
+  .filter-buttons {
+    display: flex;
+    gap: 8px;
+    overflow-x: auto;
+    padding-bottom: 8px;
+    .filter-btn {
+      padding: 8px 16px;
+      border: 1px solid #e0e0e0;
+      border-radius: 20px;
+      background-color: #fff;
+      color: #888;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      white-space: nowrap;
+      &:hover {
+        background-color: #f0f0f0;
+      }
+      &.active {
+        background-color: #007aff;
+        color: #fff;
+        border-color: #007aff;
+      }
+    }
+  }
+}
+
+/* 项目列表 */
+.project-list {
+  .list-header {
+    display: grid;
+    grid-template-columns: 1fr auto auto auto;
+    gap: 16px;
+    padding: 12px 16px;
+    background-color: #f5f5f5;
+    border-radius: 12px;
+    margin-bottom: 12px;
+    .header-item {
+      font-size: 14px;
+      font-weight: 600;
+      color: #666;
+      &:nth-child(1) { /* 项目信息 */
+        justify-self: start;
+      }
+      &:nth-child(2) { /* 财务状态 */
+        text-align: center;
+      }
+      &:nth-child(3) { /* 价格 */
+        text-align: right;
+      }
+      &:nth-child(4) { /* 操作 */
+        text-align: right;
+      }
+    }
+  }
+  
+  .project-items {
+    .project-item {
+      display: grid;
+      grid-template-columns: 1fr auto auto auto;
+      gap: 16px;
+      align-items: center;
+      padding: 16px;
+      background-color: #fff;
+      border: 1px solid #e0e0e0;
+      border-radius: 12px;
+      margin-bottom: 12px;
+      transition: all 0.2s ease;
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+        border-color: #d0d0d0;
+      }
+      
+      .project-info {
+        display: flex;
+        align-items: flex-start;
+        gap: 12px;
+        .project-id {
+          font-size: 12px;
+          color: #888;
+          font-weight: 500;
+          min-width: 80px;
+        }
+        .project-main {
+          flex: 1;
+          .project-name {
+            font-size: 16px;
+            font-weight: 600;
+            margin-bottom: 4px;
+            color: #111;
+          }
+          .project-meta {
+            display: flex;
+            gap: 16px;
+            font-size: 13px;
+            color: #666;
+            margin-bottom: 4px;
+          }
+          .project-details {
+            display: flex;
+            gap: 12px;
+            font-size: 12px;
+            color: #888;
+          }
+        }
+        .quote-type-tag {
+          padding: 4px 8px;
+          border-radius: 4px;
+          font-size: 12px;
+          font-weight: 500;
+          &.profit-tag {
+            background-color: #e6f4ea;
+            color: #34c759;
+          }
+          &.tail-tag {
+            background-color: #fff2e8;
+            color: #ff9500;
+          }
+        }
+      }
+      
+      .project-status {
+        .status-badge {
+          padding: 6px 12px;
+          border-radius: 16px;
+          font-size: 13px;
+          font-weight: 500;
+          &.status-pending {
+            background-color: #f5f5f5;
+            color: #666;
+          }
+          &.status-quoted {
+            background-color: #e6f4ea;
+            color: #34c759;
+          }
+          &.status-pending-payment {
+            background-color: #fff2e8;
+            color: #ff9500;
+          }
+          &.status-partial-payment {
+            background-color: #fff2e8;
+            color: #ff9500;
+          }
+          &.status-full-payment {
+            background-color: #e6f4ea;
+            color: #34c759;
+          }
+          &.status-pending-settlement {
+            background-color: #f0f7ff;
+            color: #007aff;
+          }
+          &.status-settled {
+            background-color: #f0f0f0;
+            color: #666;
+          }
+        }
+      }
+      
+      .project-price {
+        text-align: right;
+        .total-price {
+          font-size: 16px;
+          font-weight: 600;
+          color: #111;
+          margin-bottom: 4px;
+        }
+        .cost-info {
+          font-size: 12px;
+          color: #888;
+          display: flex;
+          flex-direction: column;
+          gap: 2px;
+        }
+      }
+      
+      .project-actions {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        .action-btn {
+          padding: 8px 12px;
+          border: none;
+          border-radius: 8px;
+          font-size: 13px;
+          font-weight: 500;
+          cursor: pointer;
+          transition: all 0.2s ease;
+          &.quote-btn {
+            background-color: #007aff;
+            color: #fff;
+            &:hover {
+              background-color: #0062cc;
+            }
+          }
+          &.status-btn {
+            background-color: #34c759;
+            color: #fff;
+            &:hover {
+              background-color: #28a745;
+            }
+          }
+          &.log-btn {
+            background-color: #f0f0f0;
+            color: #666;
+            &:hover {
+              background-color: #e0e0e0;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 弹窗样式 */
+.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-color: #fff;
+  border-radius: 16px;
+  width: 90%;
+  max-width: 600px;
+  max-height: 80vh;
+  overflow-y: auto;
+  animation: modalSlideIn 0.3s ease;
+  .modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px;
+    border-bottom: 1px solid #e0e0e0;
+    h2 {
+      font-size: 20px;
+      font-weight: 600;
+      color: #111;
+    }
+    .close-btn {
+      background: none;
+      border: none;
+      font-size: 24px;
+      color: #888;
+      cursor: pointer;
+      padding: 0;
+      width: 32px;
+      height: 32px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 50%;
+      transition: background-color 0.2s ease;
+      &:hover {
+        background-color: #f0f0f0;
+      }
+    }
+  }
+  
+  .modal-body {
+    padding: 20px;
+  }
+}
+
+/* 报价弹窗 */
+.quote-modal {
+  .quote-form {
+    .form-row {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 16px;
+      margin-bottom: 16px;
+    }
+    .form-group {
+      .label {
+        display: block;
+        font-size: 14px;
+        font-weight: 500;
+        color: #333;
+        margin-bottom: 8px;
+      }
+      input,
+      select {
+        width: 100%;
+        padding: 10px 12px;
+        border: 1px solid #e0e0e0;
+        border-radius: 8px;
+        font-size: 14px;
+        transition: border-color 0.2s ease;
+        &:focus {
+          outline: none;
+          border-color: #007aff;
+        }
+        &[readonly] {
+          background-color: #f5f5f5;
+          cursor: not-allowed;
+        }
+      }
+      .profit-rate-display {
+        text-align: center;
+        font-size: 16px;
+        font-weight: 600;
+        color: #007aff;
+        margin-top: 8px;
+      }
+    }
+    .form-actions {
+      display: flex;
+      justify-content: flex-end;
+      gap: 12px;
+      margin-top: 24px;
+      .btn-cancel,
+      .btn-primary {
+        padding: 10px 20px;
+        border: none;
+        border-radius: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.2s ease;
+      }
+      .btn-cancel {
+        background-color: #f0f0f0;
+        color: #666;
+        &:hover {
+          background-color: #e0e0e0;
+        }
+      }
+      .btn-primary {
+        background-color: #007aff;
+        color: #fff;
+        &:hover {
+          background-color: #0062cc;
+        }
+      }
+    }
+  }
+}
+
+/* 沟通日志弹窗 */
+.communication-log-modal {
+  .log-header {
+    padding: 16px;
+    background-color: #f9f9f9;
+    border-radius: 8px;
+    margin-bottom: 16px;
+    .project-name {
+      font-size: 16px;
+      font-weight: 600;
+      color: #111;
+      margin-bottom: 4px;
+    }
+    .client-info,
+    .sales-info {
+      font-size: 13px;
+      color: #666;
+    }
+  }
+  
+  .log-list {
+    max-height: 200px;
+    overflow-y: auto;
+    margin-bottom: 16px;
+    .no-logs {
+      text-align: center;
+      color: #888;
+      font-size: 14px;
+      padding: 24px;
+    }
+    .log-item {
+      padding: 12px;
+      background-color: #f9f9f9;
+      border-radius: 8px;
+      margin-bottom: 8px;
+      .log-content {
+        font-size: 14px;
+        color: #333;
+        margin-bottom: 4px;
+        &.has-red-line {
+          font-weight: 500;
+          color: #ff3b30;
+        }
+      }
+      .log-time {
+        font-size: 12px;
+        color: #888;
+      }
+    }
+  }
+  
+  .add-log-section {
+    .log-input {
+      width: 100%;
+      padding: 12px;
+      border: 1px solid #e0e0e0;
+      border-radius: 8px;
+      font-size: 14px;
+      resize: vertical;
+      transition: border-color 0.2s ease;
+      &:focus {
+        outline: none;
+        border-color: #007aff;
+      }
+    }
+    .log-actions {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-top: 12px;
+      .checkbox-label {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        font-size: 14px;
+        color: #666;
+        cursor: pointer;
+        input {
+          width: 16px;
+          height: 16px;
+          cursor: pointer;
+        }
+      }
+      .btn-add-log {
+        padding: 8px 16px;
+        background-color: #007aff;
+        color: #fff;
+        border: none;
+        border-radius: 8px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: background-color 0.2s ease;
+        &:hover {
+          background-color: #0062cc;
+        }
+      }
+    }
+  }
+}
+
+/* 动画 */
+@keyframes modalSlideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .finance-project-records {
+    padding: 12px;
+  }
+  
+  .project-list {
+    .list-header {
+      grid-template-columns: 1fr;
+      .header-item {
+        display: none;
+      }
+    }
+    .project-item {
+      grid-template-columns: 1fr;
+      gap: 12px;
+      .project-info {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 8px;
+      }
+      .project-status,
+      .project-price {
+        text-align: left;
+      }
+      .project-actions {
+        flex-direction: row;
+        flex-wrap: wrap;
+      }
+    }
+  }
+  
+  .modal-content {
+    width: 95%;
+    margin: 20px;
+    .quote-form {
+      .form-row {
+        grid-template-columns: 1fr;
+      }
+    }
+  }
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+  width: 8px;
+  height: 8px;
+}
+
+::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}

+ 424 - 4
src/app/pages/finance/project-records/project-records.ts

@@ -1,11 +1,431 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { signal } from '@angular/core';
+
+// 项目财务状态类型定义
+export type FinancialStatus = 
+  | 'pendingQuote'     // 待报价
+  | 'quoted'           // 已报价
+  | 'pendingPayment'   // 待收款
+  | 'partialPayment'   // 部分收款
+  | 'fullPayment'      // 全款到账
+  | 'pendingSettlement' // 待结算
+  | 'settled';         // 已结算
+
+// 沟通日志类型定义
+export interface CommunicationLog {
+  id: string;
+  content: string;
+  timestamp: Date;
+  hasRedLineContent: boolean;
+}
+
+// 项目类型定义
+export interface Project {
+  id: string;
+  name: string;
+  clientName: string;
+  salesPerson: string;
+  方案类型?: string;
+  技术类型?: string;
+  area?: number;
+  price: number;
+  cost: number;
+  profitRate: number;
+  financialStatus: FinancialStatus;
+  updatedAt: Date;
+  communicationLog: CommunicationLog[];
+  quoteType?: 'fixedPrice' | 'hourlyRate' | 'materialCost';
+}
+
+// 工时标准配置接口
+export interface HourlyRateConfig {
+  [key: string]: {
+    [key: string]: number;
+  };
+}
 
 @Component({
   selector: 'app-project-records',
-  imports: [],
+  imports: [CommonModule, ReactiveFormsModule],
   templateUrl: './project-records.html',
-  styleUrl: './project-records.scss'
+  styleUrls: ['./project-records.scss']
 })
-export class ProjectRecords {
+export class ProjectRecords implements OnInit {
+  // 使用signal管理项目列表数据
+  projects = signal<Project[]>([]);
+  
+  // 工时标准配置
+  hourlyRateConfig = signal<HourlyRateConfig>({
+    '建模': {
+      '基础': 100,
+      '标准': 150,
+      '高级': 200
+    },
+    '渲染': {
+      '基础': 120,
+      '标准': 180,
+      '高级': 250
+    },
+    '后期': {
+      '基础': 80,
+      '标准': 120,
+      '高级': 180
+    }
+  });
+
+  // 报价表单
+  quoteForm: FormGroup;
+  
+  // 模态框状态
+  showQuoteModal = signal(false);
+  showCommunicationLogModal = signal(false);
+  selectedProject = signal<Project | null>(null);
+
+  constructor(private fb: FormBuilder) {
+    // 初始化报价表单
+    this.quoteForm = this.fb.group({
+      projectId: [''],
+      '方案类型': [''],
+      '技术类型': [''],
+      area: [0],
+      profitRate: [30]
+    });
+  }
+
+  ngOnInit(): void {
+    // 初始化项目数据
+    this.loadProjectData();
+  }
+
+  // 加载项目数据
+  loadProjectData(): void {
+    // 模拟从API获取项目数据
+    const mockProjects: Project[] = [
+      {
+        id: 'PRJ-2023-001',
+        name: '现代风格住宅室内设计',
+        clientName: '星光地产',
+        salesPerson: '张三',
+        方案类型: '室内设计',
+        技术类型: '建模/渲染/后期',
+        area: 120,
+        price: 85000,
+        cost: 55000,
+        profitRate: 35.3,
+        financialStatus: 'pendingQuote',
+        updatedAt: new Date('2023-09-15'),
+        communicationLog: []
+      },
+      {
+        id: 'PRJ-2023-002',
+        name: '商业办公空间设计',
+        clientName: '未来科技有限公司',
+        salesPerson: '李四',
+        方案类型: '办公设计',
+        技术类型: '建模/渲染',
+        area: 350,
+        price: 120000,
+        cost: 80000,
+        profitRate: 33.3,
+        financialStatus: 'quoted',
+        updatedAt: new Date('2023-09-10'),
+        communicationLog: [
+          {
+            id: 'LOG-001',
+            content: '客户对报价表示基本接受,等待内部审批',
+            timestamp: new Date('2023-09-12'),
+            hasRedLineContent: false
+          }
+        ]
+      },
+      {
+        id: 'PRJ-2023-003',
+        name: '餐饮空间改造设计',
+        clientName: '悦享餐饮集团',
+        salesPerson: '王五',
+        方案类型: '餐饮设计',
+        技术类型: '建模/渲染/后期',
+        area: 180,
+        price: 95000,
+        cost: 65000,
+        profitRate: 31.6,
+        financialStatus: 'pendingPayment',
+        updatedAt: new Date('2023-09-05'),
+        communicationLog: [
+          {
+            id: 'LOG-002',
+            content: '已确认设计方案,准备支付首付款',
+            timestamp: new Date('2023-09-08'),
+            hasRedLineContent: false
+          }
+        ]
+      },
+      {
+        id: 'PRJ-2023-004',
+        name: '酒店大堂设计',
+        clientName: '环球酒店集团',
+        salesPerson: '赵六',
+        方案类型: '酒店设计',
+        技术类型: '建模/渲染/后期',
+        area: 500,
+        price: 250000,
+        cost: 160000,
+        profitRate: 36.0,
+        financialStatus: 'partialPayment',
+        updatedAt: new Date('2023-08-30'),
+        communicationLog: [
+          {
+            id: 'LOG-003',
+            content: '已收到50%预付款,开始正式设计工作',
+            timestamp: new Date('2023-09-02'),
+            hasRedLineContent: false
+          }
+        ]
+      },
+      {
+        id: 'PRJ-2023-005',
+        name: '品牌展厅设计',
+        clientName: '时尚家居品牌',
+        salesPerson: '张三',
+        方案类型: '展厅设计',
+        技术类型: '建模/渲染',
+        area: 200,
+        price: 110000,
+        cost: 70000,
+        profitRate: 36.4,
+        financialStatus: 'fullPayment',
+        updatedAt: new Date('2023-08-25'),
+        communicationLog: [
+          {
+            id: 'LOG-004',
+            content: '全款已到账,项目进入收尾阶段',
+            timestamp: new Date('2023-08-28'),
+            hasRedLineContent: false
+          }
+        ]
+      },
+      {
+        id: 'PRJ-2023-006',
+        name: '别墅花园景观设计',
+        clientName: '私人业主',
+        salesPerson: '李四',
+        方案类型: '景观设计',
+        技术类型: '渲染/后期',
+        area: 300,
+        price: 75000,
+        cost: 45000,
+        profitRate: 40.0,
+        financialStatus: 'pendingSettlement',
+        updatedAt: new Date('2023-08-20'),
+        communicationLog: [
+          {
+            id: 'LOG-005',
+            content: '项目已完成,等待财务结算',
+            timestamp: new Date('2023-08-22'),
+            hasRedLineContent: false
+          }
+        ]
+      },
+      {
+        id: 'PRJ-2023-007',
+        name: '零售店空间设计',
+        clientName: '潮流服饰品牌',
+        salesPerson: '王五',
+        方案类型: '零售设计',
+        技术类型: '建模/渲染/后期',
+        area: 150,
+        price: 80000,
+        cost: 50000,
+        profitRate: 37.5,
+        financialStatus: 'settled',
+        updatedAt: new Date('2023-08-15'),
+        communicationLog: [
+          {
+            id: 'LOG-006',
+            content: '项目已完成结算',
+            timestamp: new Date('2023-08-18'),
+            hasRedLineContent: false
+          }
+        ]
+      }
+    ];
+
+    this.projects.set(mockProjects);
+  }
+
+  // 打开报价弹窗
+  openQuoteModal(project: Project): void {
+    this.selectedProject.set(project);
+    this.quoteForm.patchValue({
+      projectId: project.id,
+      '方案类型': project.方案类型 || '',
+      '技术类型': project.技术类型 || '',
+      area: project.area || 0,
+      profitRate: 30
+    });
+    this.showQuoteModal.set(true);
+  }
+
+  // 计算报价
+  calculateQuote(): void {
+    if (!this.selectedProject()) return;
+
+    const formValue = this.quoteForm.value;
+    const area = formValue.area || 0;
+    const profitRate = formValue.profitRate || 30;
+    
+    // 简单的报价计算逻辑,实际项目中可能更复杂
+    let cost = area * 300; // 假设每平米成本300元
+    let price = cost * (1 + profitRate / 100);
+    
+    // 根据技术类型调整价格
+    if (formValue.技术类型 === '建模/渲染/后期') {
+      price *= 1.3;
+    } else if (formValue.技术类型 === '建模/渲染') {
+      price *= 1.1;
+    }
+
+    // 更新项目数据
+    const updatedProjects = this.projects().map(project => {
+      if (project.id === this.selectedProject()?.id) {
+        return {
+          ...project,
+          技术类型: formValue.技术类型,
+          area: area,
+          cost: cost,
+          price: price,
+          profitRate: profitRate,
+          financialStatus: 'quoted' as FinancialStatus,
+          updatedAt: new Date()
+        };
+      }
+      return project;
+    });
+
+    this.projects.set(updatedProjects);
+    this.showQuoteModal.set(false);
+    
+    // 重置选择的项目
+    this.selectedProject.set(null);
+  }
+
+  // 更新财务状态
+  updateFinancialStatus(project: Project, newStatus: FinancialStatus): void {
+    const updatedProjects = this.projects().map(p => {
+      if (p.id === project.id) {
+        return {
+          ...p,
+          financialStatus: newStatus,
+          updatedAt: new Date()
+        };
+      }
+      return p;
+    });
+
+    this.projects.set(updatedProjects);
+  }
+
+  // 打开沟通日志弹窗
+  openCommunicationLogModal(project: Project): void {
+    this.selectedProject.set(project);
+    this.showCommunicationLogModal.set(true);
+  }
+
+  // 添加沟通记录(简化版,实际项目中应该有更完整的实现)
+  addCommunicationLog(content: string, hasRedLineContent: boolean): void {
+    if (!this.selectedProject()) return;
+
+    const newLog: CommunicationLog = {
+      id: `LOG-${Date.now()}`,
+      content: content,
+      timestamp: new Date(),
+      hasRedLineContent: hasRedLineContent
+    };
+
+    const updatedProjects = this.projects().map(project => {
+      if (project.id === this.selectedProject()?.id) {
+        return {
+          ...project,
+          communicationLog: [...project.communicationLog, newLog],
+          updatedAt: new Date()
+        };
+      }
+      return project;
+    });
+
+    this.projects.set(updatedProjects);
+  }
+
+  // 获取财务状态文本
+  getStatusText(status: FinancialStatus): string {
+    const statusMap: Record<FinancialStatus, string> = {
+      'pendingQuote': '待报价',
+      'quoted': '已报价',
+      'pendingPayment': '待收款',
+      'partialPayment': '部分收款',
+      'fullPayment': '全款到账',
+      'pendingSettlement': '待结算',
+      'settled': '已结算'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取财务状态样式类
+  getStatusClass(status: FinancialStatus): string {
+    const statusClassMap: Record<FinancialStatus, string> = {
+      'pendingQuote': 'status-pending',
+      'quoted': 'status-quoted',
+      'pendingPayment': 'status-warning',
+      'partialPayment': 'status-info',
+      'fullPayment': 'status-success',
+      'pendingSettlement': 'status-warning',
+      'settled': 'status-completed'
+    };
+    return statusClassMap[status] || '';
+  }
+
+  // 获取报价类型标签
+  getQuoteTypeLabel(project: Project): string {
+    if (!project.quoteType) return '';
+    const labelMap: Record<string, string> = {
+      'fixedPrice': '固定价格',
+      'hourlyRate': '按小时计费',
+      'materialCost': '材料成本+人工费'
+    };
+    return labelMap[project.quoteType] || '';
+  }
+
+  // 获取报价类型样式类
+  getQuoteTypeClass(project: Project): string {
+    if (!project.quoteType) return '';
+    return `quote-type-${project.quoteType}`;
+  }
+
+  // 格式化日期
+  formatDate(date: Date): string {
+    if (!date) return '';
+    return new Date(date).toLocaleDateString('zh-CN');
+  }
+
+  // 格式化金额
+  formatAmount(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY',
+      minimumFractionDigits: 0,
+      maximumFractionDigits: 0
+    }).format(amount);
+  }
+
+  // 获取项目类型
+  getProjectType(project: Project): string {
+    return project.方案类型 || '未定义';
+  }
 
+  // 获取技术类型
+  getTechnicalType(project: Project): string {
+    return project.技术类型 || '未定义';
+  }
 }

+ 263 - 1
src/app/pages/finance/reconciliation/reconciliation.html

@@ -1 +1,263 @@
-<p>reconciliation works!</p>
+<div class="reconciliation-container">
+  <!-- 页面标题和操作按钮 -->
+  <div class="page-header">
+    <h2>对账管理</h2>
+    <div class="header-actions">
+      <button class="btn btn-primary" (click)="startReconciliation()">
+        开始新对账
+      </button>
+    </div>
+  </div>
+
+  <!-- 过滤和搜索区域 -->
+  <div class="filter-section">
+    <div class="filter-group">
+      <label>对账状态:</label>
+      <select [ngModel]="statusFilter()" (ngModelChange)="applyStatusFilter($event)" class="form-control">
+        <option value="all">全部</option>
+        <option value="pending">待对账</option>
+        <option value="partial">部分对账</option>
+        <option value="completed">已完成</option>
+        <option value="discrepancy">有差异</option>
+      </select>
+    </div>
+    
+    <div class="filter-group">
+      <label>对账期间:</label>
+      <div class="date-range">
+        <input type="date" [ngModel]="dateRangeFilter().start | date:'yyyy-MM-dd'" (ngModelChange)="updateDateRangeStart($event)" class="form-control date-input">
+        <span>至</span>
+        <input type="date" [ngModel]="dateRangeFilter().end | date:'yyyy-MM-dd'" (ngModelChange)="updateDateRangeEnd($event)" class="form-control date-input">
+      </div>
+    </div>
+  </div>
+
+  <!-- 对账记录表格 -->
+  <div class="section-card">
+    <div class="section-header">
+      <h3>对账记录</h3>
+    </div>
+    
+    <div class="table-responsive">
+      <table class="table table-hover">
+        <thead>
+          <tr>
+            <th>对账ID</th>
+            <th>对账日期</th>
+            <th>对账期间</th>
+            <th>状态</th>
+            <th>总金额</th>
+            <th>已对账金额</th>
+            <th>差异金额</th>
+            <th>对账人</th>
+            <th>操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr *ngFor="let record of reconciliationRecords()" (click)="viewReconciliationDetail(record)">
+            <td>{{ record.id }}</td>
+            <td>{{ formatDate(record.date) }}</td>
+            <td>{{ formatDate(record.periodStart) }} - {{ formatDate(record.periodEnd) }}</td>
+            <td><span class="status-badge {{ getStatusClass(record.status) }}">{{ getStatusText(record.status) }}</span></td>
+            <td>{{ formatAmount(record.totalAmount) }}</td>
+            <td>{{ formatAmount(record.reconciledAmount) }}</td>
+            <td>{{ formatAmount(record.discrepancyAmount) }}</td>
+            <td>{{ record.reconciledBy || '-' }}</td>
+            <td>
+              <button class="btn btn-sm btn-view" (click)="$event.stopPropagation(); viewReconciliationDetail(record)">
+                查看详情
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+
+  <!-- 未对账交易表格 -->
+  <div class="section-card">
+    <div class="section-header">
+      <h3>未对账交易 ({{ unreconciledTransactions().length }} 笔)</h3>
+    </div>
+    
+    <div class="table-responsive">
+      <table class="table table-hover">
+        <thead>
+          <tr>
+            <th>交易ID</th>
+            <th>交易日期</th>
+            <th>交易类型</th>
+            <th>金额</th>
+            <th>项目名称</th>
+            <th>客户名称</th>
+            <th>描述</th>
+            <th>操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr *ngFor="let transaction of unreconciledTransactions()">
+            <td>{{ transaction.id }}</td>
+            <td>{{ formatDate(transaction.date) }}</td>
+            <td><span class="transaction-type {{ getTransactionTypeClass(transaction.type) }}">{{ getTransactionTypeText(transaction.type) }}</span></td>
+            <td>{{ formatAmount(transaction.amount) }}</td>
+            <td>{{ transaction.projectName }}</td>
+            <td>{{ transaction.clientName }}</td>
+            <td>{{ transaction.description }}</td>
+            <td>
+              <button class="btn btn-sm btn-primary" (click)="markTransactionAsReconciled(transaction)">
+                标记为已对账
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+
+  <!-- 对账详情模态框 -->
+  <div class="modal-overlay" *ngIf="showDetailModal()" (click)="showDetailModal.set(false)">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>对账详情 - {{ selectedRecord()?.id }}</h3>
+        <button class="modal-close" (click)="showDetailModal.set(false)">&times;</button>
+      </div>
+      
+      <div class="modal-body" *ngIf="selectedRecord()">
+        <div class="detail-grid">
+          <div class="detail-item">
+            <label>对账日期:</label>
+            <span>{{ formatDate(selectedRecord()!.date) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>对账期间:</label>
+            <span>{{ formatDate(selectedRecord()!.periodStart) }} - {{ formatDate(selectedRecord()!.periodEnd) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>状态:</label>
+            <span class="status-badge {{ getStatusClass(selectedRecord()!.status) }}">{{ getStatusText(selectedRecord()!.status) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>总金额:</label>
+            <span>{{ formatAmount(selectedRecord()!.totalAmount) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>已对账金额:</label>
+            <span>{{ formatAmount(selectedRecord()!.reconciledAmount) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>差异金额:</label>
+            <span>{{ formatAmount(selectedRecord()!.discrepancyAmount) }}</span>
+          </div>
+          <div class="detail-item">
+            <label>对账人:</label>
+            <span>{{ selectedRecord()!.reconciledBy || '-' }}</span>
+          </div>
+          <div class="detail-item full-width">
+            <label>备注:</label>
+            <span>{{ selectedRecord()!.notes || '-' }}</span>
+          </div>
+        </div>
+        
+        <div class="transactions-section">
+          <h4>相关交易记录</h4>
+          <div class="table-responsive">
+            <table class="table table-small">
+              <thead>
+                <tr>
+                  <th>交易ID</th>
+                  <th>日期</th>
+                  <th>类型</th>
+                  <th>金额</th>
+                  <th>项目</th>
+                  <th>描述</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr *ngFor="let transaction of selectedRecord()!.transactions">
+                  <td>{{ transaction.id }}</td>
+                  <td>{{ formatDate(transaction.date) }}</td>
+                  <td><span class="transaction-type {{ getTransactionTypeClass(transaction.type) }}">{{ getTransactionTypeText(transaction.type) }}</span></td>
+                  <td>{{ formatAmount(transaction.amount) }}</td>
+                  <td>{{ transaction.projectName }}</td>
+                  <td>{{ transaction.description }}</td>
+                </tr>
+                <tr *ngIf="selectedRecord()!.transactions.length === 0">
+                  <td colspan="6" class="no-data">暂无交易记录</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      
+      <div class="modal-footer">
+        <button class="btn btn-secondary" (click)="showDetailModal.set(false)">关闭</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 新建对账模态框 -->
+  <div class="modal-overlay" *ngIf="showReconcileModal()" (click)="showReconcileModal.set(false)">
+    <div class="modal-content reconcile-modal" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>开始新对账</h3>
+        <button class="modal-close" (click)="showReconcileModal.set(false)">&times;</button>
+      </div>
+      
+      <div class="modal-body">
+        <form [formGroup]="reconciliationForm">
+          <div class="form-group">
+            <label>对账开始日期:</label>
+            <input type="date" formControlName="periodStart" class="form-control" required>
+          </div>
+          
+          <div class="form-group">
+            <label>对账结束日期:</label>
+            <input type="date" formControlName="periodEnd" class="form-control" required>
+          </div>
+          
+          <div class="form-group">
+            <label>备注:</label>
+            <textarea formControlName="notes" class="form-control" rows="3"></textarea>
+          </div>
+        </form>
+        
+        <div class="reconciliation-preview">
+          <h4>待对账交易 ({{ getFilteredUnreconciledTransactions().length }} 笔)</h4>
+          <div class="table-responsive">
+            <table class="table table-small">
+              <thead>
+                <tr>
+                  <th>交易ID</th>
+                  <th>日期</th>
+                  <th>类型</th>
+                  <th>金额</th>
+                  <th>项目</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr *ngFor="let transaction of getFilteredUnreconciledTransactions()">
+                  <td>{{ transaction.id }}</td>
+                  <td>{{ formatDate(transaction.date) }}</td>
+                  <td><span class="transaction-type {{ getTransactionTypeClass(transaction.type) }}">{{ getTransactionTypeText(transaction.type) }}</span></td>
+                  <td>{{ formatAmount(transaction.amount) }}</td>
+                  <td>{{ transaction.projectName }}</td>
+                </tr>
+                <tr *ngIf="!hasFilteredUnreconciledTransactions()">
+                  <td colspan="5" class="no-data">暂无符合条件的交易</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      
+      <div class="modal-footer">
+        <button class="btn btn-secondary" (click)="showReconcileModal.set(false)">取消</button>
+        <button class="btn btn-primary" (click)="performReconciliation()" [disabled]="reconciliationForm.invalid">
+          确认对账
+        </button>
+      </div>
+    </div>
+  </div>
+</div>

+ 524 - 0
src/app/pages/finance/reconciliation/reconciliation.scss

@@ -0,0 +1,524 @@
+// 对账管理页面样式
+
+.reconciliation-container {
+  padding: 20px;
+  max-width: 1400px;
+  margin: 0 auto;
+
+  // 页面标题和操作按钮
+  .page-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+
+    h2 {
+      font-size: 24px;
+      font-weight: 600;
+      color: #2c3e50;
+      margin: 0;
+    }
+
+    .header-actions {
+      button {
+        padding: 8px 20px;
+        font-size: 14px;
+        border-radius: 4px;
+        transition: all 0.3s ease;
+        &:hover {
+          transform: translateY(-1px);
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+        }
+      }
+    }
+  }
+
+  // 过滤区域
+  .filter-section {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+    margin-bottom: 24px;
+    padding: 16px;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+
+    .filter-group {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+      min-width: 200px;
+
+      label {
+        font-size: 14px;
+        font-weight: 500;
+        color: #495057;
+      }
+
+      select,
+      .form-control {
+        padding: 8px 12px;
+        border: 1px solid #ced4da;
+        border-radius: 4px;
+        font-size: 14px;
+        transition: border-color 0.3s ease;
+
+        &:focus {
+          outline: none;
+          border-color: #007bff;
+          box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+        }
+      }
+
+      .date-range {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+
+        .date-input {
+          flex: 1;
+        }
+
+        span {
+          font-size: 14px;
+          color: #6c757d;
+        }
+      }
+    }
+  }
+
+  // 卡片样式
+  .section-card {
+    background-color: #ffffff;
+    border-radius: 8px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    margin-bottom: 24px;
+    overflow: hidden;
+
+    .section-header {
+      padding: 16px 20px;
+      border-bottom: 1px solid #e9ecef;
+
+      h3 {
+        font-size: 18px;
+        font-weight: 600;
+        color: #2c3e50;
+        margin: 0;
+      }
+    }
+  }
+
+  // 表格样式
+  .table-responsive {
+    overflow-x: auto;
+
+    .table {
+      width: 100%;
+      border-collapse: collapse;
+
+      thead {
+        background-color: #f8f9fa;
+        border-bottom: 2px solid #dee2e6;
+
+        th {
+          padding: 12px 16px;
+          text-align: left;
+          font-size: 14px;
+          font-weight: 600;
+          color: #495057;
+          white-space: nowrap;
+        }
+      }
+
+      tbody {
+        tr {
+          border-bottom: 1px solid #e9ecef;
+          transition: background-color 0.2s ease;
+
+          &:hover {
+            background-color: #f8f9fa;
+          }
+
+          td {
+            padding: 12px 16px;
+            font-size: 14px;
+            color: #212529;
+            vertical-align: middle;
+
+            .btn {
+              padding: 4px 12px;
+              font-size: 12px;
+              border-radius: 4px;
+              transition: all 0.2s ease;
+
+              &:hover {
+                transform: translateY(-1px);
+              }
+            }
+
+            .btn-view {
+              background-color: #6c757d;
+              color: white;
+              border: none;
+
+              &:hover {
+                background-color: #5a6268;
+              }
+            }
+          }
+        }
+
+        .no-data {
+          text-align: center;
+          padding: 40px 16px !important;
+          color: #6c757d;
+          font-style: italic;
+        }
+      }
+    }
+
+    // 小表格样式
+    .table-small {
+      thead th,
+      tbody td {
+        padding: 8px 12px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  // 状态标签样式
+  .status-badge {
+    padding: 4px 8px;
+    border-radius: 12px;
+    font-size: 12px;
+    font-weight: 500;
+    text-align: center;
+    min-width: 60px;
+
+    &.status-pending {
+      background-color: #fff3cd;
+      color: #856404;
+    }
+
+    &.status-partial {
+      background-color: #e3f2fd;
+      color: #0d47a1;
+    }
+
+    &.status-completed {
+      background-color: #d4edda;
+      color: #155724;
+    }
+
+    &.status-discrepancy {
+      background-color: #f8d7da;
+      color: #721c24;
+    }
+  }
+
+  // 交易类型标签样式
+  .transaction-type {
+    padding: 4px 8px;
+    border-radius: 12px;
+    font-size: 12px;
+    font-weight: 500;
+    text-align: center;
+
+    &.transaction-payment {
+      background-color: #d4edda;
+      color: #155724;
+    }
+
+    &.transaction-expense {
+      background-color: #f8d7da;
+      color: #721c24;
+    }
+
+    &.transaction-refund {
+      background-color: #fff3cd;
+      color: #856404;
+    }
+  }
+
+  // 按钮样式
+  .btn {
+    padding: 8px 16px;
+    border: none;
+    border-radius: 4px;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+
+    &:disabled {
+      opacity: 0.6;
+      cursor: not-allowed;
+      transform: none !important;
+    }
+
+    &.btn-primary {
+      background-color: #007bff;
+      color: white;
+
+      &:hover:not(:disabled) {
+        background-color: #0056b3;
+        transform: translateY(-1px);
+      }
+    }
+
+    &.btn-secondary {
+      background-color: #6c757d;
+      color: white;
+
+      &:hover:not(:disabled) {
+        background-color: #5a6268;
+        transform: translateY(-1px);
+      }
+    }
+
+    &.btn-sm {
+      padding: 4px 12px;
+      font-size: 12px;
+    }
+  }
+
+  // 模态框样式
+  .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-color: white;
+    border-radius: 8px;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+    width: 90%;
+    max-width: 800px;
+    max-height: 90vh;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .modal-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 16px 20px;
+      border-bottom: 1px solid #e9ecef;
+
+      h3 {
+        font-size: 18px;
+        font-weight: 600;
+        color: #2c3e50;
+        margin: 0;
+      }
+
+      .modal-close {
+        background: none;
+        border: none;
+        font-size: 24px;
+        cursor: pointer;
+        color: #6c757d;
+        padding: 0;
+        width: 32px;
+        height: 32px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-radius: 4px;
+
+        &:hover {
+          background-color: #f8f9fa;
+          color: #212529;
+        }
+      }
+    }
+
+    .modal-body {
+      padding: 20px;
+      overflow-y: auto;
+      flex: 1;
+
+      // 详情网格样式
+      .detail-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+        gap: 16px;
+        margin-bottom: 24px;
+
+        .detail-item {
+          display: flex;
+          flex-direction: column;
+          gap: 4px;
+
+          &.full-width {
+            grid-column: 1 / -1;
+          }
+
+          label {
+            font-size: 14px;
+            font-weight: 500;
+            color: #6c757d;
+          }
+
+          span {
+            font-size: 14px;
+            color: #212529;
+          }
+        }
+      }
+
+      // 交易记录区域
+      .transactions-section {
+        margin-top: 24px;
+
+        h4 {
+          font-size: 16px;
+          font-weight: 600;
+          color: #2c3e50;
+          margin-bottom: 16px;
+        }
+      }
+
+      // 表单样式
+      .form-group {
+        margin-bottom: 16px;
+
+        label {
+          display: block;
+          font-size: 14px;
+          font-weight: 500;
+          color: #495057;
+          margin-bottom: 6px;
+        }
+
+        .form-control {
+          width: 100%;
+          padding: 8px 12px;
+          border: 1px solid #ced4da;
+          border-radius: 4px;
+          font-size: 14px;
+          transition: border-color 0.3s ease;
+
+          &:focus {
+            outline: none;
+            border-color: #007bff;
+            box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+          }
+        }
+
+        textarea {
+          resize: vertical;
+          min-height: 80px;
+        }
+      }
+
+      // 对账预览
+      .reconciliation-preview {
+        margin-top: 24px;
+        padding-top: 24px;
+        border-top: 1px solid #e9ecef;
+
+        h4 {
+          font-size: 16px;
+          font-weight: 600;
+          color: #2c3e50;
+          margin-bottom: 16px;
+        }
+      }
+    }
+
+    .modal-footer {
+      display: flex;
+      justify-content: flex-end;
+      gap: 12px;
+      padding: 16px 20px;
+      border-top: 1px solid #e9ecef;
+      background-color: #f8f9fa;
+    }
+
+    // 对账模态框特定样式
+    &.reconcile-modal {
+      max-width: 900px;
+    }
+  }
+
+  // 响应式设计
+  @media (max-width: 768px) {
+    padding: 12px;
+
+    .page-header {
+      flex-direction: column;
+      gap: 16px;
+      align-items: stretch;
+
+      h2 {
+        font-size: 20px;
+      }
+
+      .header-actions {
+        text-align: center;
+      }
+    }
+
+    .filter-section {
+      flex-direction: column;
+
+      .filter-group {
+        min-width: auto;
+      }
+    }
+
+    .modal-content {
+      width: 95%;
+      margin: 10px;
+
+      .modal-header {
+        padding: 12px 16px;
+
+        h3 {
+          font-size: 16px;
+        }
+      }
+
+      .modal-body {
+        padding: 16px;
+
+        .detail-grid {
+          grid-template-columns: 1fr;
+        }
+      }
+
+      .modal-footer {
+        padding: 12px 16px;
+        flex-direction: column;
+      }
+    }
+
+    .table-responsive {
+      .table {
+        thead th,
+        tbody td {
+          padding: 8px 12px;
+          font-size: 12px;
+        }
+
+        .btn {
+          padding: 2px 8px;
+          font-size: 10px;
+        }
+      }
+    }
+  }
+}

+ 375 - 4
src/app/pages/finance/reconciliation/reconciliation.ts

@@ -1,11 +1,382 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormBuilder, FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
+import { signal } from '@angular/core';
+
+// 对账状态类型定义
+export type ReconciliationStatus = 
+  | 'pending'       // 待对账
+  | 'partial'       // 部分对账
+  | 'completed'     // 已完成
+  | 'discrepancy';  // 有差异
+
+// 交易类型定义
+export type TransactionType = 
+  | 'payment'       // 收款
+  | 'expense'       // 支出
+  | 'refund';       // 退款
+
+// 交易记录类型定义
+export interface Transaction {
+  id: string;
+  date: Date;
+  type: TransactionType;
+  amount: number;
+  description: string;
+  projectId: string;
+  projectName: string;
+  clientName: string;
+  invoiceNumber?: string;
+  paymentMethod?: string;
+  reconciled: boolean;
+  reconciliationId?: string;
+}
+
+// 对账记录类型定义
+export interface ReconciliationRecord {
+  id: string;
+  date: Date;
+  status: ReconciliationStatus;
+  totalAmount: number;
+  reconciledAmount: number;
+  discrepancyAmount: number;
+  periodStart: Date;
+  periodEnd: Date;
+  transactions: Transaction[];
+  notes?: string;
+  reconciledBy?: string;
+}
 
 @Component({
   selector: 'app-reconciliation',
-  imports: [],
+  imports: [CommonModule, ReactiveFormsModule, FormsModule],
   templateUrl: './reconciliation.html',
-  styleUrl: './reconciliation.scss'
+  styleUrls: ['./reconciliation.scss']
 })
-export class Reconciliation {
+export class Reconciliation implements OnInit {
+  // 使用signal管理对账记录数据
+  reconciliationRecords = signal<ReconciliationRecord[]>([]);
+  
+  // 使用signal管理未对账交易
+  unreconciledTransactions = signal<Transaction[]>([]);
+  
+  // 当前选中的对账记录
+  selectedRecord = signal<ReconciliationRecord | null>(null);
+  
+  // 对账表单
+  reconciliationForm: FormGroup;
+  
+  // 模态框状态
+  showDetailModal = signal(false);
+  showReconcileModal = signal(false);
+  
+  // 过滤条件
+  dateRangeFilter = signal<{ start: Date | null; end: Date | null }>({ start: null, end: null });
+  statusFilter = signal<ReconciliationStatus | 'all'>('all');
+
+  constructor(private fb: FormBuilder) {
+    // 初始化对账表单
+    this.reconciliationForm = this.fb.group({
+      periodStart: [''],
+      periodEnd: [''],
+      notes: ['']
+    });
+  }
+
+  ngOnInit(): void {
+    // 初始化对账记录数据
+    this.loadReconciliationData();
+    // 初始化未对账交易数据
+    this.loadUnreconciledTransactions();
+  }
+
+  // 加载对账记录数据
+  loadReconciliationData(): void {
+    // 模拟从API获取对账记录数据
+    const mockRecords: ReconciliationRecord[] = [
+      {
+        id: 'REC-2023-08',
+        date: new Date('2023-08-31'),
+        status: 'completed',
+        totalAmount: 150000,
+        reconciledAmount: 150000,
+        discrepancyAmount: 0,
+        periodStart: new Date('2023-08-01'),
+        periodEnd: new Date('2023-08-31'),
+        transactions: [],
+        notes: '8月份财务对账已完成',
+        reconciledBy: '张三'
+      },
+      {
+        id: 'REC-2023-07',
+        date: new Date('2023-07-31'),
+        status: 'completed',
+        totalAmount: 120000,
+        reconciledAmount: 120000,
+        discrepancyAmount: 0,
+        periodStart: new Date('2023-07-01'),
+        periodEnd: new Date('2023-07-31'),
+        transactions: [],
+        notes: '7月份财务对账已完成',
+        reconciledBy: '李四'
+      },
+      {
+        id: 'REC-2023-09-P',
+        date: new Date('2023-09-15'),
+        status: 'partial',
+        totalAmount: 85000,
+        reconciledAmount: 60000,
+        discrepancyAmount: 25000,
+        periodStart: new Date('2023-09-01'),
+        periodEnd: new Date('2023-09-30'),
+        transactions: [],
+        notes: '9月份财务对账进行中',
+        reconciledBy: '王五'
+      }
+    ];
+
+    this.reconciliationRecords.set(mockRecords);
+  }
+
+  // 加载未对账交易数据
+  loadUnreconciledTransactions(): void {
+    // 模拟从API获取未对账交易数据
+    const mockTransactions: Transaction[] = [
+      {
+        id: 'TRX-2023-09-001',
+        date: new Date('2023-09-10'),
+        type: 'payment',
+        amount: 45000,
+        description: '现代风格住宅室内设计项目首付款',
+        projectId: 'PRJ-2023-001',
+        projectName: '现代风格住宅室内设计',
+        clientName: '星光地产',
+        invoiceNumber: 'INV-2023-09-001',
+        paymentMethod: '银行转账',
+        reconciled: false
+      },
+      {
+        id: 'TRX-2023-09-002',
+        date: new Date('2023-09-12'),
+        type: 'expense',
+        amount: 12000,
+        description: '3D建模软件许可证续费',
+        projectId: 'PRJ-2023-002',
+        projectName: '商业办公空间设计',
+        clientName: '未来科技有限公司',
+        paymentMethod: '信用卡',
+        reconciled: false
+      },
+      {
+        id: 'TRX-2023-09-003',
+        date: new Date('2023-09-15'),
+        type: 'payment',
+        amount: 30000,
+        description: '品牌展厅设计项目尾款',
+        projectId: 'PRJ-2023-005',
+        projectName: '品牌展厅设计',
+        clientName: '时尚家居品牌',
+        invoiceNumber: 'INV-2023-09-002',
+        paymentMethod: '银行转账',
+        reconciled: false
+      },
+      {
+        id: 'TRX-2023-09-004',
+        date: new Date('2023-09-18'),
+        type: 'expense',
+        amount: 8000,
+        description: '渲染服务器租赁费用',
+        projectId: 'PRJ-2023-003',
+        projectName: '餐饮空间改造设计',
+        clientName: '悦享餐饮集团',
+        paymentMethod: '银行转账',
+        reconciled: false
+      }
+    ];
+
+    this.unreconciledTransactions.set(mockTransactions);
+  }
+
+  // 开始新的对账
+  startReconciliation(): void {
+    const today = new Date();
+    const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
+    
+    this.reconciliationForm.patchValue({
+      periodStart: firstDayOfMonth,
+      periodEnd: today
+    });
+    
+    this.showReconcileModal.set(true);
+  }
+
+  // 执行对账操作
+  performReconciliation(): void {
+    if (this.reconciliationForm.invalid) return;
+    
+    const formValue = this.reconciliationForm.value;
+    const periodStart = formValue.periodStart;
+    const periodEnd = formValue.periodEnd;
+    
+    // 过滤出期间内的交易
+    const periodTransactions = this.unreconciledTransactions()
+      .filter(trx => trx.date >= periodStart && trx.date <= periodEnd);
+    
+    // 计算交易总额
+    const totalAmount = periodTransactions.reduce((sum, trx) => {
+      return trx.type === 'payment' ? sum + trx.amount : sum - trx.amount;
+    }, 0);
+    
+    // 创建新的对账记录
+    const newRecord: ReconciliationRecord = {
+      id: `REC-${new Date().getFullYear()}-${(new Date().getMonth() + 1).toString().padStart(2, '0')}-${Date.now().toString().slice(-4)}`,
+      date: new Date(),
+      status: 'partial', // 默认为部分对账
+      totalAmount: totalAmount,
+      reconciledAmount: totalAmount, // 假设所有选中的交易都已对账
+      discrepancyAmount: 0, // 假设没有差异
+      periodStart: periodStart,
+      periodEnd: periodEnd,
+      transactions: periodTransactions,
+      notes: formValue.notes,
+      reconciledBy: '当前用户'
+    };
+    
+    // 更新对账记录列表
+    const updatedRecords = [newRecord, ...this.reconciliationRecords()];
+    this.reconciliationRecords.set(updatedRecords);
+    
+    // 更新未对账交易列表
+    const remainingTransactions = this.unreconciledTransactions()
+      .filter(trx => !periodTransactions.some(t => t.id === trx.id));
+    this.unreconciledTransactions.set(remainingTransactions);
+    
+    this.showReconcileModal.set(false);
+  }
+
+  // 查看对账详情
+  viewReconciliationDetail(record: ReconciliationRecord): void {
+    this.selectedRecord.set(record);
+    this.showDetailModal.set(true);
+  }
+
+  // 标记交易为已对账
+  markTransactionAsReconciled(transaction: Transaction): void {
+    const updatedTransactions = this.unreconciledTransactions()
+      .map(trx => {
+        if (trx.id === transaction.id) {
+          return { ...trx, reconciled: true };
+        }
+        return trx;
+      });
+    
+    this.unreconciledTransactions.set(updatedTransactions);
+  }
+
+  // 获取对账状态文本
+  getStatusText(status: ReconciliationStatus): string {
+    const statusMap: Record<ReconciliationStatus, string> = {
+      'pending': '待对账',
+      'partial': '部分对账',
+      'completed': '已完成',
+      'discrepancy': '有差异'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取对账状态样式类
+  getStatusClass(status: ReconciliationStatus): string {
+    const statusClassMap: Record<ReconciliationStatus, string> = {
+      'pending': 'status-pending',
+      'partial': 'status-partial',
+      'completed': 'status-completed',
+      'discrepancy': 'status-discrepancy'
+    };
+    return statusClassMap[status] || '';
+  }
+
+  // 获取交易类型文本
+  getTransactionTypeText(type: TransactionType): string {
+    const typeMap: Record<TransactionType, string> = {
+      'payment': '收款',
+      'expense': '支出',
+      'refund': '退款'
+    };
+    return typeMap[type] || type;
+  }
+
+  // 获取交易类型样式类
+  getTransactionTypeClass(type: TransactionType): string {
+    const typeClassMap: Record<TransactionType, string> = {
+      'payment': 'transaction-payment',
+      'expense': 'transaction-expense',
+      'refund': 'transaction-refund'
+    };
+    return typeClassMap[type] || '';
+  }
+
+  // 格式化日期
+  formatDate(date: Date): string {
+    if (!date) return '';
+    return new Date(date).toLocaleDateString('zh-CN');
+  }
+
+  // 格式化金额
+  formatAmount(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY',
+      minimumFractionDigits: 0,
+      maximumFractionDigits: 0
+    }).format(amount);
+  }
+
+  // 应用日期范围过滤
+  applyDateRangeFilter(start: Date | null, end: Date | null): void {
+    this.dateRangeFilter.set({ start, end });
+    // 这里应该实现过滤逻辑,但为了简化,我们假设它会在模板中使用
+  }
+
+  // 应用状态过滤
+  applyStatusFilter(status: ReconciliationStatus | 'all'):
+    void {
+    this.statusFilter.set(status);
+    // 这里应该实现过滤逻辑,但为了简化,我们假设它会在模板中使用
+  }
+  
+  // 更新日期范围过滤的开始日期
+  updateDateRangeStart(start: Date | string | null): void {
+    const currentFilter = this.dateRangeFilter();
+    this.dateRangeFilter.set({ 
+      ...currentFilter, 
+      start: start ? new Date(start) : null 
+    });
+  }
+
+  // 更新日期范围过滤的结束日期
+  updateDateRangeEnd(end: Date | string | null): void {
+    const currentFilter = this.dateRangeFilter();
+    this.dateRangeFilter.set({ 
+      ...currentFilter, 
+      end: end ? new Date(end) : null 
+    });
+  }
+  // 过滤符合条件的未对账交易
+  getFilteredUnreconciledTransactions(): Transaction[] {
+    const formValue = this.reconciliationForm.value;
+    const periodStart = formValue.periodStart ? new Date(formValue.periodStart) : null;
+    const periodEnd = formValue.periodEnd ? new Date(formValue.periodEnd) : null;
+    
+    return this.unreconciledTransactions().filter(transaction => {
+      const transactionDate = new Date(transaction.date);
+      const matchesStart = !periodStart || transactionDate >= periodStart;
+      const matchesEnd = !periodEnd || transactionDate <= periodEnd;
+      return matchesStart && matchesEnd;
+    });
+  }
 
+  // 检查是否有符合条件的未对账交易
+  hasFilteredUnreconciledTransactions(): boolean {
+    return this.getFilteredUnreconciledTransactions().length > 0;
+  }
 }

+ 269 - 1
src/app/pages/finance/reports/reports.html

@@ -1 +1,269 @@
-<p>reports works!</p>
+<div class="reports-container">
+  <div class="reports-header">
+    <h1>报表管理</h1>
+    <p>生成、查看和导出各类财务报表</p>
+  </div>
+
+  <!-- 报表参数设置区域 -->
+  <div class="reports-params">
+    <div class="params-card">
+      <h2>报表参数</h2>
+      <div class="params-grid">
+        <div class="param-item">
+          <label for="report-type">报表类型</label>
+          <select 
+            id="report-type" 
+            [ngModel]="selectedReportType()"
+            (ngModelChange)="selectedReportType.set($event)"
+            class="form-select"
+          >
+            <option *ngFor="let type of reportTypes" [value]="type.value">
+              {{ type.label }}
+            </option>
+          </select>
+        </div>
+
+        <div class="param-item">
+          <label for="time-period">时间周期</label>
+          <select 
+            id="time-period" 
+            [ngModel]="selectedTimePeriod()"
+            (ngModelChange)="selectedTimePeriod.set($event)"
+            class="form-select"
+          >
+            <option *ngFor="let period of timePeriods" [value]="period.value">
+              {{ period.label }}
+            </option>
+          </select>
+        </div>
+
+        <div class="param-item">
+          <label for="start-date">开始日期</label>
+          <input 
+            id="start-date" 
+            type="date" 
+            [ngModel]="formatDate(dateRange().start)"
+            (ngModelChange)="updateDateRangeStart($event)"
+            class="form-input"
+          >
+        </div>
+
+        <div class="param-item">
+          <label for="end-date">结束日期</label>
+          <input 
+            id="end-date" 
+            type="date" 
+            [ngModel]="formatDate(dateRange().end)"
+            (ngModelChange)="updateDateRangeEnd($event)"
+            class="form-input"
+          >
+        </div>
+
+        <div class="param-item">
+          <label for="chart-type">图表类型</label>
+          <select 
+            id="chart-type" 
+            [ngModel]="selectedChartType()"
+            (ngModelChange)="selectedChartType.set($event)"
+            class="form-select"
+          >
+            <option *ngFor="let chart of chartTypes" [value]="chart.value">
+              {{ chart.label }}
+            </option>
+          </select>
+        </div>
+      </div>
+
+      <div class="params-actions">
+        <button 
+          class="btn-primary" 
+          (click)="generateReport()"
+          [disabled]="isLoading()"
+        >
+          <i class="fa fa-refresh" [class.fa-spin]="isLoading()"></i>
+          {{ isLoading() ? '生成中...' : '生成报表' }}
+        </button>
+        <button 
+          class="btn-secondary" 
+          (click)="exportReport()"
+          [disabled]="!reportData()"
+        >
+          <i class="fa fa-download"></i> 导出报表
+        </button>
+        <button 
+          class="btn-outline" 
+          (click)="resetParameters()"
+        >
+          <i class="fa fa-undo"></i> 重置
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 报表结果展示区域 -->
+  <div class="reports-result" *ngIf="reportData()">
+    <div class="result-header">
+      <h2>{{ reportData()?.title }}</h2>
+      <div class="result-meta">
+        <span>报表ID: {{ reportData()?.id }}</span>
+        <span>生成时间: {{ formatDate(reportData()?.generatedAt) }}</span>
+        <span>时间范围: {{ formatDate(reportData()?.dateRange?.start) }} - {{ formatDate(reportData()?.dateRange?.end) }}</span>
+      </div>
+    </div>
+
+    <!-- 报表汇总信息 -->
+    <div class="summary-cards">
+      <div class="summary-card" *ngIf="reportData()?.summary?.['totalRevenue'] !== undefined">
+        <div class="summary-icon">💰</div>
+        <div class="summary-content">
+          <div class="summary-label">总收入</div>
+          <div class="summary-value">{{ formatAmount(reportData()?.summary?.['totalRevenue'] || 0) }}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card" *ngIf="reportData()?.summary?.['totalExpense'] !== undefined">
+        <div class="summary-icon">📉</div>
+        <div class="summary-content">
+          <div class="summary-label">总支出</div>
+          <div class="summary-value">{{ formatAmount(reportData()?.summary?.['totalExpense'] || 0) }}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card" *ngIf="reportData()?.summary?.['netProfit'] !== undefined">
+        <div class="summary-icon">📊</div>
+        <div class="summary-content">
+          <div class="summary-label">净利润</div>
+          <div class="summary-value profit">{{ formatAmount(reportData()?.summary?.['netProfit'] || 0) }}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card" *ngIf="reportData()?.summary?.['profitMargin'] !== undefined">
+        <div class="summary-icon">📈</div>
+        <div class="summary-content">
+          <div class="summary-label">利润率</div>
+          <div class="summary-value">{{ formatPercentage(reportData()?.summary?.['profitMargin'] || 0) }}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card" *ngIf="reportData()?.summary?.['totalProjects'] !== undefined">
+        <div class="summary-icon">📋</div>
+        <div class="summary-content">
+          <div class="summary-label">项目总数</div>
+          <div class="summary-value">{{ reportData()?.summary?.['totalProjects'] || 0 }}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card" *ngIf="reportData()?.summary?.['averageProfitMargin'] !== undefined">
+        <div class="summary-icon">💰</div>
+        <div class="summary-content">
+          <div class="summary-label">平均利润率</div>
+          <div class="summary-value">{{ formatPercentage(reportData()?.summary?.['averageProfitMargin'] || 0) }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 报表图表展示 -->
+    <div class="chart-container">
+      <div class="chart-header">
+        <h3>数据可视化</h3>
+        <div class="chart-legend">
+          <div class="legend-item" *ngFor="let data of reportData()?.chartData || []">
+            <span class="legend-color" [style.backgroundColor]="data.color"></span>
+            <span class="legend-label">{{ data.label }}: {{ formatAmount(data.value) }}</span>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 柱状图展示 -->
+      <div class="chart-content" *ngIf="reportData()?.chartType === 'bar'">
+        <div class="bar-chart">
+          <div 
+            *ngFor="let data of reportData()?.chartData || []" 
+            class="bar-item" 
+            [style.width.%]="(data.value / maxValue(reportData()?.chartData || []) * 100)"
+            [style.backgroundColor]="data.color"
+            title="{{ data.label }}: {{ formatAmount(data.value) }}"
+          >
+            <div class="bar-label">{{ data.label }}</div>
+            <div class="bar-value">{{ formatAmount(data.value) }}</div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 饼图展示 -->
+      <div class="chart-content" *ngIf="reportData()?.chartType === 'pie'">
+        <div class="pie-chart">
+          <!-- 简化的饼图实现 -->
+          <div class="pie-container">
+            <div 
+              *ngFor="let data of reportData()?.chartData || []; let i = index" 
+              class="pie-slice" 
+              [style.backgroundColor]="data.color"
+              [style.transform]="getPieTransform(i, reportData()?.chartData || [])"
+            ></div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 表格展示 -->
+      <div class="chart-content table-container" *ngIf="reportData()?.chartType === 'table'">
+        <table class="data-table">
+          <thead>
+            <tr>
+              <th>项目</th>
+              <th>金额</th>
+              <th>占比</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr *ngFor="let data of reportData()?.chartData || []">
+              <td>
+                <div class="table-label">
+                  <span class="label-color" [style.backgroundColor]="data.color"></span>
+                  {{ data.label }}
+                </div>
+              </td>
+              <td>{{ formatAmount(data.value) }}</td>
+              <td>{{ formatPercentage((data.value / totalValue(reportData()?.chartData || [])) * 100) }}</td>
+            </tr>
+            <tr class="table-total">
+              <td>总计</td>
+              <td>{{ formatAmount(totalValue(reportData()?.chartData || [])) }}</td>
+              <td>100%</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+
+  <!-- 历史报表记录 -->
+  <div class="historical-reports">
+    <div class="history-header">
+      <h2>历史报表</h2>
+      <div class="history-count">{{ historicalReports().length }} 份记录</div>
+    </div>
+    
+    <div class="history-list">
+      <div 
+        *ngFor="let report of historicalReports()" 
+        class="history-item" 
+        (click)="viewHistoricalReport(report)"
+      >
+        <div class="history-icon">{{ getReportTypeIcon(report.type) }}</div>
+        <div class="history-content">
+          <div class="history-title">{{ report.title }}</div>
+          <div class="history-meta">
+            <span>{{ report.id }}</span>
+            <span>{{ formatDate(report.generatedAt) }}</span>
+          </div>
+        </div>
+        <div class="history-actions">
+          <button class="btn-icon" title="查看">
+            <i class="fa fa-eye"></i>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 571 - 0
src/app/pages/finance/reports/reports.scss

@@ -0,0 +1,571 @@
+.reports-container {
+  padding: 20px;
+  max-width: 1400px;
+  margin: 0 auto;
+}
+
+.reports-header {
+  margin-bottom: 30px;
+  text-align: center;
+}
+
+.reports-header h1 {
+  font-size: 28px;
+  font-weight: 600;
+  margin-bottom: 8px;
+  color: #333;
+}
+
+.reports-header p {
+  font-size: 16px;
+  color: #666;
+  margin: 0;
+}
+
+/* 报表参数设置区域 */
+.reports-params {
+  margin-bottom: 30px;
+}
+
+.params-card {
+  background-color: #fff;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border: 1px solid #e0e0e0;
+}
+
+.params-card h2 {
+  font-size: 20px;
+  font-weight: 600;
+  margin-bottom: 20px;
+  color: #333;
+}
+
+.params-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+}
+
+.param-item {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.param-item label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #555;
+}
+
+.form-select,
+.form-input {
+  padding: 10px 12px;
+  border: 1px solid #ddd;
+  border-radius: 6px;
+  font-size: 14px;
+  transition: all 0.3s ease;
+  background-color: #fff;
+}
+
+.form-select:focus,
+.form-input:focus {
+  outline: none;
+  border-color: #4a90e2;
+  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
+}
+
+.params-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  flex-wrap: wrap;
+}
+
+/* 按钮样式 */
+.btn-primary,
+.btn-secondary,
+.btn-outline {
+  padding: 10px 20px;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.btn-primary {
+  background-color: #4a90e2;
+  color: white;
+}
+
+.btn-primary:hover:not(:disabled) {
+  background-color: #357abd;
+  transform: translateY(-1px);
+}
+
+.btn-primary:disabled {
+  background-color: #ccc;
+  cursor: not-allowed;
+  transform: none;
+}
+
+.btn-secondary {
+  background-color: #28a745;
+  color: white;
+}
+
+.btn-secondary:hover:not(:disabled) {
+  background-color: #218838;
+  transform: translateY(-1px);
+}
+
+.btn-secondary:disabled {
+  background-color: #ccc;
+  cursor: not-allowed;
+  transform: none;
+}
+
+.btn-outline {
+  background-color: transparent;
+  color: #666;
+  border: 1px solid #ddd;
+}
+
+.btn-outline:hover {
+  background-color: #f5f5f5;
+  border-color: #4a90e2;
+  color: #4a90e2;
+}
+
+.btn-icon {
+  background: none;
+  border: none;
+  cursor: pointer;
+  padding: 8px;
+  border-radius: 4px;
+  color: #666;
+  transition: all 0.3s ease;
+}
+
+.btn-icon:hover {
+  background-color: #f5f5f5;
+  color: #4a90e2;
+}
+
+/* 报表结果展示区域 */
+.reports-result {
+  margin-bottom: 40px;
+  background-color: #fff;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border: 1px solid #e0e0e0;
+  animation: fadeIn 0.5s ease;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.result-header {
+  margin-bottom: 24px;
+  border-bottom: 1px solid #eee;
+  padding-bottom: 16px;
+}
+
+.result-header h2 {
+  font-size: 22px;
+  font-weight: 600;
+  margin-bottom: 8px;
+  color: #333;
+}
+
+.result-meta {
+  display: flex;
+  gap: 20px;
+  flex-wrap: wrap;
+  font-size: 13px;
+  color: #666;
+}
+
+/* 汇总卡片 */
+.summary-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 32px;
+}
+
+.summary-card {
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  padding: 20px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  transition: all 0.3s ease;
+  border: 1px solid #e9ecef;
+}
+
+.summary-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.summary-icon {
+  font-size: 24px;
+}
+
+.summary-content {
+  flex: 1;
+}
+
+.summary-label {
+  font-size: 13px;
+  color: #666;
+  margin-bottom: 4px;
+}
+
+.summary-value {
+  font-size: 20px;
+  font-weight: 600;
+  color: #333;
+}
+
+.summary-value.profit {
+  color: #28a745;
+}
+
+/* 图表容器 */
+.chart-container {
+  background-color: #fafbfc;
+  border-radius: 8px;
+  padding: 20px;
+  border: 1px solid #e9ecef;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.chart-header h3 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #333;
+  margin: 0;
+}
+
+.chart-legend {
+  display: flex;
+  gap: 16px;
+  flex-wrap: wrap;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: #666;
+}
+
+.legend-color {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+/* 柱状图 */
+.bar-chart {
+  display: flex;
+  gap: 16px;
+  align-items: flex-end;
+  height: 300px;
+  padding: 20px 0;
+  position: relative;
+}
+
+.bar-item {
+  flex: 1;
+  background-color: #4a90e2;
+  border-radius: 6px 6px 0 0;
+  position: relative;
+  transition: all 0.3s ease;
+  cursor: pointer;
+  min-width: 40px;
+}
+
+.bar-item:hover {
+  opacity: 0.9;
+  transform: translateY(-2px);
+}
+
+.bar-label {
+  position: absolute;
+  bottom: -30px;
+  left: 50%;
+  transform: translateX(-50%);
+  font-size: 12px;
+  color: #666;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+}
+
+.bar-value {
+  position: absolute;
+  top: -30px;
+  left: 50%;
+  transform: translateX(-50%);
+  font-size: 13px;
+  font-weight: 600;
+  color: #333;
+  background-color: #fff;
+  padding: 4px 8px;
+  border-radius: 4px;
+  white-space: nowrap;
+}
+
+/* 饼图 */
+.pie-chart {
+  display: flex;
+  justify-content: center;
+  padding: 20px;
+}
+
+.pie-container {
+  width: 300px;
+  height: 300px;
+  border-radius: 50%;
+  position: relative;
+  overflow: hidden;
+}
+
+.pie-slice {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  transform-origin: center;
+}
+
+/* 表格 */
+.table-container {
+  overflow-x: auto;
+}
+
+.data-table {
+  width: 100%;
+  border-collapse: collapse;
+  background-color: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.data-table th {
+  background-color: #f8f9fa;
+  padding: 12px 16px;
+  text-align: left;
+  font-weight: 600;
+  font-size: 14px;
+  color: #333;
+  border-bottom: 2px solid #e9ecef;
+}
+
+.data-table td {
+  padding: 12px 16px;
+  font-size: 14px;
+  color: #555;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.data-table tr:last-child td {
+  border-bottom: none;
+}
+
+.data-table tr:hover {
+  background-color: #f8f9fa;
+}
+
+.table-total {
+  background-color: #f8f9fa;
+  font-weight: 600;
+}
+
+.table-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.label-color {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+/* 历史报表 */
+.historical-reports {
+  background-color: #fff;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border: 1px solid #e0e0e0;
+}
+
+.history-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.history-header h2 {
+  font-size: 20px;
+  font-weight: 600;
+  color: #333;
+  margin: 0;
+}
+
+.history-count {
+  font-size: 14px;
+  color: #666;
+  background-color: #f8f9fa;
+  padding: 4px 12px;
+  border-radius: 16px;
+}
+
+.history-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.history-item {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 16px;
+  border: 1px solid #e9ecef;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  background-color: #fafbfc;
+}
+
+.history-item:hover {
+  background-color: #f0f2f5;
+  border-color: #4a90e2;
+  transform: translateX(2px);
+}
+
+.history-icon {
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+.history-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.history-title {
+  font-size: 15px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 4px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.history-meta {
+  display: flex;
+  gap: 12px;
+  font-size: 12px;
+  color: #666;
+}
+
+.history-actions {
+  flex-shrink: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .reports-container {
+    padding: 16px;
+  }
+  
+  .params-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .params-actions {
+    justify-content: center;
+  }
+  
+  .summary-cards {
+    grid-template-columns: 1fr 1fr;
+  }
+  
+  .result-meta {
+    flex-direction: column;
+    gap: 8px;
+  }
+  
+  .bar-chart {
+    height: 200px;
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .bar-item {
+    min-height: 40px;
+  }
+  
+  .pie-container {
+    width: 200px;
+    height: 200px;
+  }
+}
+
+@media (max-width: 480px) {
+  .summary-cards {
+    grid-template-columns: 1fr;
+  }
+  
+  .params-actions {
+    flex-direction: column;
+  }
+  
+  .chart-header {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .chart-legend {
+    justify-content: flex-start;
+  }
+}

+ 391 - 3
src/app/pages/finance/reports/reports.ts

@@ -1,11 +1,399 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { signal } from '@angular/core';
+
+// 报表类型定义
+export type ReportType = 
+  | 'financial_summary'  // 财务汇总
+  | 'project_profitability' // 项目盈利能力
+  | 'cash_flow'         // 现金流
+  | 'expense_analysis'; // 支出分析
+
+// 时间周期类型
+export type TimePeriod = 
+  | 'daily'    // 日
+  | 'weekly'   // 周
+  | 'monthly'  // 月
+  | 'quarterly' // 季度
+  | 'yearly';  // 年
+
+// 图表类型
+export type ChartType = 
+  | 'bar'      // 柱状图
+  | 'line'     // 折线图
+  | 'pie'      // 饼图
+  | 'table';   // 表格
+
+// 图表数据点类型
+export interface ChartDataPoint {
+  label: string;
+  value: number;
+  color?: string;
+}
+
+// 报表数据类型
+export interface ReportData {
+  id: string;
+  title: string;
+  type: ReportType;
+  period: TimePeriod;
+  dateRange: { start: Date; end: Date };
+  generatedAt: Date;
+  chartType: ChartType;
+  chartData: ChartDataPoint[];
+  summary?: { [key: string]: number };
+  details?: { [key: string]: any }[];
+}
 
 @Component({
   selector: 'app-reports',
-  imports: [],
+  imports: [CommonModule, FormsModule],
   templateUrl: './reports.html',
   styleUrl: './reports.scss'
 })
-export class Reports {
+export class Reports implements OnInit {
+  // 报表类型选项
+  reportTypes: { value: ReportType; label: string }[] = [
+    { value: 'financial_summary', label: '财务汇总报表' },
+    { value: 'project_profitability', label: '项目盈利能力报表' },
+    { value: 'cash_flow', label: '现金流报表' },
+    { value: 'expense_analysis', label: '支出分析报表' }
+  ];
+
+  // 时间周期选项
+  timePeriods: { value: TimePeriod; label: string }[] = [
+    { value: 'daily', label: '按日' },
+    { value: 'weekly', label: '按周' },
+    { value: 'monthly', label: '按月' },
+    { value: 'quarterly', label: '按季度' },
+    { value: 'yearly', label: '按年' }
+  ];
+
+  // 图表类型选项
+  chartTypes: { value: ChartType; label: string }[] = [
+    { value: 'bar', label: '柱状图' },
+    { value: 'line', label: '折线图' },
+    { value: 'pie', label: '饼图' },
+    { value: 'table', label: '表格' }
+  ];
+
+  // 当前选择的报表类型
+  selectedReportType = signal<ReportType>('financial_summary');
+  
+  // 当前选择的时间周期
+  selectedTimePeriod = signal<TimePeriod>('monthly');
+  
+  // 日期范围
+  dateRange = signal<{ start: Date; end: Date }>({
+    start: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
+    end: new Date()
+  });
+
+  // 当前选择的图表类型
+  selectedChartType = signal<ChartType>('bar');
+  
+  // 报表数据
+  reportData = signal<ReportData | null>(null);
+  
+  // 生成报表时的加载状态
+  isLoading = signal(false);
+
+  // 安全更新日期范围的开始日期
+  updateDateRangeStart(date: string) {
+    this.dateRange.set({
+      ...this.dateRange(),
+      start: new Date(date)
+    });
+  }
+
+  // 安全更新日期范围的结束日期
+  updateDateRangeEnd(date: string) {
+    this.dateRange.set({
+      ...this.dateRange(),
+      end: new Date(date)
+    });
+  }
+  
+  // 历史报表记录
+  historicalReports = signal<ReportData[]>([]);
+
+  constructor() {}
+
+  ngOnInit(): void {
+    // 加载历史报表记录
+    this.loadHistoricalReports();
+  }
+
+  // 加载历史报表记录
+  loadHistoricalReports(): void {
+    // 模拟加载历史报表数据
+    const mockHistory: ReportData[] = [
+      {
+        id: 'REP-2023-09-001',
+        title: '2023年9月财务汇总报表',
+        type: 'financial_summary',
+        period: 'monthly',
+        dateRange: {
+          start: new Date(2023, 8, 1),
+          end: new Date(2023, 8, 30)
+        },
+        generatedAt: new Date(2023, 8, 30, 15, 30),
+        chartType: 'bar',
+        chartData: [],
+        summary: {
+          totalRevenue: 120000,
+          totalExpense: 85000,
+          netProfit: 35000
+        }
+      },
+      {
+        id: 'REP-2023-08-002',
+        title: '2023年第三季度项目盈利能力报表',
+        type: 'project_profitability',
+        period: 'quarterly',
+        dateRange: {
+          start: new Date(2023, 6, 1),
+          end: new Date(2023, 8, 30)
+        },
+        generatedAt: new Date(2023, 8, 30, 16, 45),
+        chartType: 'pie',
+        chartData: [],
+        summary: {
+          averageProfitMargin: 32.5
+        }
+      }
+    ];
+
+    this.historicalReports.set(mockHistory);
+  }
+
+  // 生成报表
+  generateReport(): void {
+    this.isLoading.set(true);
+
+    // 模拟API调用延迟
+    setTimeout(() => {
+      const reportId = `REP-${new Date().getFullYear()}-${(new Date().getMonth() + 1).toString().padStart(2, '0')}-${Date.now().toString().slice(-4)}`;
+      const reportTitle = this.getReportTitle();
+      const chartData = this.generateMockChartData();
+      const summaryData = this.generateMockSummary();
+
+      const newReport: ReportData = {
+        id: reportId,
+        title: reportTitle,
+        type: this.selectedReportType(),
+        period: this.selectedTimePeriod(),
+        dateRange: { ...this.dateRange() },
+        generatedAt: new Date(),
+        chartType: this.selectedChartType(),
+        chartData: chartData,
+        summary: summaryData
+      };
+
+      this.reportData.set(newReport);
+      
+      // 添加到历史记录
+      const updatedHistory = [newReport, ...this.historicalReports()];
+      this.historicalReports.set(updatedHistory);
+      
+      this.isLoading.set(false);
+    }, 1500);
+  }
+
+  // 获取报表标题
+  getReportTitle(): string {
+    const reportTypeLabel = this.reportTypes.find(type => type.value === this.selectedReportType())?.label || '';
+    const periodLabel = this.timePeriods.find(period => period.value === this.selectedTimePeriod())?.label || '';
+    
+    const startDateStr = this.formatDate(this.dateRange().start);
+    const endDateStr = this.formatDate(this.dateRange().end);
+    
+    return `${startDateStr}至${endDateStr}${periodLabel}${reportTypeLabel}`;
+  }
+
+  // 生成模拟图表数据
+  generateMockChartData(): ChartDataPoint[] {
+    switch (this.selectedReportType()) {
+      case 'financial_summary':
+        return [
+          { label: '设计费用', value: 45000, color: '#3498db' },
+          { label: '项目管理', value: 30000, color: '#2ecc71' },
+          { label: '材料费用', value: 25000, color: '#f39c12' },
+          { label: '设备租赁', value: 15000, color: '#e74c3c' },
+          { label: '其他收入', value: 10000, color: '#9b59b6' }
+        ];
+      case 'project_profitability':
+        return [
+          { label: '现代住宅设计', value: 15000, color: '#3498db' },
+          { label: '商业办公空间', value: 12000, color: '#2ecc71' },
+          { label: '品牌展厅设计', value: 8000, color: '#f39c12' },
+          { label: '餐饮空间改造', value: 7500, color: '#e74c3c' },
+          { label: '酒店大堂设计', value: 6000, color: '#9b59b6' }
+        ];
+      case 'cash_flow':
+        return [
+          { label: '1月', value: 25000, color: '#3498db' },
+          { label: '2月', value: 28000, color: '#2ecc71' },
+          { label: '3月', value: 32000, color: '#f39c12' },
+          { label: '4月', value: 29000, color: '#e74c3c' },
+          { label: '5月', value: 35000, color: '#9b59b6' },
+          { label: '6月', value: 38000, color: '#1abc9c' }
+        ];
+      case 'expense_analysis':
+        return [
+          { label: '人力成本', value: 40000, color: '#3498db' },
+          { label: '软件工具', value: 15000, color: '#2ecc71' },
+          { label: '办公费用', value: 10000, color: '#f39c12' },
+          { label: '差旅费用', value: 8000, color: '#e74c3c' },
+          { label: '营销费用', value: 12000, color: '#9b59b6' }
+        ];
+      default:
+        return [];
+    }
+  }
+
+  // 生成模拟汇总数据
+  generateMockSummary(): { [key: string]: number } {
+    const summary: { [key: string]: number } = {};
+    
+    switch (this.selectedReportType()) {
+      case 'financial_summary':
+          summary['totalRevenue'] = 120000;
+          summary['totalExpense'] = 75000;
+          summary['netProfit'] = 45000;
+          summary['profitMargin'] = 37.5;
+        break;
+      case 'project_profitability':
+          summary['totalProjects'] = 15;
+          summary['completedProjects'] = 10;
+          summary['averageProfitMargin'] = 32.8;
+          summary['highestProjectProfit'] = 15000;
+        break;
+      case 'cash_flow':
+          summary['beginningBalance'] = 50000;
+          summary['endingBalance'] = 120000;
+          summary['netCashFlow'] = 70000;
+          summary['operatingCashFlow'] = 65000;
+        break;
+      case 'expense_analysis':
+          summary['totalExpense'] = 85000;
+          summary['personnelExpense'] = 40000;
+          summary['operatingExpense'] = 30000;
+          summary['otherExpense'] = 15000;
+        break;
+    }
+    
+    return summary;
+  }
+
+  // 导出报表
+  exportReport(): void {
+    if (!this.reportData()) {
+      // 显示提示信息
+      console.log('请先生成报表再导出');
+      return;
+    }
+
+    // 模拟导出功能
+    const report = this.reportData();
+    if (report) {
+      console.log('导出报表:', report.title);
+      alert(`报表"${report.title}"已导出`);
+    }
+  }
+
+  // 查看历史报表
+  viewHistoricalReport(report: ReportData): void {
+    this.reportData.set(report);
+    this.selectedReportType.set(report.type);
+    this.selectedTimePeriod.set(report.period);
+    this.dateRange.set(report.dateRange);
+    this.selectedChartType.set(report.chartType);
+  }
+
+  // 格式化日期
+  formatDate(date: Date | undefined | null): string {
+    if (!date) {
+      return new Date().toLocaleDateString('zh-CN');
+    }
+    return new Date(date).toLocaleDateString('zh-CN');
+  }
+
+  // 格式化金额
+  formatAmount(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY',
+      minimumFractionDigits: 0,
+      maximumFractionDigits: 0
+    }).format(amount);
+  }
+
+  // 格式化百分比
+  formatPercentage(value: number): string {
+    return `${value.toFixed(1)}%`;
+  }
+
+  // 获取报表类型图标
+  getReportTypeIcon(type: ReportType): string {
+    const iconMap: Record<ReportType, string> = {
+      'financial_summary': '📊',
+      'project_profitability': '💰',
+      'cash_flow': '💹',
+      'expense_analysis': '📉'
+    };
+    return iconMap[type] || '📋';
+  }
+
+  // 重置报表参数
+  resetParameters(): void {
+    this.selectedReportType.set('financial_summary');
+    this.selectedTimePeriod.set('monthly');
+    this.dateRange.set({
+      start: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
+      end: new Date()
+    });
+    this.selectedChartType.set('bar');
+    this.reportData.set(null);
+  }
+
+  // 获取图表数据中的最大值(用于柱状图比例计算)
+  maxValue(data: ChartDataPoint[]): number {
+    if (!data || data.length === 0) return 1;
+    return Math.max(...data.map(item => item.value));
+  }
+
+  // 计算图表数据的总和(用于饼图和占比计算)
+  totalValue(data: ChartDataPoint[]): number {
+    if (!data || data.length === 0) return 0;
+    return data.reduce((sum, item) => sum + item.value, 0);
+  }
+
+  // 获取饼图切片的变换样式
+  getPieTransform(index: number, data: ChartDataPoint[]): string {
+    const total = this.totalValue(data);
+    if (total === 0 || data.length === 0) return '';
+
+    // 计算当前切片和之前所有切片的总和
+    let cumulativeValue = 0;
+    for (let i = 0; i < index; i++) {
+      cumulativeValue += data[i].value;
+    }
+
+    // 计算当前切片的起始角度和结束角度
+    const startAngle = (cumulativeValue / total) * 360;
+    const endAngle = ((cumulativeValue + data[index].value) / total) * 360;
 
+    // 计算中间角度用于定位
+    const midAngle = (startAngle + endAngle) / 2;
+    
+    // 生成变换样式
+    // 注意:这里使用简化的实现,实际项目中可能需要更复杂的计算来生成真实的饼图
+    if (index === 0) {
+      return `rotate(${startAngle}deg)`;
+    } else {
+      return `rotate(${startAngle}deg)`;
+    }
+  }
 }