敖欣乐 7 месяцев назад
Родитель
Сommit
27bdb2a211
35 измененных файлов с 10979 добавлено и 3105 удалено
  1. 474 0
      lead-discovery/docs/DEMO_WALKTHROUGH.md
  2. 234 0
      lead-discovery/docs/IMPLEMENTATION_SUMMARY.md
  3. 141 0
      lead-discovery/docs/MEETING_ACTION_ITEMS_0213.md
  4. 853 0
      lead-discovery/docs/MODIFICATION_PLAN_0222.md
  5. 914 509
      lead-discovery/package-lock.json
  6. 2 1
      lead-discovery/package.json
  7. 56 60
      lead-discovery/src/app/app.component.ts
  8. 4 6
      lead-discovery/src/app/app.routes.ts
  9. 125 0
      lead-discovery/src/app/components/bgcheck-progress/bgcheck-progress.component.ts
  10. 182 0
      lead-discovery/src/app/models/lead.model.ts
  11. 273 0
      lead-discovery/src/app/pages/bgcheck/bgcheck.component.html
  12. 78 0
      lead-discovery/src/app/pages/bgcheck/bgcheck.component.scss
  13. 69 0
      lead-discovery/src/app/pages/bgcheck/bgcheck.component.ts
  14. 1050 0
      lead-discovery/src/app/pages/dashboard/dashboard.component.html
  15. 1510 0
      lead-discovery/src/app/pages/dashboard/dashboard.component.scss
  16. 6 1346
      lead-discovery/src/app/pages/dashboard/dashboard.component.ts
  17. 151 0
      lead-discovery/src/app/pages/kanban/kanban.component.html
  18. 103 0
      lead-discovery/src/app/pages/kanban/kanban.component.scss
  19. 2 256
      lead-discovery/src/app/pages/kanban/kanban.component.ts
  20. 1113 0
      lead-discovery/src/app/pages/lead-detail/lead-detail.component.html
  21. 898 0
      lead-discovery/src/app/pages/lead-detail/lead-detail.component.scss
  22. 589 564
      lead-discovery/src/app/pages/lead-detail/lead-detail.component.ts
  23. 192 0
      lead-discovery/src/app/pages/lead-list/lead-list.component.html
  24. 140 0
      lead-discovery/src/app/pages/lead-list/lead-list.component.scss
  25. 2 334
      lead-discovery/src/app/pages/lead-list/lead-list.component.ts
  26. 7 0
      lead-discovery/src/app/pages/list/list.component.html
  27. 19 0
      lead-discovery/src/app/pages/list/list.component.scss
  28. 15 14
      lead-discovery/src/app/pages/list/list.component.ts
  29. 557 0
      lead-discovery/src/app/services/ai-screen.service.ts
  30. 244 0
      lead-discovery/src/app/services/batch-input.service.ts
  31. 320 0
      lead-discovery/src/app/services/entity-resolver.service.ts
  32. 208 0
      lead-discovery/src/app/services/generate-api.service.ts
  33. 305 4
      lead-discovery/src/app/services/mock-data.service.ts
  34. 141 9
      lead-discovery/src/app/services/quick-screen.service.ts
  35. 2 2
      lead-discovery/src/styles.scss

+ 474 - 0
lead-discovery/docs/DEMO_WALKTHROUGH.md

@@ -0,0 +1,474 @@
+# 犀牛急救 · AI 线索管理系统 — Demo 演示讲解文档
+
+> **项目名称**:lead-discovery(线索发现与管理平台)  
+> **技术栈**:Angular 19 + Angular Material + TypeScript  
+> **定位**:面向外贸 B2B 销售团队的 AI 智能线索管理系统 Demo  
+> **行业背景**:犀牛急救(医疗急救用品出口企业),客户群体覆盖医药分销商、工业安全供应商、政府/机构采购、品牌 OEM、电商零售商等
+
+---
+
+## 一、系统整体架构
+
+### 1.1 导航结构
+
+系统采用**顶部导航栏**设计,包含两个核心入口:
+
+| 导航项 | 路由 | 说明 |
+|--------|------|------|
+| **AI 线索获取工作台** | `/dashboard` | 日常工作主界面,AI 自动处理 + 人工验证 |
+| **线索中心** | `/list` | 全量线索的多维度查看与管理 |
+
+### 1.2 页面路由总览
+
+```
+/                          → 重定向到 /dashboard
+/dashboard                 → AI 线索获取工作台(首页)
+/list                      → 线索中心(看板/列表/统计多视图)
+/leads/:id                 → 线索详情页
+/leads/:id/bgcheck         → 背调报告页
+```
+
+### 1.3 核心数据模型
+
+- **Lead(线索)**:核心业务对象,包含公司信息、联系人、画像分类、价值等级、跟进阶段等
+- **CustomerPersona(客户画像)**:6 类客户画像(P1-P6),每类有独立的识别规则和销售策略
+- **ValueGrade(价值等级)**:S / A / B / C 四级,决定跟进优先级
+- **BackgroundCheckReport(背调报告)**:三维度深度调研报告
+
+---
+
+## 二、AI 自动获取线索的来源与方式
+
+### 2.1 线索来源渠道
+
+系统支持 **5 种线索来源**,AI 会自动从多个渠道采集并识别潜在客户:
+
+| 来源 | 标识 | 说明 |
+|------|------|------|
+| **📧 邮件** | `email` | **主要来源**。AI 每日凌晨自动扫描企业收件箱,解析所有新收到的询盘邮件,提取公司名称、联系人、产品需求等关键信息 |
+| **🤖 AI 发现** | `ai_discovery` | AI 通过对已有线索的关联分析、行业数据库匹配等方式,主动发现的潜在客户 |
+| **✍️ 手动录入** | `manual` | 销售人员在系统中手动填写公司信息创建的线索 |
+| **🏢 展会** | `exhibition` | 线下展会(如广交会、MEDICA 等)收集的名片和询盘 |
+| **🌐 平台** | `platform` | 来自 B2B 平台(如阿里巴巴国际站、环球资源等)的询盘 |
+
+### 2.2 AI 每日自动处理流程
+
+系统的核心亮点是 **AI 凌晨自动批量处理**,整个过程无需人工干预:
+
+```
+🕛 每日凌晨 00:00 — AI 自动启动批量任务
+        │
+        ├─ ① 扫描企业邮箱收件箱
+        │     └─ 获取过去 24 小时内所有新收到的邮件
+        │
+        ├─ ② 智能解析邮件内容
+        │     ├─ 提取发件人信息(姓名、邮箱、公司、国家)
+        │     ├─ 解析邮件正文(产品需求、数量、规格、MOQ)
+        │     └─ 识别附件内容(PDF 报价单、产品清单等)
+        │
+        ├─ ③ AI 快速筛选 & 分类
+        │     ├─ 关键词匹配 → 判定客户画像(P1-P6)
+        │     ├─ 多维评分 → 判定价值等级(S/A/B/C)
+        │     └─ 可信度分析 → 判断线索真实性
+        │
+        ├─ ④ 生成处理结果
+        │     ├─ 创建新线索记录(状态:待验证)
+        │     ├─ 匹配推荐产品
+        │     └─ 生成初步跟进建议
+        │
+        └─ ⑤ 通知销售人员
+              └─ 销售上班后打开系统即可看到待验证线索
+```
+
+#### 关键数据指标(以 Demo 数据为例)
+
+- **单次批量处理**:约 47 封邮件
+- **新线索发现率**:约 25%(47 封中发现 12 条有效线索)
+- **自动分类准确率**:画像分类 + 价值评级同步完成
+- **待验证数量**:约 5 条需要销售人工确认
+
+### 2.3 AI 处理的触发方式
+
+| 触发方式 | 频率 | 说明 |
+|----------|------|------|
+| **每日自动** | 每天凌晨 00:00 | 主要方式,自动扫描并处理前一天的所有新邮件 |
+| **每周汇总** | 每周一 | 对一周内的线索做汇总分析和补充分类 |
+| **每月回顾** | 每月 1 号 | 对历史线索重新评估,更新价值等级 |
+| **手动触发** | 随时 | 销售人员手动导入 EML 邮件或手动录入后,即时触发 AI 分析 |
+
+> 💡 **核心理念**:销售人员每天早上打开系统,AI 已经在凌晨完成了所有邮件的分析和分类工作。销售只需要花几分钟验证 AI 的判断结果,就可以直接开始高效跟进,**将时间花在最有价值的客户上**。
+
+---
+
+## 三、核心业务流程
+
+### 📌 完整线索处理链路
+
+```
+📧 企业邮箱(询盘邮件)/ 🏢 展会名片 / 🌐 B2B平台询盘 / ✍️ 手动录入
+    ↓
+🕛 AI 凌晨 00:00 自动批量处理(或手动即时触发)
+    ↓
+① AI 快速筛选(自动提取公司信息 + 画像分类 + 价值评级)
+    ↓
+② 销售人工验证(确认/修改 AI 的分类结果)
+    ↓
+③ 深度分析(AI 背调:公司实力 + 决策人 + 采购项目)
+    ↓
+④ 生成跟进策略(推荐产品 + 话术 + 行动计划)
+    ↓
+⑤ 进入销售管道(跟进阶段:待验证 → 待背调 → 已联系 → 已报价 → 谈判中 → 已成交)
+```
+
+---
+
+## 四、各页面详细讲解
+
+---
+
+### 🏠 页面一:AI 线索获取工作台(Dashboard)
+
+**路由**:`/dashboard`  
+**定位**:销售人员每日打开的第一个页面,核心工作界面
+
+#### 4.1 页面布局
+
+页面从上到下分为以下区域:
+
+##### ① 顶部统计卡片(Stats Row)
+三张统计卡,一目了然当日工作量:
+- **今日新线索**:AI 今日批量处理后发现的新线索数量
+- **待验证**:等待销售人员确认的线索数量
+- **已确认**:销售已验证通过的线索数量
+
+##### ② AI 批次状态(Batch Status)
+显示 AI 今日批量处理的运行情况:
+- 运行时间、处理条数、发现新线索数、待验证数
+- 状态标记:✅ 已完成 / ⏳ 运行中 / ❌ 失败
+
+##### ③ 主操作区(Tab 切换)
+
+**Tab 1:待验证线索**(核心操作区)
+
+左右两栏布局:
+
+- **左栏 — 待验证线索列表**
+  - 每条线索卡片展示:
+    - 公司名称 + 国旗
+    - AI 判定的画像标签(如 P1-医药分销商)+ 价值等级(如 S 级)
+    - AI 判断依据(如"AI判断:医药分销商 · 发送完整产品目录 + 价盘")
+    - 匹配的关键词 chips(如 `distributor`、`FDA`、`bulk order`)
+  - 三个操作按钮:
+    - ✅ **确认**:同意 AI 的分类结果
+    - ✏️ **修改**:跳转到详情页修改画像/等级
+    - ❌ **无效**:标记为低质量线索(P6)
+  - 附加按钮:查看详情、查看原始邮件
+
+- **右栏 — 辅助信息**
+  - **本周画像分布**:柱状图展示各画像类型的线索数量占比
+  - **最近活动**:时间线展示近期系统事件
+
+**Tab 2:导入线索**
+
+线索录入入口,支持两种方式:
+- **EML 邮件导入(推荐)**:
+  - 上传 .eml 文件 → 调用后端 API 解析 → 自动提取公司名称、联系人、邮箱、官网
+  - 自动提取邮件正文和附件中的产品需求(产品名、数量、MOQ、规格等)
+  - 显示导入进度条和解析状态
+- **手动录入**:
+  - 填写公司名称、联系人、邮箱、官网
+  - 点击"快速筛选并创建线索" → AI 自动分类
+
+##### ④ 收件箱(Inbox Modal)
+- 点击顶部"收件箱"按钮打开全屏弹窗
+- 左侧邮件列表 + 右侧邮件详情(类 Outlook 布局)
+- 显示邮件发件人、主题、预览、附件、关联线索等
+
+##### ⑤ 邮件详情抽屉(Email Drawer)
+- 从右侧滑出的抽屉面板
+- 展示完整邮件信息:发件人、收件人、抄送、时间、主题
+- 附件列表(支持 PDF/DOC/XLS/ZIP/图片等类型图标)
+- 邮件正文完整展示
+
+#### 4.2 演示要点
+> 💡 **讲解重点**:
+> 1. 强调"AI 每日自动处理"的概念——销售人员不需要手动逐条筛选
+> 2. 演示"确认/修改/无效"三步验证流程,说明人机协作模式
+> 3. 演示 EML 导入功能,展示 AI 自动提取信息的能力
+> 4. 展示画像分布图,说明 AI 分类的全局效果
+
+---
+
+### 📋 页面二:线索中心(List)
+
+**路由**:`/list`  
+**定位**:全量线索的多维度查看与管理中心
+
+#### 4.3 五种视图模式
+
+通过顶部工具栏的 Toggle 按钮切换:
+
+| 视图 | 图标 | 说明 |
+|------|------|------|
+| **按等级** | grade | 看板视图,按 S/A/B/C 四列排列 |
+| **按画像** | category | 看板视图,按 P1-P6 六列排列 |
+| **按阶段** | linear_scale | 看板视图,按销售管道阶段排列 |
+| **列表** | list | 表格视图,支持筛选和搜索 |
+| **统计** | bar_chart | 数据统计视图 |
+
+##### 视图 1:按等级看板(kanban-grade)
+- 四列看板:S级(立即跟进·24h内回复)、A级(3天内跟进)、B级(7天内跟进)、C级(归档观察)
+- 每张卡片展示:公司名、国旗、画像标签、分类标签、来源、联系人、跟进阶段、下次跟进日期
+- 逾期日期红色高亮
+- 未验证线索显示"待验证"标记
+
+##### 视图 2:按画像看板(kanban-persona)
+- 六列看板,对应六种客户画像:
+  - P1-医药分销商(蓝色)
+  - P2-工业安全供应商(绿色)
+  - P3-政府/机构采购(紫色)
+  - P4-品牌OEM客户(橙色)
+  - P5-电商/零售商(青色)
+  - P6-低质量/无效线索(灰色)
+
+##### 视图 3:按阶段看板(kanban-stage)
+- 六列看板,对应销售管道阶段:
+  - 待验证 → 待背调 → 已联系 → 已报价 → 谈判中 → 已成交
+
+##### 视图 4:列表视图(list)
+- 完整表格,列包含:公司名称、画像、等级、联系人、国家、跟进阶段、下次跟进、验证状态
+- 支持按画像和等级筛选
+- 支持按公司名/联系人搜索
+- 点击行跳转到线索详情
+
+##### 视图 5:统计视图(stats)
+- 四个统计卡片:
+  - **按画像分布**:横向柱状图
+  - **按等级分布**:横向柱状图 + 金额
+  - **按跟进阶段**:横向柱状图
+  - **汇总**:总线索数、已验证、待验证、S级线索数
+
+#### 4.4 演示要点
+> 💡 **讲解重点**:
+> 1. 切换不同视图,展示"同一份数据,多维度观察"的能力
+> 2. 按等级看板 → 快速找到最高优先级线索
+> 3. 按画像看板 → 了解客户构成
+> 4. 按阶段看板 → 跟踪销售管道进度
+> 5. 统计视图 → 数据驱动决策
+
+---
+
+### 📄 页面三:线索详情(Lead Detail)
+
+**路由**:`/leads/:id`  
+**定位**:单条线索的 360° 全景视图,核心决策页面
+
+#### 4.5 页面布局
+
+##### ① 头部卡片(Header Card)
+- 公司头像(画像颜色)+ 公司名称
+- 标签组:画像标签、价值等级、跟进阶段、验证状态
+- 操作按钮:启动深度分析 / 查看背调报告
+
+##### ② 左栏内容
+
+**客户画像分析卡片**(带左侧彩色边框)
+- 画像类型 + 置信度百分比
+- AI 判断依据(匹配的关键词 chips)
+- 信息获取策略(如"查看产品清单页"或"查看官网")
+- 推荐首要动作(如"发送完整产品目录 + 价盘")
+- 销售策略(如"强调供应稳定性、认证齐全、复购支持")
+
+**快速筛选结果卡片**
+- 公司信息:名称、域名、国家、主营业务、预估规模、产品相关性(高/中/低)、置信度
+- 联系人信息:姓名、来源、邮箱、创建时间
+- 需要认证:FDA、CE、ISO 等 chips
+
+**需求摘要卡片**
+- 产品列表 × 数量
+- 预估年采购额
+
+**原始邮件信息卡片**
+- 邮件主题、发件人、收件人、抄送、时间
+- 邮件正文(可展开/收起)
+- 附件列表
+
+##### ③ 右栏内容
+
+**推荐产品卡片**
+- 排名列表(1/2/3 标记)
+- 产品名称(中英文)、SKU、推荐理由、批发价
+
+**可信度 + 评分 + AI 推荐(合并卡片)**
+- 可信度等级(高/中/低)+ 信号列表(企业邮箱、完整签名块等)
+- 评分详情(可展开):需求明确度、可信度、商业规模、供需匹配 — 每项 0-100 分进度条
+- AI 推荐的下一步操作:
+  - 推荐操作描述
+  - 建议时间(如"24h内")
+  - 建议发送文件(如"产品目录"、"认证证书")
+  - 重点强调事项
+
+**销售验证卡片**
+- 未验证状态:
+  - 显示 AI 的画像分类和价值等级
+  - 提供下拉框修改画像/等级
+  - 备注输入框
+  - 三个按钮:确认 / 修改 / 无效
+- 已验证状态:显示已验证结果和备注
+
+**深度调研跟进准备包**(验证后可用)
+- 公司概况、联系人画像、产品匹配表、认证匹配、跟进策略、话术草稿
+
+**背调进度/报告**
+- 未开始:显示占位卡片,说明 AI 将执行的调研内容
+- 进行中:五阶段进度条(初始化 → 公司基础信息 → 社媒验证 → 决策人调查 → 综合评级)
+- 已完成:显示综合评级、风险等级,可跳转查看完整报告
+
+#### 4.6 演示要点
+> 💡 **讲解重点**:
+> 1. 展示 AI 画像分析的完整逻辑链:关键词 → 画像分类 → 策略推荐
+> 2. 演示销售验证流程:确认 → 解锁深度分析
+> 3. 演示启动深度分析的动画进度(五阶段背调)
+> 4. 展示推荐产品和 AI 推荐操作,说明系统如何辅助销售决策
+
+---
+
+### 🔍 页面四:背调报告(BgCheck)
+
+**路由**:`/leads/:id/bgcheck`  
+**定位**:深度背调的完整报告展示
+
+#### 4.7 页面布局
+
+##### 头部
+- 公司名称 + "背调报告"标题
+- 调查时间、调查员、调查等级
+
+##### 左栏 — 三维度调研
+
+**概览**
+- 公司基本信息(名称、官网、国家、来源渠道)
+- 三项完成度进度条:公司信息、决策人、采购项目
+- 综合结论
+
+**第一维度:公司实力**
+- 基础信息:成立年份、员工数、地址
+- 财务状况:营收
+- 产品线 chips
+
+**第二维度:决策人**
+- 联系人详情:姓名、职位、邮箱、电话、决策权限
+- 背景信息
+
+**第三维度:采购项目**
+- 需求详情、订单类型、用途
+- 预算金额、价格敏感度
+
+##### 右栏 — 评估与策略
+
+**综合评级**
+- 圆形分数展示(颜色区分高/中/低)
+- 等级标签
+
+**风险评估**
+- 风险等级(低/中/高)+ 颜色标记
+- 风险因素列表
+
+**销售策略**
+- 跟进策略描述
+- 行动建议列表
+- 时间线
+
+#### 4.8 演示要点
+> 💡 **讲解重点**:
+> 1. 强调三维度调研的全面性:公司 + 决策人 + 项目
+> 2. 展示综合评分和风险评估,说明如何量化客户价值
+> 3. 展示 AI 生成的销售策略和行动建议
+
+---
+
+## 五、数据模型详解
+
+### 5.1 六大客户画像(Persona)
+
+| ID | 名称 | 颜色 | 识别关键词示例 | 信息获取策略 | 首要动作 | 销售策略 |
+|----|------|------|---------------|-------------|---------|---------|
+| **P1** | 医药分销商 | 🔵 蓝色 | distributor, wholesale, FDA, bulk order | 查看产品清单页 | 发送完整产品目录+价盘 | 强调供应稳定性、认证齐全、复购支持 |
+| **P2** | 工业安全供应商 | 🟢 绿色 | workplace safety, OSHA, PPE, ANSI | 查看产品清单页 | 发送 OSHA 合规产品线 | 强调合规认证、定制标签、壁挂方案 |
+| **P3** | 政府/机构采购 | 🟣 紫色 | tender, procurement, government, RFP | 查看官网 | 准备资质文件包 | 强调资质齐全、大批量交付、投标经验 |
+| **P4** | 品牌OEM客户 | 🟠 橙色 | OEM, private label, custom branding | 查看官网 | 发送 OEM 案例+定制能力介绍 | 强调定制能力、打样速度、小批量起订 |
+| **P5** | 电商/零售商 | 🔵 青色 | amazon, shopify, FBA, dropship | 查看产品清单页 | 发送爆款推荐+利润率分析 | 强调高利润率、FBA就绪、产品图片支持 |
+| **P6** | 低质量/无效线索 | ⚪ 灰色 | wholesale everything, lowest price, free sample | 不做深入调研 | 自动归档 | 定期群发营销邮件,不投入人工 |
+
+### 5.2 价值等级(Value Grade)
+
+| 等级 | 含义 | 跟进要求 |
+|------|------|---------|
+| **S** | 最高优先级 | 立即跟进 · 24h 内回复 |
+| **A** | 高优先级 | 3 天内跟进 |
+| **B** | 中优先级 | 7 天内跟进 |
+| **C** | 低优先级 | 归档观察 |
+
+### 5.3 销售管道阶段(Follow-up Stage)
+
+```
+待验证 → 待背调 → 已联系 → 已报价 → 谈判中 → 已成交
+```
+
+---
+
+## 六、演示建议流程(推荐顺序)
+
+### 🎯 推荐 Demo 路线(约 15-20 分钟)
+
+#### Step 1:Dashboard 概览(3 分钟)
+1. 打开系统,展示顶部导航和品牌标识
+2. 讲解三张统计卡片:今日新线索 / 待验证 / 已确认
+3. 讲解 AI 批次状态:"每天自动运行,无需人工干预"
+
+#### Step 2:验证线索(3 分钟)
+1. 在"待验证线索"列表中选一条 S 级线索
+2. 展示 AI 的判断依据:画像标签 + 关键词
+3. 点击"确认"按钮 → 展示确认成功提示
+4. 再选一条,点击"无效" → 展示标记为 P6
+
+#### Step 3:导入新线索(3 分钟)
+1. 切换到"导入线索" Tab
+2. 演示 EML 文件导入(或手动填写公司名称)
+3. 点击"快速筛选并创建线索"
+4. 展示 AI 筛选结果卡片
+
+#### Step 4:线索中心多视图(3 分钟)
+1. 导航到"线索中心"
+2. 依次切换五种视图:按等级 → 按画像 → 按阶段 → 列表 → 统计
+3. 重点讲解按等级看板:"S 级线索需要 24 小时内回复"
+
+#### Step 5:线索详情(3 分钟)
+1. 点击一条线索进入详情页
+2. 展示左栏:画像分析 + 快速筛选结果 + 原始邮件
+3. 展示右栏:推荐产品 + 评分 + AI 推荐操作
+4. 演示销售验证流程
+
+#### Step 6:深度分析 & 背调(3 分钟)
+1. 在已验证线索上点击"启动深度分析"
+2. 展示五阶段进度动画
+3. 完成后跳转查看背调报告
+4. 展示三维度报告 + 综合评级 + 销售策略
+
+---
+
+## 七、核心价值总结
+
+| 价值点 | 说明 |
+|--------|------|
+| **AI 自动分类** | 基于关键词和规则,自动将线索归类到 6 种客户画像 |
+| **人机协作验证** | AI 提供初步判断,销售人员确认/修改,持续优化 |
+| **差异化销售策略** | 不同画像有不同的信息获取策略、首要动作和销售话术 |
+| **深度背调** | 三维度(公司+决策人+项目)全面调研,量化评分 |
+| **多维度线索管理** | 看板/列表/统计多视图,灵活管理销售管道 |
+| **邮件智能解析** | 自动提取 EML 邮件中的公司信息和产品需求 |
+
+---
+
+*文档生成时间:2026-02-12*
+

+ 234 - 0
lead-discovery/docs/IMPLEMENTATION_SUMMARY.md

@@ -0,0 +1,234 @@
+# 方案一(轻量集成)实现总结
+
+> 按照 `BGCHECK_INTEGRATION_ANALYSIS.md` 中的方案一,已完成背调功能的轻量级集成。
+
+---
+
+## ✅ 已完成的工作
+
+### 1. 数据模型扩展
+
+**文件**: `src/app/models/lead.model.ts`
+
+- ✅ 扩展 `Lead` 接口,新增背调相关字段:
+  - `hasBackgroundCheck: boolean` - 是否已完成背调
+  - `backgroundCheckAt?: Date` - 背调完成时间
+  - `backgroundCheckReport?: BackgroundCheckReport` - 背调报告数据
+
+- ✅ 新增 `BackgroundCheckReport` 接口(三维报告结构):
+  - `meta` - 元数据(日期、调查员、方法、等级)
+  - `overview` - 概览(公司名、官网、国家、完成度)
+  - `dimension1` - 第一维度:公司实力(基础信息、财务状况、社媒、产品线)
+  - `dimension2` - 第二维度:决策人(联系人信息、背景、沟通风格)
+  - `dimension3` - 第三维度:采购项目(需求、预算、时间、竞争)
+  - `rating` - 综合评级(总分、等级)
+  - `risk` - 风险评估(等级、风险因素)
+  - `strategy` - 销售策略(方法、行动建议、时间线)
+
+### 2. 背调进度组件
+
+**文件**: `src/app/components/bgcheck-progress/bgcheck-progress.component.ts`
+
+- ✅ 创建 `BgCheckProgressComponent` 组件
+- ✅ 支持5阶段进度展示:
+  1. 初始化数据通过
+  2. 公司基础信息
+  3. 社媒平台验证
+  4. 决策人调查
+  5. 综合评级与策略
+- ✅ 实时显示整体进度百分比
+- ✅ 阶段状态:waiting / processing / completed / error
+- ✅ 动画效果(处理中阶段有旋转动画)
+
+### 3. 背调报告展示页面
+
+**文件**: `src/app/pages/bgcheck/bgcheck.component.ts`
+
+- ✅ 创建独立的背调报告查看页面
+- ✅ 路由:`/leads/:id/bgcheck`
+- ✅ 完整展示三维报告的所有维度:
+  - 概览卡片(完成度进度条)
+  - 第一维度:公司实力(基础信息、财务状况、产品线)
+  - 第二维度:决策人(联系人信息、背景)
+  - 第三维度:采购项目(需求、预算、时间)
+  - 综合评级(评分圆圈、等级标签)
+  - 风险评估(风险等级、风险因素)
+  - 销售策略(跟进策略、行动建议、时间线)
+- ✅ 响应式布局(支持移动端)
+
+### 4. 启动深度分析功能
+
+**文件**: `src/app/pages/lead-detail/lead-detail.component.ts`
+
+- ✅ 实现 `startDeepAnalysis()` 方法
+- ✅ 模拟5阶段背调流程(每阶段有延迟,模拟真实API调用)
+- ✅ 实时更新进度和阶段状态
+- ✅ 背调完成后生成 Mock 报告并保存到 Lead 对象
+- ✅ 支持查看已有背调报告(跳转到报告页面)
+- ✅ UI 状态管理:
+  - 背调进行中:显示进度组件
+  - 背调已完成:显示报告摘要卡片
+  - 未开始:显示启动按钮
+
+### 5. Mock 数据生成
+
+**文件**: `src/app/services/mock-data.service.ts`
+
+- ✅ 新增 `generateBackgroundCheckReport()` 方法
+- ✅ 根据线索信息(画像、等级、国家等)智能生成背调报告
+- ✅ 报告内容差异化:
+  - 根据 `persona` 生成不同的产品线、联系人职位、沟通风格
+  - 根据 `valueGrade` 生成不同的规模、营收、社媒粉丝数
+  - 根据 `country` 生成对应的认证要求
+- ✅ 所有字段都有合理的 Mock 数据填充
+
+### 6. 路由配置
+
+**文件**: `src/app/app.routes.ts`
+
+- ✅ 新增路由:`/leads/:id/bgcheck`
+- ✅ 懒加载背调报告页面组件
+
+---
+
+## 📋 功能流程
+
+```
+线索详情页 (/leads/:id)
+    ↓
+点击"启动深度分析"按钮
+    ↓
+显示背调进度组件(5阶段)
+    ├─ 阶段1: 初始化数据通过 (20%)
+    ├─ 阶段2: 公司基础信息 (40%)
+    ├─ 阶段3: 社媒平台验证 (60%)
+    ├─ 阶段4: 决策人调查 (80%)
+    └─ 阶段5: 综合评级与策略 (100%)
+    ↓
+生成背调报告(Mock数据)
+    ↓
+保存到 Lead 对象
+    ↓
+显示报告摘要卡片
+    ↓
+点击"查看完整报告" → 跳转到 /leads/:id/bgcheck
+    ↓
+展示完整的三维背调报告
+```
+
+---
+
+## 🎨 UI 展示
+
+### 1. 背调进行中
+- 显示5阶段进度列表
+- 每个阶段有图标、名称、描述、状态
+- 底部有整体进度条
+- 处理中阶段有旋转动画
+
+### 2. 背调已完成
+- 显示报告摘要卡片
+- 包含:完成时间、综合评级、风险等级
+- "查看完整报告"按钮
+
+### 3. 背调报告页面
+- 左侧:概览、公司实力、决策人、采购项目
+- 右侧:综合评级、风险评估、销售策略
+- 所有数据以卡片形式展示,清晰易读
+
+---
+
+## 🔧 技术实现
+
+### 组件结构
+```
+src/app/
+├── components/
+│   └── bgcheck-progress/
+│       └── bgcheck-progress.component.ts  (进度组件)
+├── pages/
+│   ├── lead-detail/
+│   │   └── lead-detail.component.ts        (启动背调)
+│   └── bgcheck/
+│       └── bgcheck.component.ts            (报告展示)
+├── models/
+│   └── lead.model.ts                      (数据模型)
+└── services/
+    └── mock-data.service.ts               (Mock数据生成)
+```
+
+### 关键方法
+
+1. **`startDeepAnalysis()`** - 启动背调流程
+   - 模拟5阶段异步执行
+   - 每阶段更新状态和进度
+   - 完成后生成报告
+
+2. **`generateBackgroundCheckReport(lead)`** - 生成Mock报告
+   - 根据线索信息智能生成
+   - 差异化内容(根据画像、等级)
+   - 返回完整的 `BackgroundCheckReport` 对象
+
+---
+
+## 📝 使用说明
+
+### 测试流程
+
+1. **访问线索详情页**
+   ```
+   http://localhost:4200/leads/l1
+   ```
+
+2. **点击"启动深度分析"按钮**
+   - 观察5阶段进度展示
+   - 等待约6秒(模拟真实API调用)
+
+3. **查看报告摘要**
+   - 背调完成后显示摘要卡片
+   - 包含评级、风险、完成时间
+
+4. **查看完整报告**
+   - 点击"查看完整报告"按钮
+   - 跳转到 `/leads/l1/bgcheck`
+   - 查看完整的三维报告
+
+### 已完成的线索
+
+当前 Mock 数据中,所有线索的 `hasBackgroundCheck` 默认为 `false`,需要手动触发背调。
+
+---
+
+## 🎯 下一步优化建议
+
+### Phase 2(可选)
+- [ ] 为部分线索预设背调报告(展示已完成状态)
+- [ ] 优化报告生成逻辑(更真实的Mock数据)
+- [ ] 添加报告导出功能(PDF)
+
+### Phase 3(真实服务对接)
+- [ ] 对接 Parse Server 后端
+- [ ] 集成真实的 `LeadBackgroundCheckService`
+- [ ] 集成 TikHub API(社媒数据)
+- [ ] 集成 Firecrawl(官网爬取)
+
+---
+
+## ✅ 验收标准
+
+- [x] 可以启动背调流程
+- [x] 5阶段进度正确展示
+- [x] 背调完成后生成报告
+- [x] 可以查看完整报告
+- [x] 报告内容符合三维结构
+- [x] 数据根据线索信息差异化生成
+- [x] UI 美观、响应式
+- [x] 无 TypeScript 编译错误
+
+---
+
+*实现时间:2026-02-12*  
+*方案:轻量集成(DEMO级别)*  
+*状态:✅ 已完成*
+
+

+ 141 - 0
lead-discovery/docs/MEETING_ACTION_ITEMS_0213.md

@@ -0,0 +1,141 @@
+# 会议纪要 & 行动计划
+
+**会议日期**:2026-02-13  
+**主题**:Lead Discovery 系统 MVP 闭环优化  
+**目标**:打通"输入信息 → 用户画像 → 是否值得挖掘 → 深度调研 → 生成触达邮件"的完整小闭环
+
+---
+
+## 一、会议核心结论
+
+> **体验目标**:用户输入一个名字/邮箱,马上就能看到——这个客户是什么画像、什么类型、下一步该不该挖掘、挖掘之后能看到什么信息、最终生成一封可发送的首次触达邮件。
+
+---
+
+## 二、需要做的事项(按优先级排序)
+
+### 🔴 P0 — 用户画像快速定位
+
+| 项目 | 说明 |
+|------|------|
+| **当前状态** | 已有 `screenManual(companyName, email)` 手动筛查入口,可输入公司名+邮箱进行快速筛选,返回客户类型、价值评级、画像分类 |
+| **需要优化** | ① 输入更轻量化:只需一个邮箱或公司名即可触发(目前需要同时填公司名+邮箱)<br>② 画像结果更直观:第一眼就能看到画像类型(如"医疗分销商"/"OEM品牌商"等)、价值评级(S/A/B/C)、是否值得深入挖掘的明确建议<br>③ 响应速度优化:确保"10秒出结果"的体验 |
+| **验收标准** | 输入一个邮箱地址 → 立即返回:画像类型 + 价值评级 + "是否值得深入挖掘"的明确结论 |
+
+---
+
+### 🔴 P0 — 线索唯一性识别 & 去重关联
+
+| 项目 | 说明 |
+|------|------|
+| **当前问题** | 每次筛选都会 `id: 'l-' + Date.now()` 创建全新线索,同一客户多次筛选会产生多条独立记录,无法关联 |
+| **需要实现** | ① 建立唯一性识别机制:以**邮箱域名**(公司维度)或**邮箱地址**(联系人维度)作为去重键<br>② 创建线索前先查重:如果已存在同域名/同邮箱的线索,弹出提示"该客户已存在调查记录",展示历史信息<br>③ 关联历史记录:同一客户的多次分析结果应该能串联查看,形成客户档案时间线 |
+| **验收标准** | 再次分析已调查过的客户时 → 系统提示"已有记录" → 可查看历史画像和分析结果 → 支持更新而非重复创建 |
+
+---
+
+### 🟡 P1 — "是否值得深入挖掘"决策支持
+
+| 项目 | 说明 |
+|------|------|
+| **当前状态** | 快速筛选后有 `valueGrade`(S/A/B/C)和 `recommendedAction`,但缺少明确的"挖掘/不挖掘"决策引导 |
+| **需要优化** | ① 在画像结果卡片上增加醒目的**决策标签**:如 "🟢 强烈建议深入挖掘" / "🟡 可选择性挖掘" / "🔴 建议归档"<br>② 给出决策依据摘要(如:"该客户需求明确、规模大、认证匹配")<br>③ 一键触发深度调研按钮 |
+| **验收标准** | 画像结果中有明确的挖掘建议 + 依据说明 + 一键进入深度调研的入口 |
+
+---
+
+### 🟡 P1 — 深度调研后生成首次触达邮件
+
+| 项目 | 说明 |
+|------|------|
+| **当前状态** | 深度调研组件 `DeepResearchPackageComponent` 已有 `suggestedScript`(建议话术),但不是基于真实邮件模板生成的 |
+| **需要实现** | ① **分析现有邮件数据中的首次打招呼邮件**:从 `mock-data.service.ts` 中的6封样本邮件和业务真实数据中,提取首次触达邮件的格式和模板特征<br>② **基于产品信息 + 客户画像生成个性化邮件**:<br>&nbsp;&nbsp;- 根据客户画像类型(分销商/品牌商/政府采购等)选择对应话术风格<br>&nbsp;&nbsp;- 根据匹配的产品信息填充具体产品推荐<br>&nbsp;&nbsp;- 根据价值评级调整邮件紧迫度和行动号召<br>③ **邮件可编辑、可预览、可复制** |
+| **验收标准** | 深度调研完成后 → 自动生成一封符合外贸首次触达规范的英文邮件 → 内容包含产品推荐、公司优势、行动号召 → 可编辑后一键复制 |
+
+---
+
+## 三、完整闭环流程(优化后)
+
+```
+导入邮件
+       │
+       ▼
+┌─────────────────────┐
+│  ① 快速画像           │  → 画像类型 + 价值评级
+│     唯一性查重         │  → 已存在?展示历史记录
+└──────────┬──────────┘
+           │
+           ▼
+┌─────────────────────┐
+│  ② 挖掘决策           │  → 🟢建议挖掘 / 🟡可选 / 🔴归档
+│     决策依据说明        │  → 需求明确度、规模、匹配度
+└──────────┬──────────┘
+           │ (用户点击"深入挖掘")
+           ▼
+┌─────────────────────┐
+│  ③ 深度调研           │  → 公司详情、产品匹配、认证核查
+│     信息入库关联        │  → 更新客户档案,关联历史线索
+└──────────┬──────────┘
+           │
+           ▼
+┌─────────────────────┐
+│  ④ 生成触达邮件        │  → 基于画像+产品的个性化邮件
+│     可编辑/可复制       │  → 参考真实邮件模板格式
+└─────────────────────┘
+```
+
+---
+
+## 四、具体开发任务拆解
+
+### Task 1:输入简化 & 画像快速输出
+- [ ] 支持仅输入邮箱即可触发筛查(从邮箱域名反推公司信息)
+- [ ] 画像结果卡片重新设计,突出:画像类型图标+名称、价值评级徽章、核心指标(规模/匹配度/可信度)
+- [ ] 加载动画优化,体现"AI分析中"的感知
+
+### Task 2:线索去重 & 唯一性关联
+- [ ] 在 `Lead` 模型中增加唯一标识字段(如 `domainKey`、`emailKey`)
+- [ ] `QuickScreenService.createLeadFromScreen()` 中增加查重逻辑
+- [ ] 查重命中时的 UI 提示(弹窗/卡片提示"该客户已有 X 条历史记录")
+- [ ] 历史记录查看入口(跳转到已有线索详情)
+
+### Task 3:挖掘决策标签
+- [ ] `ScreenResultCard` 组件增加决策标签(基于 valueGrade 映射)
+- [ ] 增加决策依据摘要文案(基于 scores 五维评分生成)
+- [ ] "深入挖掘"按钮直接跳转深度调研
+
+### Task 4:首次触达邮件生成
+- [ ] 分析样本邮件,提炼首次触达邮件模板(分画像类型)
+- [ ] 在 `DeepResearchPackageComponent` 或新建服务中实现邮件生成逻辑
+- [ ] 邮件编辑器 UI(预览/编辑/复制三种模式)
+- [ ] 根据画像类型 + 匹配产品 + 客户信息动态填充邮件内容
+
+---
+
+## 五、当前代码现状参考
+
+| 模块 | 文件 | 现状 |
+|------|------|------|
+| 数据模型 | `models/lead.model.ts` | ✅ Lead、QuickScreenResult、Email 等接口已定义完善 |
+| 快速筛查服务 | `services/quick-screen.service.ts` | ✅ screenManual() 已实现,但无去重逻辑 |
+| Dashboard 页面 | `pages/dashboard/` | ✅ 手动录入表单已有,但需简化输入 |
+| 筛选结果卡片 | `components/screen-result-card/` | ✅ 已有,需增加决策标签 |
+| 深度调研组件 | `components/deep-research-package/` | ✅ 已有框架,suggestedScript 需改为邮件模板 |
+| 线索详情页 | `pages/lead-detail/` | ✅ 已有报价方案生成,可参考其模板逻辑 |
+| Mock 数据 | `services/mock-data.service.ts` | ✅ 6封样本邮件可用于分析邮件模板 |
+
+---
+
+## 六、预期成果
+
+完成以上优化后,演示流程将是:
+
+1. **输入**:业务员输入 `alex@wildgear.de`
+2. **10秒后**:系统显示"WildGear GmbH | 🏷️ OEM品牌商 | ⭐ A级-3天内跟进 | 🟢 建议深入挖掘"
+3. **查重提示**:如果之前分析过,显示"该客户已有1条历史记录,上次分析于2天前"
+4. **点击深入挖掘**:3分钟后出详细报告(公司背景、产品匹配、认证核查)
+5. **自动生成邮件**:一封个性化的英文首次触达邮件,包含推荐产品、公司优势、会议邀约
+6. **一键复制**:业务员复制邮件内容,粘贴到邮箱发送
+
+**这就是一个完整的"小闭环"。**
+

+ 853 - 0
lead-discovery/docs/MODIFICATION_PLAN_0222.md

@@ -0,0 +1,853 @@
+# Lead Discovery 系统修改方案
+
+**日期**:2026-02-22  
+**目标**:围绕"两轮背调"核心模块,解决批量处理、主体唯一性、价值确认、产品匹配四大问题
+
+---
+
+## 一、需求分析与目标
+
+### 核心诉求
+
+| # | 问题域 | 痛点 | 目标 |
+|---|--------|------|------|
+| 1 | **慢/批量** | 当前只能逐条处理线索,无法截图/名片批量导入筛查 | 支持批量输入(截图OCR、名片、CSV、多行粘贴),一次提交多条线索 |
+| 2 | **主体唯一性** | 同一公司不同邮箱/域名会产生多条独立线索,无法归并 | 建立"主体识别"机制,通过域名+公司名归纳到同一主体 |
+| 3 | **价值确认** | 缺少分轮次的用户确认环节,AI判断后直接进入下一步 | 每轮分析完给出列表,让用户逐条确认后再进入下一步 |
+| 4 | **产品匹配** | 推荐规则固定,销售无法修改标注;不同画像缺少差异化价值点 | 产品匹配可人工修改和标注,不同画像给出差异化建议 |
+
+### 核心架构:两轮背调
+
+将背调功能封装为**独立核心模块**,分两轮执行:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│                    两轮背调核心模块                                │
+│                                                                 │
+│  ┌─────────────────────┐      ┌───────────────────────────┐    │
+│  │  第一轮:初步筛查      │      │  第二轮:深度背调            │    │
+│  │  (Quick Screen)      │      │  (Deep Investigation)     │    │
+│  │                      │      │                           │    │
+│  │  · 批量输入解析        │      │  · 公司实力调研              │    │
+│  │  · 主体识别 & 去重     │ ──→  │  · 决策人调查               │    │
+│  │  · 画像初判           │ 用户  │  · 采购项目分析              │    │
+│  │  · 价值评估           │ 确认  │  · 产品匹配推荐              │    │
+│  │  · 输出待确认列表      │ 后    │  · 跟进策略 & 触达邮件       │    │
+│  └─────────────────────┘      └───────────────────────────┘    │
+│                                                                 │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 二、模块拆解与修改内容
+
+### 模块 1:批量输入处理器(BatchInputProcessor)
+
+**新增文件**:`services/batch-input.service.ts`
+
+#### 1.1 支持的输入方式
+
+| 输入方式 | 说明 | 解析结果 |
+|----------|------|----------|
+| 截图 OCR | 上传截图(聊天记录、网页截图等),OCR 提取文字后解析 | 提取公司名、联系人、邮箱、电话 |
+| 名片拍照 | 上传名片照片,OCR + 结构化解析 | 提取姓名、职位、公司、邮箱、电话、地址 |
+| 多行粘贴 | 文本框粘贴多行"公司名 + 邮箱"或纯邮箱列表 | 按行拆分,每行解析为一条线索 |
+| CSV 批量 | 上传 CSV/Excel 文件 | 按列映射字段 |
+| 多邮箱输入 | 逗号/换行分隔的多个邮箱地址 | 批量域名解析 |
+
+#### 1.2 接口设计
+
+```typescript
+// services/batch-input.service.ts
+
+export interface BatchInputItem {
+  rawText: string;              // 原始输入文本
+  parsedCompanyName?: string;   // 解析出的公司名
+  parsedEmail?: string;         // 解析出的邮箱
+  parsedDomain?: string;        // 解析出的域名
+  parsedContactName?: string;   // 解析出的联系人
+  parsedPhone?: string;         // 解析出的电话
+  parsedCountry?: string;       // 解析出的国家
+  parseConfidence: number;      // 解析置信度 0-100
+  parseSource: 'ocr' | 'text' | 'csv' | 'email'; // 解析来源
+}
+
+export interface BatchProcessResult {
+  totalInput: number;           // 总输入条数
+  successParsed: number;        // 成功解析条数
+  failedParsed: number;         // 解析失败条数
+  duplicatesFound: number;      // 发现重复条数
+  items: BatchInputItem[];      // 解析结果列表
+  entities: EntityGroup[];      // 归并后的主体列表
+}
+```
+
+#### 1.3 对现有代码的修改
+
+**文件**:`pages/dashboard/dashboard.component.ts`
+
+```
+修改内容:
+1. 新增"批量导入"Tab(与现有"导入线索"Tab 并列)
+2. 批量导入区域包含:
+   - 截图/名片上传区(支持拖拽、多文件)
+   - 多行文本粘贴区
+   - CSV 文件上传
+3. 解析后展示"待确认列表"(表格形式)
+4. 支持逐条编辑、删除、合并
+5. 确认后批量执行第一轮筛查
+```
+
+**文件**:`pages/dashboard/dashboard.component.html`
+
+```
+修改内容:
+1. 新增 <mat-tab label="批量导入"> 区块
+2. 上传区域 UI(拖拽区 + 按钮组)
+3. 解析结果预览表格
+4. 批量操作工具栏(全选、删除、合并、执行筛查)
+```
+
+---
+
+### 模块 2:主体识别与去重引擎(EntityResolver)
+
+**新增文件**:`services/entity-resolver.service.ts`
+
+#### 2.1 核心逻辑
+
+```
+输入线索信息
+    ↓
+┌──────────────────────────────────────────┐
+│           主体识别流程                      │
+│                                          │
+│  Step 1: 域名标准化                       │
+│    alex@wildgear.de → wildgear.de        │
+│    info@wild-gear.com → wildgear.com     │
+│    免费邮箱(gmail等) → 降级为联系人维度    │
+│                                          │
+│  Step 2: 公司名标准化                     │
+│    "WildGear GmbH" → "wildgear"          │
+│    去除后缀: Ltd/Inc/GmbH/Co./LLC        │
+│    去除空格/标点/大小写差异               │
+│                                          │
+│  Step 3: 主体匹配                        │
+│    域名完全匹配 → 同一主体 (置信度 95%)   │
+│    域名相似度 > 0.8 → 疑似同一主体 (70%) │
+│    公司名相似度 > 0.85 → 疑似 (75%)      │
+│    域名+公司名都匹配 → 确定 (99%)        │
+│                                          │
+│  Step 4: 输出                            │
+│    → EntityGroup(归并主体)              │
+│    → 每个主体下挂载多个线索/联系人         │
+└──────────────────────────────────────────┘
+```
+
+#### 2.2 接口设计
+
+```typescript
+// services/entity-resolver.service.ts
+
+export interface EntityGroup {
+  entityId: string;                     // 主体 ID
+  primaryDomain: string;               // 主域名
+  primaryCompanyName: string;          // 主公司名
+  normalizedDomain: string;            // 标准化域名
+  normalizedCompanyName: string;       // 标准化公司名
+  confidence: number;                  // 归并置信度
+  leads: Lead[];                       // 该主体下所有线索
+  contacts: ContactInfo[];             // 该主体下所有联系人
+  duplicateWarnings: string[];         // 重复提醒
+  existingLeadIds: string[];           // 已有线索ID(用于去重提示)
+  isNew: boolean;                      // 是否新主体
+  mergeHistory: MergeRecord[];         // 合并历史
+}
+
+export interface ContactInfo {
+  name: string;
+  email: string;
+  phone?: string;
+  title?: string;
+  source: string;
+  firstSeenAt: Date;
+}
+
+export interface MergeRecord {
+  mergedAt: Date;
+  fromLeadId: string;
+  reason: string;
+}
+```
+
+#### 2.3 对现有代码的修改
+
+**文件**:`models/lead.model.ts`
+
+```
+修改内容:
+1. Lead 接口新增字段:
+   - entityId: string               // 所属主体ID
+   - domainKey: string              // 域名去重键
+   - emailKey: string               // 邮箱去重键
+   - relatedLeadIds: string[]       // 关联线索ID列表
+   - mergedFrom?: string            // 如果是合并来的,来源线索ID
+   - isEntityPrimary: boolean       // 是否为该主体的主线索
+
+2. 新增 EntityProfile 接口:
+   - 主体画像(汇总该主体下所有线索信息)
+   - 联系人列表
+   - 互动历史时间线
+```
+
+**文件**:`services/quick-screen.service.ts`
+
+```
+修改内容:
+1. createLeadFromScreen() 方法增加查重逻辑:
+   - 调用 EntityResolver 检查是否已有同主体线索
+   - 已有 → 返回 { isDuplicate: true, existingEntity: EntityGroup }
+   - 新主体 → 正常创建并注册到 EntityResolver
+2. 新增 screenBatch() 方法:
+   - 接收 BatchInputItem[] 批量输入
+   - 并行执行筛查
+   - 自动归并到主体
+   - 返回按主体分组的结果列表
+```
+
+**文件**:`services/mock-data.service.ts`
+
+```
+修改内容:
+1. 现有 mockLeads 增加 entityId / domainKey / emailKey 字段
+2. 增加主体索引 Map(域名 → entityId)
+3. 模拟一些"同公司多联系人"的数据,验证去重效果
+```
+
+---
+
+### 模块 3:两轮背调核心模块(TwoRoundBgCheck)
+
+**新增文件**:`services/two-round-bgcheck.service.ts`
+
+#### 3.1 第一轮:初步筛查(QuickScreen)
+
+这一轮是现有 `QuickScreenService` 的增强版,改造点:
+
+```
+现有流程:
+  输入 → AI筛查 → 直接创建线索
+
+改造后:
+  批量输入 → 解析 → 主体去重 → AI筛查 → 输出"待确认列表" → 用户确认
+```
+
+**用户确认列表**设计:
+
+```typescript
+// 第一轮输出:待确认列表
+export interface Round1Result {
+  entities: Round1EntityResult[];  // 按主体分组的结果
+  summary: {
+    totalEntities: number;
+    newEntities: number;
+    existingEntities: number;
+    recommendExplore: number;     // 建议深入挖掘数量
+    recommendArchive: number;     // 建议归档数量
+  };
+}
+
+export interface Round1EntityResult {
+  entity: EntityGroup;
+  screenResult: QuickScreenResult;
+  
+  // 价值判断(差异化)
+  valueAssessment: {
+    decision: 'strongly_recommend' | 'optional' | 'archive';
+    decisionLabel: string;        // "强烈建议深入挖掘" / "可选择性挖掘" / "建议归档"
+    decisionColor: string;        // green / yellow / red
+    reasons: string[];            // 判断依据
+    valueHighlights: ValueHighlight[];  // 差异化价值亮点
+  };
+  
+  // 用户操作
+  userAction?: 'confirmed' | 'modified' | 'rejected' | 'pending';
+  userNotes?: string;
+}
+
+// 差异化价值亮点(根据不同画像类型)
+export interface ValueHighlight {
+  type: 'product_info' | 'market_potential' | 'certification' | 'scale' | 'urgency' | 'custom';
+  icon: string;
+  label: string;
+  detail: string;
+  importance: 'high' | 'medium' | 'low';
+}
+```
+
+**差异化价值点示例**:
+
+| 画像类型 | 有商品信息的价值点 | 无商品信息但有潜在价值的 |
+|----------|-------------------|----------------------|
+| P1 医药分销商 | "明确列出3款产品需求,年采购额估$50K+" | "大型分销渠道,覆盖200+药房,潜在年采购额高" |
+| P2 工业安全 | "指定OSHA合规产品,急需30天内交付" | "建筑行业安全合规需求增长,潜在长期客户" |
+| P3 政府采购 | "招标文件明确,预算已批复" | "政府机构年度采购计划中,需要提前布局" |
+| P4 品牌OEM | "提供了Pantone色号和品牌VI,定制需求明确" | "新兴户外品牌,增长迅速,OEM需求即将释放" |
+| P5 电商零售 | "Amazon店铺月销500+,明确要FBA发货" | "Shopify新店,品类匹配度高,教育引导后可转化" |
+| P6 低质量 | — | — |
+
+#### 3.2 第二轮:深度背调(DeepInvestigation)
+
+第二轮在用户确认第一轮结果后启动,批量执行:
+
+```
+用户在第一轮确认列表中勾选要深入的主体
+    ↓
+批量启动深度背调(可并行处理多个主体)
+    ↓
+┌──────────────────────────────────────┐
+│  深度背调 5 阶段(每个主体独立进度)    │
+│                                      │
+│  ① 初始化 & 数据验证                  │
+│  ② 公司实力调研                       │
+│     - 官网爬取 + 企业数据库            │
+│     - 财务/规模信息                    │
+│  ③ 社媒 & 网络验证                    │
+│     - LinkedIn/Twitter/Instagram      │
+│     - 行业论坛/展会记录               │
+│  ④ 决策人调查                         │
+│     - 关键联系人识别                   │
+│     - 角色/职级/决策权限               │
+│  ⑤ 综合评级 & 策略生成                │
+│     - 三维评分                        │
+│     - 产品匹配推荐(可编辑)           │
+│     - 跟进策略 + 触达邮件              │
+└──────────────────────────────────────┘
+    ↓
+输出"第二轮确认列表"
+    ↓
+用户确认/修改产品推荐和跟进策略
+```
+
+**批量进度设计**:
+
+```typescript
+export interface BatchBgCheckProgress {
+  totalEntities: number;
+  completedEntities: number;
+  currentEntity?: string;       // 当前正在处理的主体名称
+  entityProgresses: Map<string, EntityBgCheckProgress>;
+  overallProgress: number;      // 总体进度 0-100
+  startedAt: Date;
+  estimatedCompletion?: Date;
+}
+
+export interface EntityBgCheckProgress {
+  entityId: string;
+  companyName: string;
+  stages: BgCheckStage[];       // 复用现有5阶段
+  currentStage: number;
+  progress: number;             // 该主体的进度 0-100
+  status: 'queued' | 'processing' | 'completed' | 'error';
+  error?: string;
+}
+```
+
+#### 3.3 对现有代码的修改
+
+**文件**:`pages/lead-detail/lead-detail.component.ts`
+
+```
+修改内容:
+1. startDeepAnalysis() 方法重构:
+   - 改为调用 TwoRoundBgCheckService
+   - 支持从第一轮确认结果直接进入第二轮
+2. 新增批量背调入口(从 Dashboard 也可触发)
+3. 背调进度展示改为支持批量进度视图
+```
+
+**文件**:`components/bgcheck-progress/bgcheck-progress.component.ts`
+
+```
+修改内容:
+1. 新增批量模式:显示多个主体的并行进度
+2. 每个主体可展开/折叠查看5阶段详情
+3. 整体进度条 + 单体进度条
+```
+
+---
+
+### 模块 4:产品匹配推荐引擎(ProductMatcher)
+
+**新增文件**:`services/product-matcher.service.ts`
+
+#### 4.1 推荐规则体系
+
+```typescript
+export interface ProductMatchRule {
+  ruleId: string;
+  ruleName: string;
+  persona: CustomerPersona;               // 适用画像
+  conditions: MatchCondition[];            // 匹配条件
+  recommendProducts: string[];             // 推荐产品SKU列表
+  priority: number;                        // 优先级
+  reason: string;                          // 推荐理由模板
+  isSystemRule: boolean;                   // 系统规则 vs 用户自定义
+  createdBy?: string;                      // 创建人
+  lastModifiedAt?: Date;
+}
+
+export interface MatchCondition {
+  field: 'keywords' | 'industry' | 'scale' | 'country' | 'certifications' | 'custom';
+  operator: 'contains' | 'equals' | 'greater_than' | 'in';
+  value: any;
+}
+
+// 产品推荐结果(可编辑)
+export interface EditableProductRecommendation {
+  product: Product;
+  matchedRule: ProductMatchRule;
+  autoScore: number;                       // AI 匹配分
+  manualOverride: boolean;                 // 是否被人工修改
+  manualScore?: number;                    // 人工评分
+  salesNotes?: string;                     // 销售标注
+  isRemoved: boolean;                      // 是否被移除
+  addedManually: boolean;                  // 是否手动添加
+  sortOrder: number;                       // 排序(支持拖拽调整)
+}
+```
+
+#### 4.2 差异化推荐逻辑
+
+```
+┌───────────────────────────────────────────────────┐
+│  产品匹配推荐流程                                    │
+│                                                   │
+│  输入:客户画像 + 需求信息 + 公司背景                  │
+│                                                   │
+│  ① 画像规则匹配                                    │
+│     P1 医药分销 → 完整产品目录 + 认证文件             │
+│     P2 工业安全 → OSHA 合规产品线                    │
+│     P3 政府采购 → 投标级产品组合                      │
+│     P4 OEM品牌 → 可定制产品 + 打样方案                │
+│     P5 电商零售 → 高利润爆款 + FBA适配                │
+│                                                   │
+│  ② 需求关键词匹配                                   │
+│     邮件/名片中提到的具体产品 → 精确匹配              │
+│                                                   │
+│  ③ 历史成交数据参考                                  │
+│     同类画像历史成交产品排名                          │
+│                                                   │
+│  ④ 输出推荐列表(带理由)                             │
+│     销售可修改排序、添加/删除、标注理由                │
+└───────────────────────────────────────────────────┘
+```
+
+#### 4.3 对现有代码的修改
+
+**文件**:`pages/lead-detail/lead-detail.component.ts` 和 `.html`
+
+```
+修改内容:
+1. 推荐产品区域改为可编辑模式:
+   - 每个推荐产品卡片增加"编辑"、"移除"按钮
+   - 支持拖拽调整排序
+   - 支持手动添加产品(从产品目录中搜索)
+   - 每个产品可添加销售标注
+2. 产品匹配规则可查看/编辑入口
+3. 新增"跟进建议"卡片:
+   - 基于画像 + 产品匹配结果,给出第一步跟进建议
+   - 建议内容可编辑
+   - 包含:推荐话术、发送文件清单、跟进时间节点
+```
+
+**文件**:`components/deep-research-package/deep-research-package.component.ts` 和 `.html`
+
+```
+修改内容:
+1. 产品匹配表格增加"编辑"列
+2. 跟进策略区域增加"自定义修改"功能
+3. 话术建议改为可编辑富文本
+```
+
+---
+
+### 模块 5:用户确认流程(ConfirmationFlow)
+
+**新增文件**:`components/confirmation-list/confirmation-list.component.ts`
+
+#### 5.1 第一轮确认列表
+
+```
+┌────────────────────────────────────────────────────────────┐
+│  第一轮筛查结果 — 待确认列表                                  │
+│                                                            │
+│  ┌─ 汇总栏 ─────────────────────────────────────────────┐  │
+│  │  共 12 个主体 │ 建议挖掘 8 │ 可选 3 │ 建议归档 1       │  │
+│  │  [全部确认] [批量操作▼]                                │  │
+│  └──────────────────────────────────────────────────────┘  │
+│                                                            │
+│  ┌─ 主体卡片 ──────────────────────────────────────────┐   │
+│  │ ☑ WildGear GmbH                    🟢 强烈建议挖掘   │   │
+│  │   画像: P4-品牌OEM客户  等级: A  域名: wildgear.de    │   │
+│  │   ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄                   │   │
+│  │   💡 价值亮点:                                        │   │
+│  │   · 新兴户外品牌,年增长率40%                          │   │
+│  │   · 明确提到OEM定制需求                               │   │
+│  │   · 预估年采购额 $30K+                                │   │
+│  │   ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄                   │   │
+│  │   📧 联系人: alex@wildgear.de (Alex Mueller, CEO)    │   │
+│  │   ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄                   │   │
+│  │   [✓确认] [✏修改画像] [✗归档] [查看详情]              │   │
+│  └──────────────────────────────────────────────────────┘  │
+│                                                            │
+│  ┌─ 主体卡片 ──────────────────────────────────────────┐   │
+│  │ ☑ EduFirst Solutions                🟡 可选择性挖掘   │   │
+│  │   画像: P3-政府/机构  等级: B  域名: edufirst.org     │   │
+│  │   ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄                   │   │
+│  │   💡 价值亮点:                                        │   │
+│  │   · 教育类公司,无直接商品需求                         │   │
+│  │   · 但管理200+学校急救物资采购                        │   │
+│  │   · 潜在年采购额可观(集中采购模式)                   │   │
+│  │   ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄                   │   │
+│  │   ⚠ 无商品信息,需深入询问确认采购意向                 │   │
+│  │   [✓确认] [✏修改画像] [✗归档] [查看详情]              │   │
+│  └──────────────────────────────────────────────────────┘  │
+│                                                            │
+│  ┌────────────────────────────────────────────────────┐    │
+│  │  [▶ 对选中的 8 个主体启动深度背调]                    │    │
+│  └────────────────────────────────────────────────────┘    │
+└────────────────────────────────────────────────────────────┘
+```
+
+#### 5.2 第二轮确认列表
+
+```
+┌────────────────────────────────────────────────────────────┐
+│  第二轮深度背调结果 — 待确认列表                               │
+│                                                            │
+│  ┌─ 主体报告卡片 ──────────────────────────────────────┐   │
+│  │ WildGear GmbH          综合评分: 78/100  ⭐ A级      │   │
+│  │                                                      │   │
+│  │ 📊 三维评分:                                          │   │
+│  │   公司实力 ████████░░ 80%                             │   │
+│  │   决策人   ███████░░░ 70%                             │   │
+│  │   采购项目 ████████░░ 85%                             │   │
+│  │                                                      │   │
+│  │ 📦 推荐产品 (可编辑):                                 │   │
+│  │   1. FK-001 急救箱   $12.50  [✏][✗]  批发价合适       │   │
+│  │   2. FK-003 户外包   $8.90   [✏][✗]  匹配OEM需求     │   │
+│  │   3. + [添加产品]                                     │   │
+│  │                                                      │   │
+│  │ 🎯 跟进建议:                                          │   │
+│  │   · 首先发送OEM案例集 + 定制能力介绍 [✏]              │   │
+│  │   · 3天内安排视频会议讨论打样 [✏]                      │   │
+│  │   · 准备Pantone色卡和包装方案 [✏]                     │   │
+│  │                                                      │   │
+│  │ 📧 触达邮件 (可编辑):                                 │   │
+│  │   [预览邮件] [编辑邮件] [复制]                         │   │
+│  │                                                      │   │
+│  │ ⚠ 风险提示: 低风险 — 新品牌首单可能较小               │   │
+│  │                                                      │   │
+│  │ [✓ 确认并进入跟进] [✏ 修改] [↗ 查看完整报告]          │   │
+│  └──────────────────────────────────────────────────────┘  │
+└────────────────────────────────────────────────────────────┘
+```
+
+#### 5.3 对现有代码的修改
+
+**文件**:`pages/dashboard/dashboard.component.ts` 和 `.html`
+
+```
+修改内容:
+1. "待验证线索" Tab 重构为两轮确认流程:
+   - 第一轮:批量筛查确认列表
+   - 第二轮:深度背调确认列表
+2. 支持在两轮间切换查看
+3. 汇总统计更新为双轮数据
+```
+
+---
+
+### 模块 6:分析结果与提示优化
+
+#### 6.1 深度询问模板
+
+**新增文件**:`services/inquiry-template.service.ts`
+
+不同画像类型需要不同的深度询问方向,系统提供结构化的询问模板:
+
+```typescript
+export interface InquiryTemplate {
+  persona: CustomerPersona;
+  phase: 'initial' | 'deep';
+  questions: InquiryQuestion[];
+}
+
+export interface InquiryQuestion {
+  id: string;
+  category: string;             // "采购需求" / "公司背景" / "预算" / "时间"
+  question: string;             // 问题内容
+  questionEn: string;           // 英文版本
+  importance: 'critical' | 'important' | 'nice_to_have';
+  aiCanAutoAnswer: boolean;     // AI 是否可以自动从公开信息回答
+  salesNeedToAsk: boolean;      // 是否需要销售手动询问
+  expectedAnswer?: string;      // 期望的回答类型
+}
+```
+
+**各画像询问重点**:
+
+| 画像 | 关键询问方向 | 价值判断重点 |
+|------|-------------|-------------|
+| P1 医药分销 | 渠道覆盖、年采购量、现有供应商、认证要求 | 复购能力、渠道价值 |
+| P2 工业安全 | 终端客户行业、合规标准、定制需求 | 长期复购、标准化程度 |
+| P3 政府采购 | 预算周期、招标流程、历史采购量 | 单次订单规模 |
+| P4 OEM品牌 | 品牌定位、设计要求、起订量、交付周期 | 品牌溢价空间、定制利润 |
+| P5 电商零售 | 店铺规模、月销量、FBA需求、利润率要求 | 订单频次、增长潜力 |
+| P6 低质量 | — | 直接归档 |
+
+#### 6.2 分析结果呈现优化
+
+**文件**:`components/screen-result-card/screen-result-card.component.ts` 和 `.html`
+
+```
+修改内容:
+1. 卡片顶部增加醒目的"决策标签":
+   - 🟢 强烈建议深入挖掘(S/A 级 + 高匹配度)
+   - 🟡 可选择性挖掘(B 级 或 中匹配度)
+   - 🔴 建议归档(C 级 或 低匹配度)
+2. 价值亮点区域:
+   - 有商品信息的:突出具体产品需求和金额
+   - 无商品信息的:突出潜在价值和挖掘方向
+3. 风险提示:
+   - 免费邮箱警告
+   - 无官网警告
+   - 信息不完整提示
+```
+
+**文件**:`pages/bgcheck/bgcheck.component.ts` 和 `.html`
+
+```
+修改内容:
+1. 综合评级区域增加更直观的评分解读
+2. 销售策略区域增加"第一步行动清单"
+3. 产品推荐区域支持编辑
+4. 增加"一键生成跟进邮件"按钮
+```
+
+---
+
+## 三、新增/修改文件清单
+
+### 新增文件
+
+| 文件路径 | 说明 |
+|----------|------|
+| `services/batch-input.service.ts` | 批量输入处理服务 |
+| `services/entity-resolver.service.ts` | 主体识别与去重服务 |
+| `services/two-round-bgcheck.service.ts` | 两轮背调核心服务(封装模块) |
+| `services/product-matcher.service.ts` | 产品匹配推荐服务 |
+| `services/inquiry-template.service.ts` | 深度询问模板服务 |
+| `components/confirmation-list/confirmation-list.component.ts` | 确认列表组件 |
+| `components/confirmation-list/confirmation-list.component.html` | 确认列表模板 |
+| `components/confirmation-list/confirmation-list.component.scss` | 确认列表样式 |
+| `components/batch-progress/batch-progress.component.ts` | 批量进度展示组件 |
+| `components/batch-progress/batch-progress.component.html` | 批量进度模板 |
+| `components/batch-progress/batch-progress.component.scss` | 批量进度样式 |
+| `components/editable-product-list/editable-product-list.component.ts` | 可编辑产品列表组件 |
+| `components/editable-product-list/editable-product-list.component.html` | 可编辑产品列表模板 |
+| `components/editable-product-list/editable-product-list.component.scss` | 可编辑产品列表样式 |
+
+### 修改文件
+
+| 文件路径 | 修改内容概要 |
+|----------|-------------|
+| `models/lead.model.ts` | 新增 entityId/domainKey/emailKey 等字段,新增 EntityGroup/EntityProfile 等接口 |
+| `services/quick-screen.service.ts` | 增加 screenBatch() 方法,createLeadFromScreen() 增加查重逻辑 |
+| `services/mock-data.service.ts` | mockLeads 增加主体字段,新增批量/去重相关模拟数据 |
+| `pages/dashboard/dashboard.component.ts` | 新增批量导入Tab,重构待验证列表为两轮确认流程 |
+| `pages/dashboard/dashboard.component.html` | 批量导入UI、确认列表UI |
+| `pages/dashboard/dashboard.component.scss` | 新增样式 |
+| `pages/lead-detail/lead-detail.component.ts` | startDeepAnalysis 重构,产品推荐可编辑 |
+| `pages/lead-detail/lead-detail.component.html` | 产品可编辑UI,跟进建议可编辑UI |
+| `pages/lead-detail/lead-detail.component.scss` | 新增样式 |
+| `pages/bgcheck/bgcheck.component.ts` | 评分解读优化,产品推荐可编辑 |
+| `pages/bgcheck/bgcheck.component.html` | UI优化、一键生成邮件按钮 |
+| `components/bgcheck-progress/bgcheck-progress.component.ts` | 支持批量模式 |
+| `components/screen-result-card/screen-result-card.component.ts` | 增加决策标签、价值亮点 |
+| `components/screen-result-card/screen-result-card.component.html` | UI重构 |
+| `components/deep-research-package/deep-research-package.component.ts` | 产品匹配可编辑、话术可编辑 |
+| `components/deep-research-package/deep-research-package.component.html` | UI更新 |
+| `app.routes.ts` | 如需新增路由(批量背调结果页等) |
+
+---
+
+## 四、两轮背调模块封装设计
+
+### 4.1 模块对外接口
+
+```typescript
+// services/two-round-bgcheck.service.ts
+
+@Injectable({ providedIn: 'root' })
+export class TwoRoundBgCheckService {
+  
+  // ===== 第一轮:批量初步筛查 =====
+  
+  /**
+   * 执行第一轮批量筛查
+   * @param inputs 批量输入项(已解析)
+   * @returns 按主体分组的筛查结果,等待用户确认
+   */
+  async executeRound1(inputs: BatchInputItem[]): Promise<Round1Result>;
+  
+  /**
+   * 用户确认第一轮结果
+   * @param confirmations 用户对每个主体的操作
+   * @returns 确认后的主体列表(只包含确认/修改的)
+   */
+  async confirmRound1(confirmations: Round1Confirmation[]): Promise<Round1ConfirmedResult>;
+  
+  // ===== 第二轮:批量深度背调 =====
+  
+  /**
+   * 对确认的主体批量启动深度背调
+   * @param entityIds 要进行深度背调的主体ID列表
+   * @returns 进度 Observable(实时推送进度)
+   */
+  startRound2(entityIds: string[]): Observable<BatchBgCheckProgress>;
+  
+  /**
+   * 获取第二轮结果
+   * @returns 深度背调结果列表,等待用户确认产品和策略
+   */
+  async getRound2Results(): Promise<Round2Result>;
+  
+  /**
+   * 用户确认第二轮结果(确认产品推荐和跟进策略)
+   * @param confirmations 用户修改后的产品推荐和策略
+   */
+  async confirmRound2(confirmations: Round2Confirmation[]): Promise<void>;
+  
+  // ===== 辅助方法 =====
+  
+  /** 获取当前进度 */
+  getCurrentProgress(): BatchBgCheckProgress | null;
+  
+  /** 取消正在进行的背调 */
+  cancelBgCheck(entityId: string): void;
+  
+  /** 重试失败的背调 */
+  retryBgCheck(entityId: string): Observable<EntityBgCheckProgress>;
+}
+```
+
+### 4.2 数据流
+
+```
+BatchInputService          TwoRoundBgCheckService          UI Components
+     │                            │                            │
+     │  1. 解析原始输入             │                            │
+     │ ─────────────────────────→ │                            │
+     │                            │  2. 主体识别 & 去重          │
+     │                            │  3. 执行第一轮筛查           │
+     │                            │ ──────────────────────────→ │
+     │                            │     显示第一轮确认列表       │
+     │                            │ ←────────────────────────── │
+     │                            │     用户确认操作             │
+     │                            │  4. 保存确认结果             │
+     │                            │  5. 启动第二轮深度背调       │
+     │                            │ ──────────────────────────→ │
+     │                            │     显示批量进度             │
+     │                            │     ...                     │
+     │                            │ ──────────────────────────→ │
+     │                            │     显示第二轮确认列表       │
+     │                            │ ←────────────────────────── │
+     │                            │     用户修改产品/策略        │
+     │                            │  6. 保存最终结果             │
+     │                            │ ──────────────────────────→ │
+     │                            │     进入跟进阶段             │
+```
+
+---
+
+## 五、实现优先级
+
+### Phase 1 — 核心框架(建议先做)
+
+| 优先级 | 任务 | 涉及文件 | 工作量 |
+|--------|------|----------|--------|
+| P0 | TwoRoundBgCheckService 核心服务 | `services/two-round-bgcheck.service.ts` | 大 |
+| P0 | EntityResolver 主体识别服务 | `services/entity-resolver.service.ts` | 中 |
+| P0 | Lead 模型扩展 | `models/lead.model.ts` | 小 |
+| P0 | QuickScreenService 增加查重 | `services/quick-screen.service.ts` | 中 |
+| P0 | MockData 适配 | `services/mock-data.service.ts` | 中 |
+
+### Phase 2 — 确认流程 UI
+
+| 优先级 | 任务 | 涉及文件 | 工作量 |
+|--------|------|----------|--------|
+| P0 | 第一轮确认列表组件 | `components/confirmation-list/` | 大 |
+| P0 | 第二轮确认列表组件 | `components/confirmation-list/` | 大 |
+| P1 | Dashboard 集成两轮流程 | `pages/dashboard/` | 大 |
+| P1 | 批量进度组件 | `components/batch-progress/` | 中 |
+
+### Phase 3 — 批量输入与产品匹配
+
+| 优先级 | 任务 | 涉及文件 | 工作量 |
+|--------|------|----------|--------|
+| P1 | BatchInputService 批量输入 | `services/batch-input.service.ts` | 中 |
+| P1 | 可编辑产品列表组件 | `components/editable-product-list/` | 中 |
+| P1 | ProductMatcher 推荐引擎 | `services/product-matcher.service.ts` | 中 |
+| P2 | 深度询问模板服务 | `services/inquiry-template.service.ts` | 小 |
+
+### Phase 4 — 分析结果优化
+
+| 优先级 | 任务 | 涉及文件 | 工作量 |
+|--------|------|----------|--------|
+| P1 | ScreenResultCard 决策标签 | `components/screen-result-card/` | 小 |
+| P1 | BgCheck 报告页优化 | `pages/bgcheck/` | 中 |
+| P2 | LeadDetail 产品可编辑 | `pages/lead-detail/` | 中 |
+| P2 | 触达邮件生成优化 | `components/deep-research-package/` | 中 |
+
+---
+
+## 六、预期效果
+
+### 优化前(当前状态)
+
+```
+逐条手动输入 → 单条筛查 → 无去重 → 直接分析 → 固定推荐 → 销售手动决策
+```
+
+### 优化后
+
+```
+批量导入(截图/名片/粘贴)
+    ↓
+自动解析 + 主体识别 + 去重
+    ↓
+第一轮:AI 初步筛查 → 差异化价值点 → 待确认列表 → 用户确认
+    ↓
+第二轮:批量深度背调 → 产品匹配推荐(可编辑)→ 跟进策略 → 用户确认/标注
+    ↓
+输出:精准的跟进建议 + 可编辑的产品方案 + 个性化触达邮件
+```
+
+### 关键改进
+
+| 维度 | 改进前 | 改进后 |
+|------|--------|--------|
+| **效率** | 逐条处理,每条3-5分钟 | 批量处理10-50条,整体5-10分钟 |
+| **准确性** | 同公司多条线索无法关联 | 主体唯一性保证,自动去重归并 |
+| **价值判断** | AI 直出结果,销售被动接受 | 每轮给列表、用户确认,人机协作 |
+| **产品匹配** | 固定规则,不可修改 | 可编辑、可标注、可自定义规则 |
+| **差异化** | 所有画像同一模板 | 不同画像不同价值点和询问方向 |
+| **跟进支持** | 笼统建议 | 具体的第一步行动清单 + 可编辑触达邮件 |
+
+---
+
+*文档生成时间:2026-02-22*  
+*文档版本:v1.0*

Разница между файлами не показана из-за своего большого размера
+ 914 - 509
lead-discovery/package-lock.json


+ 2 - 1
lead-discovery/package.json

@@ -20,6 +20,7 @@
     "@angular/platform-browser": "^19.2.18",
     "@angular/platform-browser": "^19.2.18",
     "@angular/platform-browser-dynamic": "^19.2.18",
     "@angular/platform-browser-dynamic": "^19.2.18",
     "@angular/router": "^19.2.18",
     "@angular/router": "^19.2.18",
+    "fmode-ng": "^0.0.245",
     "rxjs": "~7.8.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.1"
     "zone.js": "~0.15.1"
@@ -37,4 +38,4 @@
     "karma-jasmine-html-reporter": "~2.1.0",
     "karma-jasmine-html-reporter": "~2.1.0",
     "typescript": "~5.5.2"
     "typescript": "~5.5.2"
   }
   }
-}
+}

+ 56 - 60
lead-discovery/src/app/app.component.ts

@@ -1,64 +1,58 @@
 import { Component } from '@angular/core';
 import { Component } from '@angular/core';
 import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
 import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
 import { MatToolbarModule } from '@angular/material/toolbar';
 import { MatToolbarModule } from '@angular/material/toolbar';
-import { MatSidenavModule } from '@angular/material/sidenav';
 import { MatIconModule } from '@angular/material/icon';
 import { MatIconModule } from '@angular/material/icon';
 import { MatButtonModule } from '@angular/material/button';
 import { MatButtonModule } from '@angular/material/button';
-import { MatListModule } from '@angular/material/list';
 import { MatBadgeModule } from '@angular/material/badge';
 import { MatBadgeModule } from '@angular/material/badge';
 
 
 @Component({
 @Component({
     selector: 'app-root',
     selector: 'app-root',
     imports: [
     imports: [
         RouterOutlet, RouterLink, RouterLinkActive,
         RouterOutlet, RouterLink, RouterLinkActive,
-        MatToolbarModule, MatSidenavModule, MatIconModule,
-        MatButtonModule, MatListModule, MatBadgeModule,
+        MatToolbarModule, MatIconModule,
+        MatButtonModule, MatBadgeModule,
     ],
     ],
     template: `
     template: `
     <div class="app-shell">
     <div class="app-shell">
       <!-- Top Bar -->
       <!-- Top Bar -->
       <mat-toolbar class="top-bar">
       <mat-toolbar class="top-bar">
-        <button mat-icon-button (click)="sidenavOpen = !sidenavOpen">
-          <mat-icon>menu</mat-icon>
-        </button>
         <span class="brand">
         <span class="brand">
           <mat-icon class="brand-icon">local_hospital</mat-icon>
           <mat-icon class="brand-icon">local_hospital</mat-icon>
           线索管理
           线索管理
         </span>
         </span>
+
+        <nav class="top-nav" aria-label="主导航">
+          <a
+            mat-button
+            routerLink="/dashboard"
+            routerLinkActive="active-top-link"
+            [routerLinkActiveOptions]="{ exact: true }"
+          >
+            <mat-icon>auto_awesome</mat-icon>
+            <span>AI 线索获取工作台</span>
+            <span class="nav-pill" matBadge="5" matBadgeSize="small" matBadgeColor="primary"></span>
+          </a>
+
+          <a
+            mat-button
+            routerLink="/list"
+            routerLinkActive="active-top-link"
+            [routerLinkActiveOptions]="{ exact: true }"
+          >
+            <mat-icon>hub</mat-icon>
+            <span>线索中心</span>
+            <span class="nav-pill" matBadge="10" matBadgeSize="small" matBadgeColor="primary"></span>
+          </a>
+        </nav>
+
         <span class="spacer"></span>
         <span class="spacer"></span>
         <span class="env-badge">DEMO</span>
         <span class="env-badge">DEMO</span>
       </mat-toolbar>
       </mat-toolbar>
 
 
-      <mat-sidenav-container class="sidenav-container">
-        <!-- Side Nav -->
-        <mat-sidenav [opened]="sidenavOpen" mode="side" class="sidenav">
-          <mat-nav-list>
-            <a mat-list-item routerLink="/dashboard" routerLinkActive="active-link">
-              <mat-icon matListItemIcon>auto_awesome</mat-icon>
-              <span matListItemTitle>AI 工作台</span>
-              <span class="nav-badge" matListItemMeta>5</span>
-            </a>
-            <a mat-list-item routerLink="/list" routerLinkActive="active-link">
-              <mat-icon matListItemIcon>hub</mat-icon>
-              <span matListItemTitle>线索中心</span>
-              <span class="nav-badge" matListItemMeta>10</span>
-            </a>
-            <a mat-list-item routerLink="/personas" routerLinkActive="active-link">
-              <mat-icon matListItemIcon>psychology</mat-icon>
-              <span matListItemTitle>画像知识库</span>
-            </a>
-            <a mat-list-item routerLink="/products" routerLinkActive="active-link">
-              <mat-icon matListItemIcon>inventory_2</mat-icon>
-              <span matListItemTitle>产品目录</span>
-            </a>
-          </mat-nav-list>
-        </mat-sidenav>
-
-        <!-- Main Content -->
-        <mat-sidenav-content class="main-content">
-          <router-outlet></router-outlet>
-        </mat-sidenav-content>
-      </mat-sidenav-container>
+      <!-- Main Content -->
+      <main class="main-content" role="main">
+        <router-outlet></router-outlet>
+      </main>
     </div>
     </div>
   `,
   `,
     styles: [`
     styles: [`
@@ -86,9 +80,27 @@ import { MatBadgeModule } from '@angular/material/badge';
       gap: 8px;
       gap: 8px;
       font-size: 18px;
       font-size: 18px;
       font-weight: 500;
       font-weight: 500;
-      margin-left: 8px;
     }
     }
     .brand-icon { font-size: 24px; }
     .brand-icon { font-size: 24px; }
+    .top-nav {
+      display: flex;
+      align-items: center;
+      gap: 4px;
+      margin-left: 16px;
+    }
+    .top-nav a[mat-button] {
+      color: rgba(255,255,255,0.92);
+      border-radius: 999px;
+      padding: 0 10px;
+      line-height: 36px;
+      height: 36px;
+    }
+    .top-nav mat-icon {
+      margin-right: 6px;
+    }
+    .nav-pill {
+      margin-left: 8px;
+    }
     .spacer { flex: 1; }
     .spacer { flex: 1; }
     .env-badge {
     .env-badge {
       background: rgba(255,255,255,0.2);
       background: rgba(255,255,255,0.2);
@@ -98,33 +110,17 @@ import { MatBadgeModule } from '@angular/material/badge';
       font-weight: 500;
       font-weight: 500;
       letter-spacing: 1px;
       letter-spacing: 1px;
     }
     }
-    .sidenav-container {
-      flex: 1;
-      background: #f5f7fa;
-    }
-    .sidenav {
-      width: 220px;
-      background: #fff;
-      border-right: 1px solid #e8e8e8;
-    }
     .main-content {
     .main-content {
+      flex: 1;
       background: #f5f7fa;
       background: #f5f7fa;
+      overflow: auto;
     }
     }
-    .active-link {
-      background: #e3f2fd !important;
-      color: #1565c0 !important;
-      border-right: 3px solid #1565c0;
-    }
-    .nav-badge {
-      background: #e0e0e0;
-      color: #555;
-      padding: 1px 8px;
-      border-radius: 10px;
-      font-size: 11px;
-      font-weight: 600;
+    .active-top-link {
+      background: rgba(255,255,255,0.18) !important;
+      color: #fff !important;
+      box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
     }
     }
   `]
   `]
 })
 })
 export class AppComponent {
 export class AppComponent {
-  sidenavOpen = true;
 }
 }

+ 4 - 6
lead-discovery/src/app/app.routes.ts

@@ -15,14 +15,12 @@ export const routes: Routes = [
     loadComponent: () => import('./pages/lead-detail/lead-detail.component').then(m => m.LeadDetailComponent),
     loadComponent: () => import('./pages/lead-detail/lead-detail.component').then(m => m.LeadDetailComponent),
   },
   },
   {
   {
-    path: 'personas',
-    loadComponent: () => import('./pages/personas/personas.component').then(m => m.PersonasComponent),
-  },
-  {
-    path: 'products',
-    loadComponent: () => import('./pages/product-catalog/product-catalog.component').then(m => m.ProductCatalogComponent),
+    path: 'leads/:id/bgcheck',
+    loadComponent: () => import('./pages/bgcheck/bgcheck.component').then(m => m.BgCheckComponent),
   },
   },
   // Redirects for old routes
   // Redirects for old routes
   { path: 'kanban', redirectTo: 'list', pathMatch: 'full' },
   { path: 'kanban', redirectTo: 'list', pathMatch: 'full' },
   { path: 'leads', redirectTo: 'list', pathMatch: 'full' },
   { path: 'leads', redirectTo: 'list', pathMatch: 'full' },
+  // Fallback
+  { path: '**', redirectTo: 'dashboard' },
 ];
 ];

+ 125 - 0
lead-discovery/src/app/components/bgcheck-progress/bgcheck-progress.component.ts

@@ -0,0 +1,125 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatIconModule } from '@angular/material/icon';
+import { MatCardModule } from '@angular/material/card';
+
+export interface BgCheckStage {
+  id: number;
+  name: string;
+  status: 'waiting' | 'processing' | 'completed' | 'error';
+  icon: string;
+  description?: string;
+}
+
+@Component({
+  selector: 'app-bgcheck-progress',
+  standalone: true,
+  imports: [CommonModule, MatProgressBarModule, MatIconModule, MatCardModule],
+  template: `
+    <div class="bgcheck-progress-container">
+      <div class="stages-list">
+        <div
+          *ngFor="let stage of stages"
+          class="stage-item"
+          [class.completed]="stage.status === 'completed'"
+          [class.processing]="stage.status === 'processing'"
+          [class.error]="stage.status === 'error'">
+          <div class="stage-icon">
+            <mat-icon>{{ getStageIcon(stage) }}</mat-icon>
+          </div>
+          <div class="stage-content">
+            <div class="stage-name">{{ stage.name }}</div>
+            <div class="stage-desc" *ngIf="stage.description">{{ stage.description }}</div>
+          </div>
+          <div class="stage-status">
+            <mat-icon *ngIf="stage.status === 'completed'" class="status-icon success">check_circle</mat-icon>
+            <mat-icon *ngIf="stage.status === 'processing'" class="status-icon processing">sync</mat-icon>
+            <mat-icon *ngIf="stage.status === 'error'" class="status-icon error">error</mat-icon>
+            <mat-icon *ngIf="stage.status === 'waiting'" class="status-icon waiting">schedule</mat-icon>
+          </div>
+        </div>
+      </div>
+      <div class="progress-bar-container">
+        <mat-progress-bar
+          mode="determinate"
+          [value]="overallProgress"
+          [color]="progressColor">
+        </mat-progress-bar>
+        <div class="progress-text">{{ overallProgress }}% 完成</div>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .bgcheck-progress-container {
+      padding: 20px;
+      background: #fafafa;
+      border-radius: 8px;
+    }
+    .stages-list {
+      display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px;
+    }
+    .stage-item {
+      display: flex; align-items: center; gap: 12px;
+      padding: 12px 16px; background: #fff; border-radius: 8px;
+      border-left: 4px solid #e0e0e0; transition: all 0.3s;
+    }
+    .stage-item.completed {
+      border-left-color: #4caf50; background: #f1f8e9;
+    }
+    .stage-item.processing {
+      border-left-color: #2196f3; background: #e3f2fd;
+    }
+    .stage-item.error {
+      border-left-color: #f44336; background: #ffebee;
+    }
+    .stage-icon {
+      width: 40px; height: 40px; border-radius: 50%;
+      display: flex; align-items: center; justify-content: center;
+      background: #f5f5f5; color: #999;
+    }
+    .stage-item.completed .stage-icon {
+      background: #4caf50; color: #fff;
+    }
+    .stage-item.processing .stage-icon {
+      background: #2196f3; color: #fff;
+      animation: spin 1s linear infinite;
+    }
+    @keyframes spin {
+      from { transform: rotate(0deg); }
+      to { transform: rotate(360deg); }
+    }
+    .stage-content { flex: 1; }
+    .stage-name { font-size: 14px; font-weight: 600; color: #333; }
+    .stage-desc { font-size: 12px; color: #888; margin-top: 2px; }
+    .stage-status { }
+    .status-icon { font-size: 20px; width: 20px; height: 20px; }
+    .status-icon.success { color: #4caf50; }
+    .status-icon.processing { color: #2196f3; animation: spin 1s linear infinite; }
+    .status-icon.error { color: #f44336; }
+    .status-icon.waiting { color: #ccc; }
+    .progress-bar-container { margin-top: 16px; }
+    .progress-text {
+      text-align: center; font-size: 12px; color: #666; margin-top: 4px;
+    }
+  `]
+})
+export class BgCheckProgressComponent {
+  @Input() stages: BgCheckStage[] = [];
+  @Input() overallProgress: number = 0;
+
+  get progressColor(): 'primary' | 'accent' | 'warn' {
+    if (this.overallProgress >= 80) return 'primary';
+    if (this.overallProgress >= 50) return 'accent';
+    return 'warn';
+  }
+
+  getStageIcon(stage: BgCheckStage): string {
+    if (stage.status === 'completed') return 'check_circle';
+    if (stage.status === 'processing') return 'sync';
+    if (stage.status === 'error') return 'error';
+    return stage.icon || 'schedule';
+  }
+}
+
+

+ 182 - 0
lead-discovery/src/app/models/lead.model.ts

@@ -200,6 +200,79 @@ export interface ProductMatch {
   profitMargin: string;
   profitMargin: string;
 }
 }
 
 
+// ===== 主体联系人 =====
+export interface ContactInfo {
+  name: string;
+  email: string;
+  phone?: string;
+  title?: string;
+  source: string;
+  firstSeenAt: Date;
+}
+
+// ===== 主体合并记录 =====
+export interface MergeRecord {
+  mergedAt: Date;
+  fromLeadId: string;
+  reason: string;
+}
+
+// ===== 主体分组 =====
+export interface EntityGroup {
+  entityId: string;
+  primaryDomain: string;
+  primaryCompanyName: string;
+  normalizedDomain: string;
+  normalizedCompanyName: string;
+  confidence: number;
+  leads: Lead[];
+  contacts: ContactInfo[];
+  duplicateWarnings: string[];
+  existingLeadIds: string[];
+  isNew: boolean;
+  mergeHistory: MergeRecord[];
+}
+
+// ===== 主体画像(汇总) =====
+export interface EntityProfile {
+  entityId: string;
+  primaryCompanyName: string;
+  primaryDomain: string;
+  allDomains: string[];
+  allEmails: string[];
+  contacts: ContactInfo[];
+  leadIds: string[];
+  latestPersona: CustomerPersona;
+  latestGrade: ValueGrade;
+  totalEstimatedValue: number;
+  interactionTimeline: { date: Date; event: string; leadId: string }[];
+  createdAt: Date;
+  updatedAt: Date;
+}
+
+// ===== 批量输入项 =====
+export interface BatchInputItem {
+  rawText: string;
+  parsedCompanyName?: string;
+  parsedEmail?: string;
+  parsedDomain?: string;
+  parsedContactName?: string;
+  parsedPhone?: string;
+  parsedCountry?: string;
+  parseConfidence: number;
+  parseSource: 'ocr' | 'text' | 'csv' | 'email';
+}
+
+// ===== 批量处理结果 =====
+export interface BatchProcessResult {
+  totalInput: number;
+  successParsed: number;
+  failedParsed: number;
+  duplicatesFound: number;
+  items: BatchInputItem[];
+  entities: EntityGroup[];
+}
+
 // ===== Lead 对象 =====
 // ===== Lead 对象 =====
 export interface Lead {
 export interface Lead {
   id: string;
   id: string;
@@ -254,9 +327,118 @@ export interface Lead {
   matchedProductSKUs: string[];
   matchedProductSKUs: string[];
   estimatedOrderValue: number;
   estimatedOrderValue: number;
 
 
+  /** 背调相关(方案一:轻量集成) */
+  hasBackgroundCheck: boolean;
+  backgroundCheckAt?: Date;
+  backgroundCheckReport?: BackgroundCheckReport;
+
+  /** 主体关联 */
+  entityId: string;
+  domainKey: string;
+  emailKey: string;
+  relatedLeadIds: string[];
+  mergedFrom?: string;
+  isEntityPrimary: boolean;
+
   createdAt: Date;
   createdAt: Date;
 }
 }
 
 
+// ===== 背调报告(三维报告结构) =====
+export interface BackgroundCheckReport {
+  meta: {
+    date: string;
+    investigator: string;
+    method: string;
+    level: string;
+  };
+  overview: {
+    companyName: string;
+    website: string;
+    country: string;
+    channel: string;
+    completion: {
+      company: number;
+      decisionMaker: number;
+      project: number;
+    };
+    conclusion: string;
+  };
+  dimension1: {
+    basic: {
+      regYear?: string;
+      employees?: number;
+      address?: string;
+    };
+    financial: {
+      revenue?: string;
+      currency?: string;
+    };
+    social: {
+      linkedin?: SocialMediaData;
+      twitter?: SocialMediaData;
+      instagram?: SocialMediaData;
+      youtube?: SocialMediaData;
+      fromWebsite?: Record<string, string>;
+    };
+    product: {
+      mainCategories: string[];
+      businessType?: string;
+    };
+  };
+  dimension2: {
+    contact: {
+      name: string;
+      title: string;
+      email?: string;
+      phone?: string;
+      linkedin?: string;
+      authority?: string;
+    };
+    background?: string;
+    communication?: string;
+  };
+  dimension3: {
+    needs?: {
+      details: string;
+      orderType?: string;
+      usage?: string;
+    };
+    budget?: {
+      amount?: string;
+      sensitivity?: string;
+      payment?: string;
+    };
+    time?: {
+      delivery?: string;
+      urgency?: string;
+    };
+    competition?: {
+      currentSupplier?: string;
+    };
+    potential?: any;
+  };
+  rating: {
+    overallScore: number;
+    level: string;
+  };
+  risk: {
+    level: 'low' | 'medium' | 'high';
+    factors: string[];
+  };
+  strategy: {
+    approach: string;
+    actions: string[];
+    timeline?: string;
+  };
+}
+
+export interface SocialMediaData {
+  url?: string;
+  followers?: number;
+  verified?: boolean;
+  rawData?: any;
+}
+
 // ===== AI 挖掘批次 =====
 // ===== AI 挖掘批次 =====
 export interface AiDiscoveryBatch {
 export interface AiDiscoveryBatch {
   id: string;
   id: string;

+ 273 - 0
lead-discovery/src/app/pages/bgcheck/bgcheck.component.html

@@ -0,0 +1,273 @@
+<div class="page-container" *ngIf="lead && report">
+  <!-- Back -->
+  <a class="back-link" (click)="goBack()">
+    <mat-icon>arrow_back</mat-icon> 返回线索详情
+  </a>
+
+  <!-- Header -->
+  <div class="header-card card">
+    <div class="header-top">
+      <div>
+        <h1>{{ lead.companyName }} - 背调报告</h1>
+        <div class="header-meta">
+          <span class="meta-item">
+            <mat-icon>schedule</mat-icon>
+            {{ lead.backgroundCheckAt | date:'yyyy-MM-dd HH:mm' }}
+          </span>
+          <span class="meta-item">
+            <mat-icon>person</mat-icon>
+            {{ report.meta.investigator }}
+          </span>
+          <span class="meta-item">
+            <mat-icon>assessment</mat-icon>
+            等级:{{ report.meta.level }}
+          </span>
+        </div>
+      </div>
+      <button mat-raised-button color="primary" (click)="goBack()">
+        <mat-icon>arrow_back</mat-icon> 返回
+      </button>
+    </div>
+  </div>
+
+  <!-- Report Content -->
+  <div class="report-grid">
+    <!-- Left Column -->
+    <div class="left-col">
+      <!-- Overview -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>info</mat-icon>
+          <h3>概览</h3>
+        </div>
+        <div class="info-grid">
+          <div class="info-item">
+            <span class="info-label">公司名称</span>
+            <span class="info-value">{{ report.overview.companyName }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">官网</span>
+            <span class="info-value">{{ report.overview.website || '-' }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">国家</span>
+            <span class="info-value">{{ report.overview.country }}</span>
+          </div>
+          <div class="info-item">
+            <span class="info-label">来源渠道</span>
+            <span class="info-value">{{ report.overview.channel }}</span>
+          </div>
+        </div>
+        <div class="completion-row">
+          <div class="completion-item">
+            <span class="completion-label">公司信息</span>
+            <mat-progress-bar mode="determinate" [value]="report.overview.completion.company"></mat-progress-bar>
+            <span class="completion-value">{{ report.overview.completion.company }}%</span>
+          </div>
+          <div class="completion-item">
+            <span class="completion-label">决策人</span>
+            <mat-progress-bar mode="determinate" [value]="report.overview.completion.decisionMaker"></mat-progress-bar>
+            <span class="completion-value">{{ report.overview.completion.decisionMaker }}%</span>
+          </div>
+          <div class="completion-item">
+            <span class="completion-label">采购项目</span>
+            <mat-progress-bar mode="determinate" [value]="report.overview.completion.project"></mat-progress-bar>
+            <span class="completion-value">{{ report.overview.completion.project }}%</span>
+          </div>
+        </div>
+        <div class="conclusion">
+          <strong>结论:</strong>{{ report.overview.conclusion }}
+        </div>
+      </div>
+
+      <!-- Dimension 1: Company -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>business</mat-icon>
+          <h3>第一维度:公司实力</h3>
+        </div>
+        <div class="dimension-content">
+          <div class="dimension-section">
+            <h4>基础信息</h4>
+            <div class="info-grid">
+              <div class="info-item" *ngIf="report.dimension1.basic.regYear">
+                <span class="info-label">成立年份</span>
+                <span class="info-value">{{ report.dimension1.basic.regYear }}</span>
+              </div>
+              <div class="info-item" *ngIf="report.dimension1.basic.employees">
+                <span class="info-label">员工数</span>
+                <span class="info-value">{{ report.dimension1.basic.employees }}人</span>
+              </div>
+              <div class="info-item" *ngIf="report.dimension1.basic.address">
+                <span class="info-label">地址</span>
+                <span class="info-value">{{ report.dimension1.basic.address }}</span>
+              </div>
+            </div>
+          </div>
+          <div class="dimension-section" *ngIf="report.dimension1.financial.revenue">
+            <h4>财务状况</h4>
+            <div class="info-item">
+              <span class="info-label">营收</span>
+              <span class="info-value">{{ report.dimension1.financial.revenue }} {{ report.dimension1.financial.currency || '' }}</span>
+            </div>
+          </div>
+          <div class="dimension-section" *ngIf="report.dimension1.product.mainCategories.length">
+            <h4>产品线</h4>
+            <div class="chips-row">
+              <span class="chip" *ngFor="let cat of report.dimension1.product.mainCategories">{{ cat }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Dimension 2: Decision Maker -->
+      <div class="section-card card" *ngIf="report.dimension2">
+        <div class="section-header">
+          <mat-icon>person</mat-icon>
+          <h3>第二维度:决策人</h3>
+        </div>
+        <div class="dimension-content">
+          <div class="info-grid">
+            <div class="info-item">
+              <span class="info-label">姓名</span>
+              <span class="info-value">{{ report.dimension2.contact.name }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">职位</span>
+              <span class="info-value">{{ report.dimension2.contact.title }}</span>
+            </div>
+            <div class="info-item" *ngIf="report.dimension2.contact.email">
+              <span class="info-label">邮箱</span>
+              <span class="info-value">{{ report.dimension2.contact.email }}</span>
+            </div>
+            <div class="info-item" *ngIf="report.dimension2.contact.phone">
+              <span class="info-label">电话</span>
+              <span class="info-value">{{ report.dimension2.contact.phone }}</span>
+            </div>
+            <div class="info-item" *ngIf="report.dimension2.contact.authority">
+              <span class="info-label">决策权限</span>
+              <span class="info-value">{{ report.dimension2.contact.authority }}</span>
+            </div>
+          </div>
+          <div class="dimension-section" *ngIf="report.dimension2.background">
+            <h4>背景信息</h4>
+            <p>{{ report.dimension2.background }}</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- Dimension 3: Project -->
+      <div class="section-card card" *ngIf="report.dimension3">
+        <div class="section-header">
+          <mat-icon>shopping_cart</mat-icon>
+          <h3>第三维度:采购项目</h3>
+        </div>
+        <div class="dimension-content">
+          <div class="dimension-section" *ngIf="report.dimension3.needs">
+            <h4>需求详情</h4>
+            <p>{{ report.dimension3.needs.details }}</p>
+            <div class="info-grid" *ngIf="report.dimension3.needs.orderType || report.dimension3.needs.usage">
+              <div class="info-item" *ngIf="report.dimension3.needs.orderType">
+                <span class="info-label">订单类型</span>
+                <span class="info-value">{{ report.dimension3.needs.orderType }}</span>
+              </div>
+              <div class="info-item" *ngIf="report.dimension3.needs.usage">
+                <span class="info-label">用途</span>
+                <span class="info-value">{{ report.dimension3.needs.usage }}</span>
+              </div>
+            </div>
+          </div>
+          <div class="dimension-section" *ngIf="report.dimension3.budget">
+            <h4>预算信息</h4>
+            <div class="info-grid">
+              <div class="info-item" *ngIf="report.dimension3.budget.amount">
+                <span class="info-label">预算金额</span>
+                <span class="info-value">{{ report.dimension3.budget.amount }}</span>
+              </div>
+              <div class="info-item" *ngIf="report.dimension3.budget.sensitivity">
+                <span class="info-label">价格敏感度</span>
+                <span class="info-value">{{ report.dimension3.budget.sensitivity }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Right Column -->
+    <div class="right-col">
+      <!-- Rating -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>star</mat-icon>
+          <h3>综合评级</h3>
+        </div>
+        <div class="rating-display">
+          <div class="score-circle" [class]="'score-' + getScoreLevel(report.rating.overallScore)">
+            <div class="score-value">{{ report.rating.overallScore }}</div>
+            <div class="score-label">分</div>
+          </div>
+          <div class="rating-info">
+            <div class="rating-level badge" [class]="'level-' + report.rating.level.toLowerCase()">
+              {{ report.rating.level }}级
+            </div>
+            <p class="rating-desc">综合评估得分</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- Risk -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>warning</mat-icon>
+          <h3>风险评估</h3>
+        </div>
+        <div class="risk-display">
+          <div class="risk-level" [class]="'risk-' + report.risk.level">
+            <mat-icon>{{ getRiskIcon(report.risk.level) }}</mat-icon>
+            <span>{{ getRiskLabel(report.risk.level) }}</span>
+          </div>
+          <div class="risk-factors" *ngIf="report.risk.factors.length">
+            <h4>风险因素</h4>
+            <ul>
+              <li *ngFor="let factor of report.risk.factors">{{ factor }}</li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <!-- Strategy -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>lightbulb</mat-icon>
+          <h3>销售策略</h3>
+        </div>
+        <div class="strategy-content">
+          <div class="strategy-approach">
+            <h4>跟进策略</h4>
+            <p>{{ report.strategy.approach }}</p>
+          </div>
+          <div class="strategy-actions" *ngIf="report.strategy.actions.length">
+            <h4>行动建议</h4>
+            <ul>
+              <li *ngFor="let action of report.strategy.actions">{{ action }}</li>
+            </ul>
+          </div>
+          <div class="strategy-timeline" *ngIf="report.strategy.timeline">
+            <h4>时间线</h4>
+            <p>{{ report.strategy.timeline }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="page-container" *ngIf="!lead || !report">
+  <div class="empty-state card">
+    <mat-icon>search_off</mat-icon>
+    <p>未找到背调报告</p>
+    <a mat-button (click)="goBack()">返回上一页</a>
+  </div>
+</div>
+

+ 78 - 0
lead-discovery/src/app/pages/bgcheck/bgcheck.component.scss

@@ -0,0 +1,78 @@
+.back-link {
+  display: inline-flex; align-items: center; gap: 4px;
+  color: #1976d2; text-decoration: none; font-size: 14px; margin-bottom: 16px; cursor: pointer;
+}
+.back-link:hover { text-decoration: underline; }
+.header-card { padding: 24px; margin-bottom: 20px; }
+.header-top { display: flex; justify-content: space-between; align-items: center; }
+.header-card h1 { margin: 0 0 12px; font-size: 22px; }
+.header-meta { display: flex; gap: 16px; flex-wrap: wrap; }
+.meta-item {
+  display: flex; align-items: center; gap: 4px;
+  font-size: 13px; color: #666;
+}
+.meta-item mat-icon { font-size: 16px; width: 16px; height: 16px; }
+.report-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
+.section-card { padding: 20px; margin-bottom: 0; }
+.section-header {
+  display: flex; align-items: center; gap: 8px; margin-bottom: 16px;
+  border-bottom: 1px solid #eee; padding-bottom: 12px;
+}
+.section-header mat-icon { color: #1976d2; }
+.section-header h3 { margin: 0; font-size: 16px; flex: 1; }
+.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
+.info-label { display: block; font-size: 11px; color: #888; margin-bottom: 2px; }
+.info-value { font-size: 14px; color: #333; font-weight: 500; }
+.completion-row { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; }
+.completion-item { display: flex; align-items: center; gap: 8px; }
+.completion-label { font-size: 12px; color: #666; min-width: 70px; }
+.completion-item mat-progress-bar { flex: 1; }
+.completion-value { font-size: 12px; color: #666; min-width: 40px; text-align: right; }
+.conclusion { margin-top: 16px; padding: 12px; background: #f5f5f5; border-radius: 6px; font-size: 14px; }
+.dimension-content { display: flex; flex-direction: column; gap: 16px; }
+.dimension-section h4 { font-size: 14px; color: #333; margin: 0 0 8px; }
+.chips-row { display: flex; gap: 6px; flex-wrap: wrap; }
+.chip {
+  padding: 4px 12px; background: #e3f2fd; color: #1565c0;
+  border-radius: 12px; font-size: 12px;
+}
+.rating-display {
+  display: flex; align-items: center; gap: 20px; padding: 20px;
+  background: #f8f9fa; border-radius: 8px;
+}
+.score-circle {
+  width: 80px; height: 80px; border-radius: 50%;
+  display: flex; flex-direction: column; align-items: center; justify-content: center;
+  background: #e0e0e0; color: #666;
+}
+.score-circle.score-high { background: #4caf50; color: #fff; }
+.score-circle.score-medium { background: #ff9800; color: #fff; }
+.score-circle.score-low { background: #f44336; color: #fff; }
+.score-value { font-size: 32px; font-weight: 700; line-height: 1; }
+.score-label { font-size: 12px; }
+.rating-info { flex: 1; }
+.rating-level { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 14px; font-weight: 600; }
+.level-a { background: #fff3e0; color: #e65100; }
+.level-b { background: #fffde7; color: #f9a825; }
+.level-c { background: #fafafa; color: #757575; }
+.rating-desc { font-size: 12px; color: #888; margin: 8px 0 0; }
+.risk-display { }
+.risk-level {
+  display: flex; align-items: center; gap: 8px; padding: 12px;
+  border-radius: 8px; font-size: 14px; font-weight: 600; margin-bottom: 12px;
+}
+.risk-level.risk-low { background: #e8f5e9; color: #2e7d32; }
+.risk-level.risk-medium { background: #fff3e0; color: #e65100; }
+.risk-level.risk-high { background: #ffebee; color: #c62828; }
+.risk-factors h4 { font-size: 13px; color: #666; margin: 12px 0 8px; }
+.risk-factors ul { margin: 0; padding-left: 20px; font-size: 13px; color: #555; }
+.strategy-content { display: flex; flex-direction: column; gap: 16px; }
+.strategy-content h4 { font-size: 13px; color: #666; margin: 0 0 8px; }
+.strategy-content p { font-size: 14px; color: #555; margin: 0; line-height: 1.6; }
+.strategy-content ul { margin: 0; padding-left: 20px; font-size: 13px; color: #555; }
+.empty-state {
+  display: flex; flex-direction: column; align-items: center; padding: 60px 20px; color: #999;
+}
+.empty-state mat-icon { font-size: 64px; width: 64px; height: 64px; color: #ddd; }
+@media (max-width: 900px) { .report-grid { grid-template-columns: 1fr; } }
+

+ 69 - 0
lead-discovery/src/app/pages/bgcheck/bgcheck.component.ts

@@ -0,0 +1,69 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Location } from '@angular/common';
+import { MatCardModule } from '@angular/material/card';
+import { MatIconModule } from '@angular/material/icon';
+import { MatButtonModule } from '@angular/material/button';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MockDataService } from '../../services/mock-data.service';
+import { Lead, BackgroundCheckReport } from '../../models/lead.model';
+
+@Component({
+  selector: 'app-bgcheck',
+  standalone: true,
+  imports: [
+    CommonModule,
+    MatCardModule, MatIconModule, MatButtonModule, MatChipsModule,
+    MatDividerModule, MatTabsModule, MatProgressBarModule
+  ],
+  templateUrl: './bgcheck.component.html',
+  styleUrls: ['./bgcheck.component.scss'],
+})
+export class BgCheckComponent implements OnInit {
+  lead: Lead | null = null;
+  report: BackgroundCheckReport | null = null;
+
+  constructor(
+    private route: ActivatedRoute,
+    private router: Router,
+    private location: Location,
+    private mockData: MockDataService
+  ) {}
+
+  ngOnInit() {
+    const id = this.route.snapshot.paramMap.get('id');
+    this.lead = this.mockData.leads.find(l => l.id === id) || null;
+    if (this.lead && this.lead.backgroundCheckReport) {
+      this.report = this.lead.backgroundCheckReport;
+    }
+  }
+
+  goBack() {
+    this.location.back();
+  }
+
+  getScoreLevel(score: number): string {
+    if (score >= 80) return 'high';
+    if (score >= 60) return 'medium';
+    return 'low';
+  }
+
+  getRiskIcon(level: string): string {
+    if (level === 'low') return 'check_circle';
+    if (level === 'medium') return 'warning';
+    return 'error';
+  }
+
+  getRiskLabel(level: string): string {
+    const labels: Record<string, string> = {
+      low: '低风险',
+      medium: '中风险',
+      high: '高风险'
+    };
+    return labels[level] || level;
+  }
+}

+ 1050 - 0
lead-discovery/src/app/pages/dashboard/dashboard.component.html

@@ -0,0 +1,1050 @@
+<div class="page-container">
+  <!-- Header -->
+  <div class="page-header">
+    <div>
+      <h1><mat-icon>auto_awesome</mat-icon> 线索获取工作台</h1>
+      <p class="subtitle">AI 每日自动处理新线索,您只需验证结果</p>
+    </div>
+    <div class="header-actions">
+      <button mat-stroked-button class="inbox-btn" (click)="openInbox()">
+        <mat-icon>inbox</mat-icon>
+        收件箱
+      </button>
+      <div class="header-date">
+        <mat-icon>today</mat-icon>
+        {{ today | date:'yyyy-MM-dd EEEE' }}
+      </div>
+    </div>
+  </div>
+
+  <!-- Stats Cards -->
+  <div class="stats-row">
+    <div class="stat-card new">
+      <div class="stat-icon-wrap"><mat-icon>fiber_new</mat-icon></div>
+      <div class="stat-info">
+        <span class="stat-value">{{ latestBatch?.newLeadsFound || 0 }}</span>
+        <span class="stat-label">今日新线索</span>
+      </div>
+    </div>
+    <div class="stat-card pending">
+      <div class="stat-icon-wrap"><mat-icon>pending_actions</mat-icon></div>
+      <div class="stat-info">
+        <span class="stat-value">{{ pendingCount }}</span>
+        <span class="stat-label">待验证</span>
+      </div>
+    </div>
+    <div class="stat-card verified">
+      <div class="stat-icon-wrap"><mat-icon>verified</mat-icon></div>
+      <div class="stat-info">
+        <span class="stat-value">{{ verifiedCount }}</span>
+        <span class="stat-label">已确认</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- AI Batch Status -->
+  <div class="batch-status card" *ngIf="latestBatch">
+    <div class="batch-header">
+      <mat-icon>smart_toy</mat-icon>
+      <span>AI 今日批次</span>
+      <span class="batch-time">{{ latestBatch.runAt | date:'HH:mm' }} 运行</span>
+      <span class="batch-badge" [class]="'status-' + latestBatch.status">
+        {{ latestBatch.status === 'completed' ? '✅ 已完成' : latestBatch.status === 'running' ? '⏳ 运行中' : '❌ 失败' }}
+      </span>
+    </div>
+    <div class="batch-stats">
+      <span>处理 <strong>{{ latestBatch.totalProcessed }}</strong> 条</span>
+      <span>发现 <strong>{{ latestBatch.newLeadsFound }}</strong> 条新线索</span>
+      <span>待验证 <strong>{{ latestBatch.pendingVerification }}</strong></span>
+    </div>
+  </div>
+
+  <!-- ===== Main Tab Area ===== -->
+  <mat-tab-group class="main-tabs" animationDuration="200ms" [(selectedIndex)]="activeTab">
+    <!-- Tab 1: 待验证线索 -->
+    <mat-tab>
+      <ng-template mat-tab-label>
+        <mat-icon>assignment</mat-icon>
+        <span class="tab-label-text">待验证线索</span>
+        <span class="tab-count" *ngIf="pendingLeads.length > 0">{{ pendingLeads.length }}</span>
+      </ng-template>
+
+      <div class="tab-content">
+        <div class="content-grid">
+          <!-- Left: Pending Verification List -->
+          <div class="left-col">
+            <div class="section-card card">
+              <div class="section-header">
+                <mat-icon>assignment</mat-icon>
+                <h3>AI 发现 · 需要您验证</h3>
+                <span class="count-badge">{{ pendingLeads.length }}</span>
+              </div>
+
+              <div class="lead-verify-list">
+                <div class="verify-card" *ngFor="let lead of pendingLeads"
+                     [class]="'persona-border-' + lead.persona.toLowerCase()">
+                  <div class="verify-top">
+                    <div class="verify-avatar" [style.background]="getPersonaColor(lead.persona)">
+                      <mat-icon>{{ getPersonaIcon(lead.persona) }}</mat-icon>
+                    </div>
+                    <div class="verify-info">
+                      <div class="verify-company">{{ lead.companyName }}</div>
+                      <div class="verify-tags">
+                        <span class="persona-tag" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
+                          {{ lead.persona }}-{{ lead.personaLabel }}
+                        </span>
+                        <span class="grade-tag" [class]="'grade-' + lead.valueGrade.toLowerCase()">
+                          {{ lead.valueGrade }}级
+                        </span>
+                        <span class="flag">{{ getFlag(lead.country) }}</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div class="verify-ai-reason">
+                    <mat-icon>psychology</mat-icon>
+                    <span>AI判断:{{ lead.personaLabel }} · {{ getPersonaStrategy(lead.persona) }}</span>
+                  </div>
+
+                  <div class="verify-keywords" *ngIf="lead.quickScreenResult.matchedKeywords?.length">
+                    <span class="keyword-chip" *ngFor="let kw of lead.quickScreenResult.matchedKeywords">{{ kw }}</span>
+                  </div>
+
+                  <div class="verify-actions">
+                    <button mat-raised-button color="primary" class="action-btn" (click)="confirmLead(lead)">
+                      <mat-icon>check</mat-icon> 确认
+                    </button>
+                    <button mat-stroked-button class="action-btn" (click)="editLead(lead)">
+                      <mat-icon>edit</mat-icon> 修改
+                    </button>
+                    <button mat-stroked-button color="warn" class="action-btn" (click)="dismissLead(lead)">
+                      <mat-icon>close</mat-icon> 无效
+                    </button>
+                    <button mat-icon-button [matTooltip]="'查看详情'" (click)="openLead(lead)">
+                      <mat-icon>open_in_new</mat-icon>
+                    </button>
+                    <button mat-icon-button [matTooltip]="'查看原始邮件'" (click)="showEmailDrawer(lead)"
+                            *ngIf="getEmailForLead(lead)">
+                      <mat-icon>email</mat-icon>
+                    </button>
+                  </div>
+                </div>
+
+                <div class="empty-verify" *ngIf="pendingLeads.length === 0">
+                  <mat-icon>celebration</mat-icon>
+                  <p>所有线索已验证完毕 🎉</p>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- Right: Persona Distribution + Activity + Quick Links -->
+          <div class="right-col">
+            <div class="section-card card">
+              <div class="section-header">
+                <mat-icon>donut_large</mat-icon>
+                <h3>本周画像分布</h3>
+              </div>
+              <div class="persona-dist-list">
+                <div class="dist-row" *ngFor="let p of personaDistribution">
+                  <div class="dist-label">
+                    <mat-icon [style.color]="p.color">{{ p.icon }}</mat-icon>
+                    <span>{{ p.id }}-{{ p.name }}</span>
+                  </div>
+                  <div class="dist-bar-wrap">
+                    <div class="dist-bar" [style.width.%]="p.percent" [style.background]="p.color"></div>
+                  </div>
+                  <span class="dist-count">{{ p.count }} ({{ p.percent }}%)</span>
+                </div>
+              </div>
+            </div>
+
+            <div class="section-card card">
+              <div class="section-header">
+                <mat-icon>history</mat-icon>
+                <h3>最近活动</h3>
+              </div>
+              <div class="activity-list">
+                <div class="activity-item" *ngFor="let a of recentActivities">
+                  <div class="activity-dot" [style.background]="a.color"></div>
+                  <div class="activity-content">
+                    <span class="activity-text">{{ a.text }}</span>
+                    <span class="activity-time">{{ a.time }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div> <!-- /right-col -->
+        </div> <!-- /content-grid -->
+      </div> <!-- /tab-content -->
+    </mat-tab>
+
+    <!-- Tab 2: 导入线索 -->
+    <mat-tab>
+      <ng-template mat-tab-label>
+        <mat-icon>cloud_upload</mat-icon>
+        <span class="tab-label-text">导入线索</span>
+        <span class="tab-count new" *ngIf="batchScreenResult">{{ batchScreenResult.entities.length }}</span>
+      </ng-template>
+
+      <div class="tab-content">
+
+        <!-- ===== Source Selector ===== -->
+        <div class="import-source-tabs">
+          <button class="source-tab" [class.active]="importSource === 'email'" (click)="importSource = 'email'">
+            <mat-icon>email</mat-icon>
+            <span>邮件导入</span>
+          </button>
+          <button class="source-tab" [class.active]="importSource === 'exhibition'" (click)="importSource = 'exhibition'">
+            <mat-icon>dynamic_feed</mat-icon>
+            <span>批量筛查</span>
+            <span class="source-tab-badge new" *ngIf="batchScreenResult">{{ batchScreenResult.entities.length }}</span>
+          </button>
+          <button class="source-tab" [class.active]="importSource === 'platform'" (click)="importSource = 'platform'">
+            <mat-icon>cloud_sync</mat-icon>
+            <span>平台询盘同步</span>
+            <span class="source-tab-badge new" *ngIf="!platformSynced">NEW</span>
+          </button>
+        </div>
+
+        <!-- ===== Source 1: 邮件导入(原有) ===== -->
+        <div class="import-section card" *ngIf="importSource === 'email'">
+          <div class="section-title-row">
+            <mat-icon class="section-icon">email</mat-icon>
+            <span>导入邮件 / 手动录入</span>
+          </div>
+
+          <!-- EML Import Banner -->
+          <div class="import-banner" (click)="importFromEmail()" [class.disabled]="isImporting">
+            <div class="import-drop-zone">
+              <mat-icon class="upload-icon">cloud_upload</mat-icon>
+              <div class="drop-text">
+                <span class="drop-main">点击选择 .eml 邮件文件</span>
+                <span class="drop-hint">自动提取邮件中的公司信息和产品线索</span>
+              </div>
+              <button mat-stroked-button color="accent" class="import-btn" [disabled]="isImporting">
+                <mat-icon>email</mat-icon>
+                邮件导入(推荐)
+              </button>
+            </div>
+          </div>
+
+          <!-- Import Progress -->
+          <div *ngIf="isImporting" class="import-progress-section">
+            <mat-progress-bar mode="determinate" [value]="importProgress"></mat-progress-bar>
+            <div class="import-progress-info">
+              <mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
+              <span>{{ importStepMessage }}</span>
+            </div>
+          </div>
+
+          <!-- Linked Email Status -->
+          <div *ngIf="importedEmailId" class="linked-status">
+            <mat-icon>link</mat-icon>
+            <span>已关联邮件数据(ID: {{ importedEmailId }})</span>
+            <button mat-icon-button class="clear-import-btn" (click)="clearImport()" matTooltip="清除导入">
+              <mat-icon>close</mat-icon>
+            </button>
+          </div>
+
+          <!-- Auto-filled Form -->
+          <div class="import-form-grid">
+            <div class="form-item">
+              <div class="label">公司名称<span class="required">*</span></div>
+              <input class="form-input" [(ngModel)]="importData.companyName" placeholder="请输入公司名称">
+            </div>
+            <div class="form-item">
+              <div class="label">关键联系人</div>
+              <input class="form-input" [(ngModel)]="importData.contactName" placeholder="请输入客户名称,如:John Smith">
+              <div *ngIf="importData.roleDescription" class="hint">
+                <mat-icon class="hint-icon">work</mat-icon>
+                <span>参考职位:{{ importData.roleDescription }}</span>
+              </div>
+            </div>
+            <div class="form-item">
+              <div class="label">邮箱地址</div>
+              <input class="form-input" [(ngModel)]="importData.email" placeholder="name&#64;company.com">
+            </div>
+            <div class="form-item">
+              <div class="label">公司官网(选填)</div>
+              <input class="form-input" [(ngModel)]="importData.website" placeholder="支持从邮箱域名自动填充">
+              <div class="hint">
+                <mat-icon class="hint-icon">info</mat-icon>
+                <span>支持自动从邮箱提取域名,可手动修改</span>
+              </div>
+            </div>
+          </div>
+
+          <!-- Product Demands Preview -->
+          <div *ngIf="importedProducts.length > 0" class="product-demands-section">
+            <div class="demands-header">
+              <mat-icon>inventory_2</mat-icon>
+              <span>提取的产品需求 ({{ importedProducts.length }})</span>
+              <span class="demands-source-tag" *ngIf="importedAttachmentStats">
+                正文 {{ importedAttachmentStats.bodyAnalyzed ? '✓' : '✗' }} ·
+                附件 {{ importedAttachmentStats.analyzed }}/{{ importedAttachmentStats.total }}
+              </span>
+            </div>
+            <div class="demands-grid">
+              <div class="demand-card" *ngFor="let p of importedProducts; let i = index">
+                <div class="demand-index">{{ i + 1 }}</div>
+                <div class="demand-info">
+                  <div class="demand-name">{{ p.productName }}</div>
+                  <div class="demand-meta">
+                    <span *ngIf="p.quantity" class="meta-tag">📦 {{ p.quantity }}</span>
+                    <span *ngIf="p.moq" class="meta-tag">MOQ: {{ p.moq }}</span>
+                    <span *ngIf="p.specifications" class="meta-tag">{{ p.specifications }}</span>
+                  </div>
+                  <div class="demand-source">来源: {{ p.source }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!-- Info Box -->
+          <div class="info-box">
+            <div class="info-title">
+              <mat-icon class="info-icon">info</mat-icon>
+              <span>产品清单来源说明</span>
+            </div>
+            <div class="info-content">
+              <div class="info-item">📧 邮件正文及附件中的产品描述</div>
+              <div class="info-item">🌐 公司官网的产品页面</div>
+            </div>
+          </div>
+
+          <!-- Action Buttons -->
+          <div class="import-actions">
+            <button mat-raised-button color="primary"
+                    (click)="importAndScreen()"
+                    [disabled]="!importData.companyName || isImporting || isScreening">
+              <mat-icon>bolt</mat-icon>
+              快速筛选并创建线索
+            </button>
+          </div>
+
+          <!-- Screening Progress -->
+          <div *ngIf="isScreening" class="screening-progress card">
+            <mat-progress-spinner mode="indeterminate" diameter="36"></mat-progress-spinner>
+            <div class="progress-text">
+              <h4>AI 快速筛选中...</h4>
+              <p>{{ screeningStep }}</p>
+            </div>
+          </div>
+
+          <!-- Screen Result -->
+          <div *ngIf="importScreenResult" class="import-result">
+            <app-screen-result-card
+              [result]="importScreenResult"
+              (createLead)="onCreateImportLead()"
+              (dismiss)="importScreenResult = null">
+            </app-screen-result-card>
+          </div>
+        </div>
+
+        <!-- ===== Source 2: 展会批量导入 & 批量筛查(合并) ===== -->
+        <div class="import-section card" *ngIf="importSource === 'exhibition'">
+          <div class="section-title-row">
+            <mat-icon class="section-icon">groups</mat-icon>
+            <span>批量线索导入 & 筛查</span>
+          </div>
+
+          <div class="source-description">
+            <div class="desc-icon-wrap exhibition">
+              <mat-icon>event</mat-icon>
+            </div>
+            <div class="desc-text">
+              <strong>适用场景</strong>:展会名片、截图、聊天记录、CSV 文件或直接粘贴多条线索信息。
+              AI 自动解析 → 主体识别去重 → 画像分类 → 价值评级 → 产品匹配 → 生成跟进建议。
+            </div>
+          </div>
+
+          <!-- ===== 输入区(未筛查时显示) ===== -->
+          <ng-container *ngIf="!batchScreenResult && !isExhibitionImporting && !exhibitionImported">
+
+            <!-- 上传按钮组 -->
+            <div class="batch-input-methods">
+              <div class="batch-method-card" (click)="importBatchCSV()">
+                <mat-icon>upload_file</mat-icon>
+                <span>CSV / Excel 文件</span>
+                <small>批量导入表格数据</small>
+              </div>
+              <div class="batch-method-card" (click)="importBatchOCR()">
+                <mat-icon>photo_camera</mat-icon>
+                <span>截图 / 名片 OCR</span>
+                <small>上传图片自动识别</small>
+              </div>
+              <div class="batch-method-card" (click)="importExhibitionCSV()">
+                <mat-icon>auto_awesome</mat-icon>
+                <span>展会模拟导入</span>
+                <small>模拟大批量 AI 处理</small>
+              </div>
+            </div>
+
+            <!-- 文本粘贴区 -->
+            <div class="batch-text-area">
+              <div class="batch-text-label">
+                <mat-icon>edit_note</mat-icon>
+                <span>或直接粘贴线索信息(每行一条,支持 "公司名 邮箱" 或纯邮箱列表)</span>
+              </div>
+              <textarea class="batch-textarea"
+                        [(ngModel)]="batchTextInput"
+                        placeholder="示例:&#10;Acme Medical john@acmemedical.com&#10;info@safetyfirst-uk.co.uk&#10;WildGear GmbH alex@wildgear.de&#10;buyer@newcompany.com"
+                        rows="5"></textarea>
+              <div class="batch-text-actions">
+                <button mat-raised-button color="primary" (click)="parseBatchText()" [disabled]="!batchTextInput.trim()">
+                  <mat-icon>manage_search</mat-icon> 解析文本
+                </button>
+                <button mat-stroked-button (click)="parseBatchEmails()" [disabled]="!batchTextInput.trim()">
+                  <mat-icon>alternate_email</mat-icon> 按邮箱解析
+                </button>
+              </div>
+            </div>
+
+            <!-- 解析结果预览表格 -->
+            <div class="batch-preview" *ngIf="batchParsedItems.length > 0">
+              <div class="batch-preview-header">
+                <mat-icon>checklist</mat-icon>
+                <h4>解析结果预览</h4>
+                <span class="batch-count">{{ batchParsedItems.length }} 条</span>
+                <span class="batch-count duplicate">已选 {{ batchSelectedItems.size }} 条</span>
+                <div class="batch-preview-actions">
+                  <button mat-icon-button matTooltip="全选/取消" (click)="toggleBatchSelectAll()">
+                    <mat-icon>{{ batchSelectedItems.size === batchParsedItems.length ? 'deselect' : 'select_all' }}</mat-icon>
+                  </button>
+                  <button mat-icon-button matTooltip="清空" (click)="clearBatch()">
+                    <mat-icon>delete_sweep</mat-icon>
+                  </button>
+                </div>
+              </div>
+
+              <div class="batch-table-wrap">
+                <table class="batch-table">
+                  <thead>
+                    <tr>
+                      <th class="col-check"></th>
+                      <th>公司名</th>
+                      <th>邮箱</th>
+                      <th>域名</th>
+                      <th>联系人</th>
+                      <th>置信度</th>
+                      <th>来源</th>
+                      <th></th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr *ngFor="let item of batchParsedItems; let i = index"
+                        [class.selected]="batchSelectedItems.has(i)"
+                        [class.low-confidence]="item.parseConfidence < 50">
+                      <td class="col-check">
+                        <input type="checkbox" [checked]="batchSelectedItems.has(i)" (change)="toggleBatchItem(i)">
+                      </td>
+                      <td>{{ item.parsedCompanyName || '—' }}</td>
+                      <td>{{ item.parsedEmail || '—' }}</td>
+                      <td>{{ item.parsedDomain || '—' }}</td>
+                      <td>{{ item.parsedContactName || '—' }}</td>
+                      <td>
+                        <span class="confidence-badge" [class]="getConfidenceClass(item.parseConfidence)">
+                          {{ item.parseConfidence }}%
+                        </span>
+                      </td>
+                      <td><span class="source-tag">{{ item.parseSource }}</span></td>
+                      <td>
+                        <button mat-icon-button matTooltip="移除" (click)="removeBatchItem(i)">
+                          <mat-icon>close</mat-icon>
+                        </button>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+
+              <div class="batch-execute-bar">
+                <button mat-raised-button color="primary" class="batch-execute-btn"
+                        (click)="executeBatchScreen()"
+                        [disabled]="batchSelectedItems.size === 0 || batchProcessing">
+                  <mat-icon>bolt</mat-icon>
+                  批量执行初步筛查({{ batchSelectedItems.size }} 条)
+                </button>
+              </div>
+            </div>
+
+            <!-- 说明 -->
+            <div class="info-box" *ngIf="batchParsedItems.length === 0">
+              <div class="info-title">
+                <mat-icon class="info-icon">lightbulb</mat-icon>
+                <span>AI 批量处理流程</span>
+              </div>
+              <div class="info-content">
+                <div class="info-item">📋 <strong>Step 1</strong>:解析输入(CSV / 名片 OCR / 粘贴文本),提取公司、联系人、邮箱</div>
+                <div class="info-item">🔄 <strong>Step 2</strong>:主体识别 & 去重 — 域名+公司名归并到同一主体</div>
+                <div class="info-item">🤖 <strong>Step 3</strong>:AI 画像分类(P1-P6)+ 价值评级(S/A/B/C)</div>
+                <div class="info-item">📦 <strong>Step 4</strong>:产品匹配 + 差异化信息获取</div>
+                <div class="info-item">📌 <strong>Step 5</strong>:输出待确认列表,逐条确认后进入深度背调</div>
+              </div>
+            </div>
+
+          </ng-container>
+
+          <!-- ===== 批量筛查进度 ===== -->
+          <div *ngIf="batchProcessing" class="batch-import-progress">
+            <div class="batch-progress-header">
+              <mat-icon class="spin-icon">autorenew</mat-icon>
+              <strong>AI 批量筛查中...</strong>
+              <span class="batch-progress-pct">{{ batchProgress }}%</span>
+            </div>
+            <mat-progress-bar mode="determinate" [value]="batchProgress" color="primary"></mat-progress-bar>
+            <div class="batch-progress-steps">
+              <div class="batch-step" [class.done]="batchProgress >= 10" [class.active]="batchProgress > 0 && batchProgress < 10">
+                <mat-icon>{{ batchProgress >= 10 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>解析输入</span>
+              </div>
+              <div class="batch-step" [class.done]="batchProgress >= 30" [class.active]="batchProgress >= 10 && batchProgress < 30">
+                <mat-icon>{{ batchProgress >= 30 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>主体去重</span>
+              </div>
+              <div class="batch-step" [class.done]="batchProgress >= 50" [class.active]="batchProgress >= 30 && batchProgress < 50">
+                <mat-icon>{{ batchProgress >= 50 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>AI 画像分类</span>
+              </div>
+              <div class="batch-step" [class.done]="batchProgress >= 90" [class.active]="batchProgress >= 50 && batchProgress < 90">
+                <mat-icon>{{ batchProgress >= 90 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>价值评估</span>
+              </div>
+              <div class="batch-step" [class.done]="batchProgress >= 100" [class.active]="batchProgress >= 90 && batchProgress < 100">
+                <mat-icon>{{ batchProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>生成结果</span>
+              </div>
+            </div>
+            <div class="batch-progress-detail">{{ batchStepMessage }}</div>
+          </div>
+
+          <!-- ===== 展会模拟导入进度 ===== -->
+          <div *ngIf="isExhibitionImporting" class="batch-import-progress">
+            <div class="batch-progress-header">
+              <mat-icon class="spin-icon">autorenew</mat-icon>
+              <strong>AI 批量分析中...</strong>
+              <span class="batch-progress-pct">{{ exhibitionProgress }}%</span>
+            </div>
+            <mat-progress-bar mode="determinate" [value]="exhibitionProgress" color="primary"></mat-progress-bar>
+            <div class="batch-progress-steps">
+              <div class="batch-step" [class.done]="exhibitionProgress >= 15" [class.active]="exhibitionProgress > 0 && exhibitionProgress < 15">
+                <mat-icon>{{ exhibitionProgress >= 15 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>解析文件</span>
+              </div>
+              <div class="batch-step" [class.done]="exhibitionProgress >= 40" [class.active]="exhibitionProgress >= 15 && exhibitionProgress < 40">
+                <mat-icon>{{ exhibitionProgress >= 40 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>AI 画像分类</span>
+              </div>
+              <div class="batch-step" [class.done]="exhibitionProgress >= 65" [class.active]="exhibitionProgress >= 40 && exhibitionProgress < 65">
+                <mat-icon>{{ exhibitionProgress >= 65 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>价值评级</span>
+              </div>
+              <div class="batch-step" [class.done]="exhibitionProgress >= 85" [class.active]="exhibitionProgress >= 65 && exhibitionProgress < 85">
+                <mat-icon>{{ exhibitionProgress >= 85 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>产品匹配</span>
+              </div>
+              <div class="batch-step" [class.done]="exhibitionProgress >= 100" [class.active]="exhibitionProgress >= 85 && exhibitionProgress < 100">
+                <mat-icon>{{ exhibitionProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>生成跟进建议</span>
+              </div>
+            </div>
+            <div class="batch-progress-detail">{{ exhibitionStepMessage }}</div>
+          </div>
+
+          <!-- ===== 展会模拟导入结果 ===== -->
+          <div *ngIf="exhibitionImported && !isExhibitionImporting" class="batch-import-result">
+            <div class="batch-result-header">
+              <mat-icon>celebration</mat-icon>
+              <strong>批量线索导入完成</strong>
+            </div>
+            <div class="batch-result-stats">
+              <div class="batch-stat-item">
+                <span class="batch-stat-num">{{ exhibitionResult.total }}</span>
+                <span class="batch-stat-label">导入总数</span>
+              </div>
+              <div class="batch-stat-item highlight-s">
+                <span class="batch-stat-num">{{ exhibitionResult.sCount }}</span>
+                <span class="batch-stat-label">S 级线索</span>
+              </div>
+              <div class="batch-stat-item highlight-a">
+                <span class="batch-stat-num">{{ exhibitionResult.aCount }}</span>
+                <span class="batch-stat-label">A 级线索</span>
+              </div>
+              <div class="batch-stat-item">
+                <span class="batch-stat-num">{{ exhibitionResult.bCount }}</span>
+                <span class="batch-stat-label">B 级线索</span>
+              </div>
+              <div class="batch-stat-item dim">
+                <span class="batch-stat-num">{{ exhibitionResult.cCount }}</span>
+                <span class="batch-stat-label">C 级/无效</span>
+              </div>
+            </div>
+            <div class="batch-result-personas">
+              <span class="persona-chip" *ngFor="let p of exhibitionResult.personas">
+                {{ p.label }} × {{ p.count }}
+              </span>
+            </div>
+            <div class="batch-result-actions">
+              <button mat-raised-button color="primary" (click)="router.navigate(['/list'])">
+                <mat-icon>visibility</mat-icon> 查看线索看板
+              </button>
+              <button mat-stroked-button (click)="activeTab = 0">
+                <mat-icon>assignment</mat-icon> 去验证 ({{ exhibitionResult.sCount + exhibitionResult.aCount }})
+              </button>
+            </div>
+          </div>
+
+          <!-- ===== 第一轮筛查结果 — 待确认列表 ===== -->
+          <div *ngIf="batchScreenResult && !batchProcessing" class="batch-screen-result-section">
+            <div class="section-header">
+              <mat-icon>fact_check</mat-icon>
+              <h3>第一轮筛查结果 — 待确认</h3>
+              <span class="section-hint">请确认 AI 的判断结果,确认后可进入第二轮深度背调</span>
+            </div>
+
+            <div class="batch-summary-bar">
+              <div class="summary-item">
+                <strong>{{ batchScreenResult.entities.length }}</strong>
+                <span>个主体</span>
+              </div>
+              <div class="summary-item new-entities">
+                <strong>{{ batchScreenResult.newEntities }}</strong>
+                <span>新主体</span>
+              </div>
+              <div class="summary-item duplicate-entities">
+                <strong>{{ batchScreenResult.duplicateEntities }}</strong>
+                <span>已有主体</span>
+              </div>
+              <div class="summary-actions">
+                <button mat-stroked-button (click)="clearBatch()">
+                  <mat-icon>refresh</mat-icon> 重新筛查
+                </button>
+              </div>
+            </div>
+
+            <div class="batch-entity-list">
+              <div class="batch-entity-card" *ngFor="let item of batchScreenResult.entities"
+                   [class.is-duplicate]="item.isDuplicate">
+                <div class="entity-card-top">
+                  <div class="entity-avatar" [style.background]="getPersonaColor(item.screenResult.persona)">
+                    <mat-icon>{{ getPersonaIcon(item.screenResult.persona) }}</mat-icon>
+                  </div>
+                  <div class="entity-info">
+                    <div class="entity-company">{{ item.entity.primaryCompanyName || item.screenResult.companyName }}</div>
+                    <div class="entity-tags">
+                      <span class="persona-tag" [style.background]="getPersonaBg(item.screenResult.persona)" [style.color]="getPersonaColor(item.screenResult.persona)">
+                        {{ item.screenResult.persona }}-{{ item.screenResult.personaLabel }}
+                      </span>
+                      <span class="grade-tag" [class]="getGradeClass(item.screenResult.valueGrade)">
+                        {{ item.screenResult.valueGrade }}级
+                      </span>
+                      <span class="flag" *ngIf="item.screenResult.country !== 'Unknown'">{{ getFlag(item.screenResult.country) }}</span>
+                    </div>
+                  </div>
+                  <div class="entity-status">
+                    <span class="entity-badge new" *ngIf="!item.isDuplicate">新主体</span>
+                    <span class="entity-badge duplicate" *ngIf="item.isDuplicate">
+                      已有记录
+                      <span class="dup-count" *ngIf="item.entity.existingLeadIds.length">({{ item.entity.existingLeadIds.length }}条)</span>
+                    </span>
+                  </div>
+                </div>
+
+                <div class="entity-duplicate-warn" *ngIf="item.isDuplicate">
+                  <mat-icon>info</mat-icon>
+                  <span>{{ item.matchInfo }}</span>
+                </div>
+
+                <div class="entity-details">
+                  <div class="entity-detail-row" *ngIf="item.entity.primaryDomain">
+                    <mat-icon>language</mat-icon>
+                    <span>{{ item.entity.primaryDomain }}</span>
+                  </div>
+                  <div class="entity-detail-row" *ngIf="item.entity.contacts.length > 0">
+                    <mat-icon>people</mat-icon>
+                    <span>联系人: {{ item.entity.contacts.length }}
+                      <span class="contact-list">({{ getContactSummary(item.entity.contacts) }})</span>
+                    </span>
+                  </div>
+                </div>
+
+                <div class="entity-keywords" *ngIf="item.screenResult.matchedKeywords?.length">
+                  <span class="keyword-chip" *ngFor="let kw of item.screenResult.matchedKeywords">{{ kw }}</span>
+                </div>
+
+                <div class="entity-actions">
+                  <button mat-raised-button color="primary" class="action-btn">
+                    <mat-icon>check</mat-icon> 确认
+                  </button>
+                  <button mat-stroked-button class="action-btn">
+                    <mat-icon>edit</mat-icon> 修改画像
+                  </button>
+                  <button mat-stroked-button color="warn" class="action-btn">
+                    <mat-icon>close</mat-icon> 归档
+                  </button>
+                </div>
+              </div>
+            </div>
+          </div>
+
+        </div>
+
+        <!-- ===== Source 3: 平台询盘同步 ===== -->
+        <div class="import-section card" *ngIf="importSource === 'platform'">
+          <div class="section-title-row">
+            <mat-icon class="section-icon">cloud_sync</mat-icon>
+            <span>B2B 平台询盘自动同步</span>
+          </div>
+
+          <div class="source-description">
+            <div class="desc-icon-wrap platform">
+              <mat-icon>language</mat-icon>
+            </div>
+            <div class="desc-text">
+              <strong>典型场景</strong>:系统通过 API 对接阿里巴巴国际站、环球资源、Made-in-China 等 B2B 平台,
+              每日定时拉取新询盘,纳入凌晨 AI 批量处理任务。销售上班后直接看到 AI 已分析好的平台线索。
+            </div>
+          </div>
+
+          <!-- Platform Cards -->
+          <div class="platform-grid">
+            <div class="platform-card" *ngFor="let plat of platformList" [class.connected]="plat.connected">
+              <div class="platform-card-header">
+                <div class="platform-logo" [style.background]="plat.color">
+                  <mat-icon>{{ plat.icon }}</mat-icon>
+                </div>
+                <div class="platform-info">
+                  <div class="platform-name">{{ plat.name }}</div>
+                  <div class="platform-desc">{{ plat.description }}</div>
+                </div>
+                <div class="platform-status">
+                  <span class="platform-status-tag" [class.connected]="plat.connected" [class.disconnected]="!plat.connected">
+                    {{ plat.connected ? '✅ 已对接' : '⏳ 待对接' }}
+                  </span>
+                </div>
+              </div>
+              <div class="platform-card-body" *ngIf="plat.connected">
+                <div class="platform-meta-row">
+                  <mat-icon>schedule</mat-icon>
+                  <span>同步频率:<strong>{{ plat.syncFrequency }}</strong></span>
+                </div>
+                <div class="platform-meta-row">
+                  <mat-icon>update</mat-icon>
+                  <span>上次同步:<strong>{{ plat.lastSync }}</strong></span>
+                </div>
+                <div class="platform-meta-row">
+                  <mat-icon>leaderboard</mat-icon>
+                  <span>本月拉取:<strong>{{ plat.monthlyCount }} 条询盘</strong></span>
+                </div>
+              </div>
+              <div class="platform-card-body" *ngIf="!plat.connected">
+                <div class="platform-pending-msg">
+                  <mat-icon>info</mat-icon>
+                  <span>需要在平台开放平台获取 API Key 后对接</span>
+                </div>
+              </div>
+              <div class="platform-card-actions">
+                <button mat-raised-button [color]="plat.connected ? 'primary' : 'basic'"
+                        (click)="syncPlatform(plat)"
+                        [disabled]="!plat.connected || isPlatformSyncing">
+                  <mat-icon>{{ plat.connected ? 'sync' : 'link' }}</mat-icon>
+                  {{ plat.connected ? '立即同步' : '配置对接' }}
+                </button>
+              </div>
+            </div>
+          </div>
+
+          <!-- Platform Sync Progress -->
+          <div *ngIf="isPlatformSyncing" class="batch-import-progress platform-sync-progress">
+            <div class="batch-progress-header">
+              <mat-icon class="spin-icon">sync</mat-icon>
+              <strong>正在从 {{ syncingPlatformName }} 拉取询盘...</strong>
+              <span class="batch-progress-pct">{{ platformSyncProgress }}%</span>
+            </div>
+            <mat-progress-bar mode="determinate" [value]="platformSyncProgress" color="accent"></mat-progress-bar>
+            <div class="batch-progress-steps">
+              <div class="batch-step" [class.done]="platformSyncProgress >= 20" [class.active]="platformSyncProgress > 0 && platformSyncProgress < 20">
+                <mat-icon>{{ platformSyncProgress >= 20 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>连接平台 API</span>
+              </div>
+              <div class="batch-step" [class.done]="platformSyncProgress >= 45" [class.active]="platformSyncProgress >= 20 && platformSyncProgress < 45">
+                <mat-icon>{{ platformSyncProgress >= 45 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>拉取新询盘</span>
+              </div>
+              <div class="batch-step" [class.done]="platformSyncProgress >= 70" [class.active]="platformSyncProgress >= 45 && platformSyncProgress < 70">
+                <mat-icon>{{ platformSyncProgress >= 70 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>AI 分析 & 分类</span>
+              </div>
+              <div class="batch-step" [class.done]="platformSyncProgress >= 100" [class.active]="platformSyncProgress >= 70 && platformSyncProgress < 100">
+                <mat-icon>{{ platformSyncProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
+                <span>入库 & 生成建议</span>
+              </div>
+            </div>
+            <div class="batch-progress-detail">
+              {{ platformStepMessage }}
+            </div>
+          </div>
+
+          <!-- Platform Sync Result -->
+          <div *ngIf="platformSynced && !isPlatformSyncing" class="batch-import-result platform-result">
+            <div class="batch-result-header">
+              <mat-icon>cloud_done</mat-icon>
+              <strong>平台询盘同步完成</strong>
+            </div>
+            <div class="batch-result-stats">
+              <div class="batch-stat-item">
+                <span class="batch-stat-num">{{ platformResult.total }}</span>
+                <span class="batch-stat-label">拉取询盘</span>
+              </div>
+              <div class="batch-stat-item highlight-s">
+                <span class="batch-stat-num">{{ platformResult.sCount }}</span>
+                <span class="batch-stat-label">S 级线索</span>
+              </div>
+              <div class="batch-stat-item highlight-a">
+                <span class="batch-stat-num">{{ platformResult.aCount }}</span>
+                <span class="batch-stat-label">A 级线索</span>
+              </div>
+              <div class="batch-stat-item">
+                <span class="batch-stat-num">{{ platformResult.bCount }}</span>
+                <span class="batch-stat-label">B 级线索</span>
+              </div>
+              <div class="batch-stat-item dim">
+                <span class="batch-stat-num">{{ platformResult.duplicates }}</span>
+                <span class="batch-stat-label">重复/已有</span>
+              </div>
+            </div>
+            <div class="batch-result-actions">
+              <button mat-raised-button color="primary" (click)="router.navigate(['/list'])">
+                <mat-icon>visibility</mat-icon> 查看线索看板
+              </button>
+              <button mat-stroked-button (click)="activeTab = 0">
+                <mat-icon>assignment</mat-icon> 去验证
+              </button>
+            </div>
+          </div>
+
+          <!-- Platform Schedule Info -->
+          <div class="platform-schedule-card" *ngIf="!isPlatformSyncing && !platformSynced">
+            <div class="schedule-header">
+              <mat-icon>event_repeat</mat-icon>
+              <strong>自动同步计划</strong>
+            </div>
+            <div class="schedule-timeline">
+              <div class="schedule-item">
+                <div class="schedule-time">🕛 每日 00:00</div>
+                <div class="schedule-desc">自动拉取已对接平台的所有新询盘,纳入 AI 凌晨批量处理</div>
+              </div>
+              <div class="schedule-item">
+                <div class="schedule-time">🌅 每日 08:00</div>
+                <div class="schedule-desc">AI 分析完毕,结果推送到"待验证线索"列表,等待销售确认</div>
+              </div>
+              <div class="schedule-item">
+                <div class="schedule-time">📊 每周一</div>
+                <div class="schedule-desc">生成本周平台询盘汇总分析报告(画像分布、价值评级、转化建议)</div>
+              </div>
+            </div>
+          </div>
+
+          <!-- Platform Info Box -->
+          <div class="info-box" *ngIf="!isPlatformSyncing && !platformSynced">
+            <div class="info-title">
+              <mat-icon class="info-icon">lightbulb</mat-icon>
+              <span>平台询盘 AI 处理流程</span>
+            </div>
+            <div class="info-content">
+              <div class="info-item">🔗 <strong>Step 1</strong>:通过平台 API 自动拉取过去 24 小时的新询盘</div>
+              <div class="info-item">📧 <strong>Step 2</strong>:解析询盘内容(产品需求、数量、规格、买家信息)</div>
+              <div class="info-item">🤖 <strong>Step 3</strong>:AI 画像分类 + 五维评分 + 价值评级</div>
+              <div class="info-item">🔄 <strong>Step 4</strong>:去重(与现有线索比对邮箱/公司名)</div>
+              <div class="info-item">📌 <strong>Step 5</strong>:新线索入库,标记"待验证",推送到工作台</div>
+            </div>
+          </div>
+        </div>
+
+      </div>
+    </mat-tab>
+  </mat-tab-group>
+
+  <!-- ===== Email Drawer Overlay ===== -->
+  <div class="drawer-overlay" *ngIf="drawerEmail" (click)="closeEmailDrawer()"></div>
+  <div class="email-drawer" [class.open]="drawerEmail">
+    <div class="drawer-header" *ngIf="drawerEmail">
+      <h3>📧 原始邮件</h3>
+      <button mat-icon-button (click)="closeEmailDrawer()">
+        <mat-icon>close</mat-icon>
+      </button>
+    </div>
+    <div class="drawer-body" *ngIf="drawerEmail">
+      <div class="drawer-meta">
+        <div class="drawer-meta-row">
+          <span class="drawer-label">发件人</span>
+          <span>{{ drawerEmail.fromName }} &lt;{{ drawerEmail.from }}&gt;</span>
+        </div>
+        <div class="drawer-meta-row">
+          <span class="drawer-label">收件人</span>
+          <span>{{ drawerEmail.to }}</span>
+        </div>
+        <div class="drawer-meta-row" *ngIf="drawerEmail.cc">
+          <span class="drawer-label">抄送</span>
+          <span>{{ drawerEmail.cc }}</span>
+        </div>
+        <div class="drawer-meta-row">
+          <span class="drawer-label">时间</span>
+          <span>{{ drawerEmail.receivedAt | date:'yyyy-MM-dd HH:mm:ss' }}</span>
+        </div>
+        <div class="drawer-meta-row">
+          <span class="drawer-label">主题</span>
+          <span class="drawer-subject">{{ drawerEmail.subject }}</span>
+        </div>
+      </div>
+
+      <!-- Attachments -->
+      <div class="drawer-attachments" *ngIf="drawerEmail.attachments.length > 0">
+        <div class="drawer-att-header">
+          <mat-icon>attach_file</mat-icon>
+          附件 ({{ drawerEmail.attachments.length }})
+          <span class="att-total-size">共 {{ getTotalSize(drawerEmail.attachments) }}</span>
+        </div>
+        <div class="drawer-att-grid">
+          <div class="drawer-att-card" *ngFor="let att of drawerEmail.attachments" (click)="downloadAttachment(att)">
+            <div class="att-icon-wrap" [class]="getAttColorClass(att.mimeType)">
+              <mat-icon>{{ att.icon }}</mat-icon>
+            </div>
+            <div class="att-info">
+              <span class="att-name" [title]="att.fileName">{{ att.fileName }}</span>
+              <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
+            </div>
+            <mat-icon class="att-download-icon">download</mat-icon>
+          </div>
+        </div>
+      </div>
+
+      <mat-divider></mat-divider>
+
+      <div class="drawer-email-body">
+        <pre class="email-body-text">{{ drawerEmail.body }}</pre>
+      </div>
+    </div>
+  </div>
+
+</div>
+
+<!-- ===== Inbox Full-Screen Overlay (outside page-container to avoid sidenav clipping) ===== -->
+<div class="inbox-overlay" *ngIf="inboxOpen" (click)="closeInbox()"></div>
+<div class="inbox-modal" *ngIf="inboxOpen">
+  <!-- Inbox Header -->
+  <div class="inbox-header">
+      <div class="inbox-title">
+        <mat-icon>inbox</mat-icon>
+        <h2>收件箱</h2>
+        <span class="inbox-count">{{ emails.length }} 封邮件</span>
+      </div>
+      <div class="inbox-header-actions">
+        <button mat-icon-button (click)="closeInbox()" matTooltip="关闭">
+          <mat-icon>close</mat-icon>
+        </button>
+      </div>
+    </div>
+
+    <!-- Inbox Body: List + Detail -->
+    <div class="inbox-body">
+      <!-- Left: Email List -->
+      <div class="inbox-list">
+        <div class="inbox-list-item" *ngFor="let email of emails"
+             [class.active]="selectedInboxEmail?.id === email.id"
+             (click)="selectInboxEmail(email)">
+          <div class="inbox-item-avatar">
+            <mat-icon>mail</mat-icon>
+          </div>
+          <div class="inbox-item-content">
+            <div class="inbox-item-top">
+              <span class="inbox-item-sender">{{ email.fromName }}</span>
+              <span class="inbox-item-time">{{ email.receivedAt | date:'MM/dd HH:mm' }}</span>
+            </div>
+            <div class="inbox-item-subject">{{ email.subject }}</div>
+            <div class="inbox-item-preview">{{ email.preview }}</div>
+            <div class="inbox-item-tags">
+              <span class="inbox-att-tag" *ngIf="email.attachments.length > 0">
+                <mat-icon>attach_file</mat-icon> {{ email.attachments.length }}
+              </span>
+              <span class="inbox-lead-tag" *ngIf="email.leadId">
+                <mat-icon>person</mat-icon> 已关联线索
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Right: Email Detail -->
+      <div class="inbox-detail" *ngIf="selectedInboxEmail">
+        <div class="inbox-detail-subject">
+          <mat-icon class="subject-icon">email</mat-icon>
+          {{ selectedInboxEmail.subject }}
+        </div>
+
+        <div class="inbox-detail-meta">
+          <div class="inbox-detail-sender-row">
+            <div class="sender-avatar">
+              <mat-icon>person</mat-icon>
+            </div>
+            <div class="sender-info">
+              <span class="sender-name">{{ selectedInboxEmail.fromName }}</span>
+              <span class="sender-email">&lt;{{ selectedInboxEmail.from }}&gt;</span>
+            </div>
+            <span class="detail-time">{{ selectedInboxEmail.receivedAt | date:'yyyy-MM-dd HH:mm:ss' }}</span>
+          </div>
+          <div class="inbox-detail-recipients">
+            <span class="recipient-label">收件人:</span>
+            <span>{{ selectedInboxEmail.to }}</span>
+          </div>
+          <div class="inbox-detail-recipients" *ngIf="selectedInboxEmail.cc">
+            <span class="recipient-label">抄送:</span>
+            <span>{{ selectedInboxEmail.cc }}</span>
+          </div>
+        </div>
+
+        <!-- Attachments -->
+        <div class="inbox-detail-attachments" *ngIf="selectedInboxEmail.attachments.length > 0">
+          <div class="inbox-att-header">
+            <mat-icon>attach_file</mat-icon>
+            <span>附件 ({{ selectedInboxEmail.attachments.length }})</span>
+            <span class="att-total-size">共 {{ getTotalSize(selectedInboxEmail.attachments) }}</span>
+          </div>
+          <div class="inbox-att-list">
+            <div class="inbox-att-chip" *ngFor="let att of selectedInboxEmail.attachments" (click)="downloadAttachment(att)">
+              <div class="att-icon-wrap" [class]="getAttColorClass(att.mimeType)">
+                <mat-icon>{{ att.icon }}</mat-icon>
+              </div>
+              <div class="att-info">
+                <span class="att-name" [title]="att.fileName">{{ att.fileName }}</span>
+                <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
+              </div>
+              <mat-icon class="att-download-icon">download</mat-icon>
+            </div>
+          </div>
+        </div>
+
+        <mat-divider></mat-divider>
+
+        <!-- Email Body -->
+        <div class="inbox-detail-body">
+          <pre class="email-body-text">{{ selectedInboxEmail.body }}</pre>
+        </div>
+
+        <!-- Actions -->
+        <div class="inbox-detail-actions" *ngIf="getLeadForEmail(selectedInboxEmail) as lead">
+          <button mat-raised-button color="primary" (click)="openLead(lead); closeInbox()">
+            <mat-icon>open_in_new</mat-icon> 查看关联线索:{{ lead.companyName }}
+          </button>
+        </div>
+      </div>
+
+      <!-- Empty State -->
+      <div class="inbox-detail inbox-empty-detail" *ngIf="!selectedInboxEmail">
+        <mat-icon>markunread_mailbox</mat-icon>
+        <p>选择一封邮件查看详情</p>
+      </div>
+    </div>
+  </div>
+

+ 1510 - 0
lead-discovery/src/app/pages/dashboard/dashboard.component.scss

@@ -0,0 +1,1510 @@
+/* ===== Page Header ===== */
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24px;
+}
+.page-header h1 {
+  display: flex; align-items: center; gap: 8px;
+  font-size: 24px; font-weight: 600; margin: 0;
+}
+.page-header h1 mat-icon { color: #1976d2; }
+.subtitle { font-size: 14px; color: #666; margin: 4px 0 0; }
+.header-date {
+  display: flex; align-items: center; gap: 6px;
+  font-size: 14px; color: #888;
+  background: #fff; padding: 8px 16px; border-radius: 10px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
+}
+.header-date mat-icon { font-size: 18px; width: 18px; height: 18px; }
+
+/* ===== Stats Row ===== */
+.stats-row {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+}
+.stat-card {
+  display: flex; align-items: center; gap: 14px;
+  padding: 18px 20px;
+  background: #fff;
+  border-radius: 14px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+  transition: transform 0.2s;
+}
+.stat-card:hover { transform: translateY(-2px); }
+.stat-icon-wrap {
+  width: 48px; height: 48px; border-radius: 12px;
+  display: flex; align-items: center; justify-content: center;
+}
+.stat-icon-wrap mat-icon { font-size: 24px; width: 24px; height: 24px; color: #fff; }
+.stat-card.new .stat-icon-wrap { background: linear-gradient(135deg, #1976d2, #1565c0); }
+.stat-card.pending .stat-icon-wrap { background: linear-gradient(135deg, #ff9800, #ef6c00); }
+.stat-card.verified .stat-icon-wrap { background: linear-gradient(135deg, #43a047, #2e7d32); }
+.stat-card.pipeline .stat-icon-wrap { background: linear-gradient(135deg, #8e24aa, #6a1b9a); }
+.stat-value { display: block; font-size: 24px; font-weight: 700; color: #1a1a2e; }
+.stat-label { font-size: 12px; color: #888; }
+
+/* ===== Batch Status ===== */
+.batch-status { padding: 14px 20px; margin-bottom: 20px; }
+.batch-header {
+  display: flex; align-items: center; gap: 8px;
+  font-size: 14px; font-weight: 600;
+}
+.batch-header mat-icon { color: #1976d2; }
+.batch-time { font-size: 12px; color: #999; font-weight: 400; }
+.batch-badge {
+  margin-left: auto;
+  font-size: 12px; padding: 2px 10px; border-radius: 10px;
+}
+.status-completed { background: #e8f5e9; color: #2e7d32; }
+.status-running { background: #fff3e0; color: #e65100; }
+.status-failed { background: #ffebee; color: #c62828; }
+.batch-stats {
+  display: flex; gap: 20px; margin-top: 8px;
+  font-size: 13px; color: #666;
+}
+.batch-stats strong { color: #1a1a2e; }
+
+/* ===== Tabs ===== */
+.main-tabs {
+  background: transparent;
+}
+::ng-deep .main-tabs .mat-mdc-tab-labels {
+  background: #fff;
+  border-radius: 12px 12px 0 0;
+  padding: 0 8px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
+}
+::ng-deep .main-tabs .mat-mdc-tab {
+  min-width: 140px;
+}
+.tab-label-text { margin-left: 6px; }
+.tab-count {
+  margin-left: 6px;
+  background: #c62828;
+  color: #fff;
+  padding: 1px 7px;
+  border-radius: 10px;
+  font-size: 11px;
+  font-weight: 600;
+}
+.tab-content { padding-top: 20px; }
+
+/* ===== Content Grid (Tab 1) ===== */
+.content-grid {
+  display: grid;
+  grid-template-columns: 1.2fr 1fr;
+  gap: 20px;
+}
+.section-card { padding: 20px; }
+.section-header {
+  display: flex; align-items: center; gap: 8px; margin-bottom: 16px;
+}
+.section-header mat-icon { color: #1976d2; }
+.section-header h3 { margin: 0; font-size: 16px; flex: 1; }
+.count-badge {
+  background: #e3f2fd; color: #1565c0;
+  padding: 2px 10px; border-radius: 10px;
+  font-size: 12px; font-weight: 600;
+}
+
+/* Verify Cards */
+.lead-verify-list { display: flex; flex-direction: column; gap: 12px; }
+.verify-card {
+  padding: 16px;
+  background: #fafbfc;
+  border-radius: 12px;
+  border-left: 4px solid #ccc;
+  transition: all 0.2s;
+}
+.verify-card:hover { background: #f0f4ff; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
+.persona-border-p1 { border-left-color: #1565c0; }
+.persona-border-p2 { border-left-color: #2e7d32; }
+.persona-border-p3 { border-left-color: #6a1b9a; }
+.persona-border-p4 { border-left-color: #e65100; }
+.persona-border-p5 { border-left-color: #00838f; }
+.persona-border-p6 { border-left-color: #9e9e9e; }
+
+.verify-top { display: flex; gap: 12px; align-items: center; }
+.verify-avatar {
+  width: 40px; height: 40px; border-radius: 10px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.verify-avatar mat-icon { font-size: 20px; width: 20px; height: 20px; color: #fff; }
+.verify-info { flex: 1; }
+.verify-company { font-size: 15px; font-weight: 600; color: #1a1a2e; }
+.verify-tags { display: flex; gap: 6px; margin-top: 4px; flex-wrap: wrap; align-items: center; }
+.persona-tag {
+  font-size: 11px; padding: 2px 8px; border-radius: 8px; font-weight: 600;
+}
+.grade-tag {
+  font-size: 11px; padding: 2px 8px; border-radius: 8px; font-weight: 600;
+}
+.grade-s { background: #ffebee; color: #c62828; }
+.grade-a { background: #fff3e0; color: #e65100; }
+.grade-b { background: #fffde7; color: #f9a825; }
+.grade-c { background: #fafafa; color: #757575; }
+.flag { font-size: 14px; }
+.verify-value { font-size: 18px; font-weight: 700; color: #2e7d32; white-space: nowrap; }
+
+.verify-ai-reason {
+  display: flex; align-items: center; gap: 6px;
+  margin-top: 10px; padding: 8px 12px;
+  background: #f0f4ff; border-radius: 8px;
+  font-size: 13px; color: #1565c0;
+}
+.verify-ai-reason mat-icon { font-size: 16px; width: 16px; height: 16px; }
+
+.verify-keywords {
+  display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px;
+}
+.keyword-chip {
+  font-size: 11px; padding: 2px 8px;
+  background: #e8eaf6; color: #5c6bc0;
+  border-radius: 6px; font-family: monospace;
+}
+
+.verify-actions {
+  display: flex; gap: 8px; margin-top: 12px; align-items: center;
+}
+.action-btn { font-size: 12px; }
+.action-btn mat-icon { font-size: 16px; width: 16px; height: 16px; margin-right: 2px; }
+
+.empty-verify {
+  text-align: center; padding: 40px 20px; color: #999;
+}
+.empty-verify mat-icon { font-size: 48px; width: 48px; height: 48px; color: #ddd; }
+
+/* Persona Distribution */
+.persona-dist-list { display: flex; flex-direction: column; gap: 12px; }
+.dist-row { display: flex; align-items: center; gap: 10px; }
+.dist-label {
+  display: flex; align-items: center; gap: 6px;
+  font-size: 13px; min-width: 130px; color: #555;
+}
+.dist-label mat-icon { font-size: 18px; width: 18px; height: 18px; }
+.dist-bar-wrap {
+  flex: 1; height: 8px; background: #f0f0f0;
+  border-radius: 4px; overflow: hidden;
+}
+.dist-bar { height: 100%; border-radius: 4px; transition: width 0.5s; }
+.dist-count { font-size: 12px; color: #888; min-width: 70px; text-align: right; }
+
+/* Activity */
+.activity-list { display: flex; flex-direction: column; gap: 10px; }
+.activity-item { display: flex; gap: 10px; align-items: flex-start; }
+.activity-dot {
+  width: 8px; height: 8px; border-radius: 50%;
+  margin-top: 5px; flex-shrink: 0;
+}
+.activity-content { flex: 1; }
+.activity-text { font-size: 13px; color: #444; display: block; }
+.activity-time { font-size: 11px; color: #999; }
+
+/* Quick Links */
+.quick-links { display: flex; gap: 10px; flex-wrap: wrap; }
+.quick-links button { font-size: 13px; }
+.quick-links mat-icon { font-size: 18px; width: 18px; height: 18px; margin-right: 4px; }
+
+/* ===== Tab 2: Import Section ===== */
+.import-section { padding: 24px; }
+.section-title-row {
+  display: inline-flex;
+  gap: 10px;
+  align-items: center;
+  font-size: 16px;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 16px;
+}
+.section-title-row .section-icon { color: #1976d2; }
+
+.import-banner {
+  border: 2px dashed #d1d5db;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #f8fafc 0%, #f0f4ff 100%);
+  cursor: pointer;
+  transition: all 0.2s;
+  margin-bottom: 16px;
+}
+.import-banner:hover { border-color: #1976d2; background: linear-gradient(135deg, #f0f4ff 0%, #e8f0fe 100%); }
+.import-banner.disabled { opacity: 0.6; pointer-events: none; }
+.import-drop-zone {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 16px 20px;
+}
+.upload-icon { font-size: 36px; width: 36px; height: 36px; color: #1976d2; flex-shrink: 0; }
+.drop-text { flex: 1; display: flex; flex-direction: column; gap: 2px; }
+.drop-main { font-size: 14px; font-weight: 600; color: #333; }
+.drop-hint { font-size: 12px; color: #6b7280; }
+.import-btn { white-space: nowrap; flex-shrink: 0; }
+
+.import-progress-section {
+  margin-bottom: 16px;
+  background: #f8fafc;
+  border-radius: 10px;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+}
+.import-progress-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 8px;
+  font-size: 13px;
+  color: #555;
+}
+
+.linked-status {
+  margin-bottom: 16px;
+  padding: 10px 14px;
+  border-radius: 12px;
+  border: 1px solid #d1fae5;
+  background: #ecfdf5;
+  color: #065f46;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  font-weight: 700;
+}
+.linked-status mat-icon { width: 18px; height: 18px; font-size: 18px; }
+.clear-import-btn { margin-left: auto; }
+.clear-import-btn mat-icon { font-size: 16px; width: 16px; height: 16px; }
+
+.import-form-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+.form-item {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+.form-item .label {
+  font-size: 13px;
+  font-weight: 600;
+  color: #374151;
+}
+.form-item .required { margin-left: 4px; color: #ef4444; font-weight: 800; }
+.form-input {
+  width: 100%;
+  padding: 10px 14px;
+  border: 1px solid #e5e7eb;
+  border-radius: 10px;
+  font-size: 14px;
+  color: #111827;
+  background: #fff;
+  transition: border-color 0.15s, box-shadow 0.15s;
+  box-sizing: border-box;
+}
+.form-input::placeholder { color: #9ca3af; }
+.form-input:hover { border-color: #d1d5db; }
+.form-input:focus { outline: none; border-color: #1976d2; box-shadow: 0 0 0 3px rgba(25,118,210,0.1); }
+.form-item .hint {
+  font-size: 12px;
+  color: #6b7280;
+  display: inline-flex;
+  gap: 6px;
+  align-items: center;
+}
+.form-item .hint-icon { width: 16px; height: 16px; font-size: 16px; opacity: 0.85; }
+
+/* Product Demands */
+.product-demands-section {
+  margin-bottom: 16px;
+  background: #f8f9fb;
+  border-radius: 12px;
+  padding: 14px 16px;
+  border: 1px solid #eef0f4;
+}
+.demands-header {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #555;
+  margin-bottom: 10px;
+}
+.demands-header mat-icon { font-size: 18px; width: 18px; height: 18px; color: #1976d2; }
+.demands-source-tag {
+  margin-left: auto;
+  font-size: 11px;
+  font-weight: 400;
+  color: #999;
+}
+.demands-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  max-height: 240px;
+  overflow-y: auto;
+}
+.demand-card {
+  display: flex;
+  align-items: flex-start;
+  gap: 10px;
+  padding: 8px 12px;
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #e8eaee;
+  transition: all 0.15s;
+}
+.demand-card:hover { border-color: #1976d2; background: #f0f4ff; }
+.demand-index {
+  width: 22px; height: 22px;
+  border-radius: 50%;
+  background: #1976d2;
+  color: #fff;
+  font-size: 11px;
+  font-weight: 700;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  margin-top: 2px;
+}
+.demand-info { flex: 1; min-width: 0; }
+.demand-name { font-size: 13px; font-weight: 600; color: #333; }
+.demand-meta { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
+.meta-tag {
+  font-size: 11px;
+  padding: 1px 6px;
+  border-radius: 6px;
+  background: #f0f4ff;
+  color: #1976d2;
+}
+.demand-source { font-size: 11px; color: #999; margin-top: 2px; }
+
+/* Info Box */
+.info-box {
+  margin-bottom: 16px;
+  padding: 12px 14px;
+  border-radius: 10px;
+  border: 1px solid #e5e7eb;
+  background: #fafafa;
+}
+.info-title {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #555;
+  margin-bottom: 6px;
+}
+.info-title .info-icon { font-size: 16px; width: 16px; height: 16px; color: #1976d2; }
+.info-content { display: flex; flex-direction: column; gap: 4px; }
+.info-item { font-size: 12px; color: #6b7280; padding-left: 22px; }
+
+.import-actions {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+.import-result { margin-top: 16px; }
+
+.screening-progress {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 20px;
+  margin-top: 16px;
+}
+.progress-text h4 { margin: 0; font-size: 15px; }
+.progress-text p { margin: 4px 0 0; font-size: 13px; color: #666; }
+
+/* ===== Email Drawer ===== */
+.drawer-overlay {
+  position: fixed;
+  top: 0; left: 0; right: 0; bottom: 0;
+  background: rgba(0,0,0,0.35);
+  z-index: 999;
+  animation: fadeIn 0.2s;
+}
+.email-drawer {
+  position: fixed;
+  top: 0; right: -520px; bottom: 0;
+  width: 520px;
+  background: #fff;
+  box-shadow: -4px 0 24px rgba(0,0,0,0.15);
+  z-index: 1000;
+  transition: right 0.3s ease;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+.email-drawer.open { right: 0; }
+
+.drawer-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  border-bottom: 1px solid #eee;
+  flex-shrink: 0;
+}
+.drawer-header h3 { margin: 0; font-size: 16px; }
+
+.drawer-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+}
+
+.drawer-meta {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  margin-bottom: 16px;
+}
+.drawer-meta-row {
+  display: flex;
+  gap: 10px;
+  font-size: 13px;
+  color: #555;
+}
+.drawer-label {
+  color: #999;
+  font-size: 12px;
+  min-width: 48px;
+  flex-shrink: 0;
+}
+.drawer-subject {
+  font-weight: 600;
+  color: #1a1a2e;
+}
+
+/* Drawer Attachments */
+.drawer-attachments {
+  margin: 16px 0;
+  background: #f8f9fb;
+  border-radius: 10px;
+  padding: 12px 16px;
+  border: 1px solid #eef0f4;
+}
+.drawer-att-header {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #555;
+  margin-bottom: 10px;
+}
+.drawer-att-header mat-icon { font-size: 18px; width: 18px; height: 18px; color: #888; }
+.att-total-size {
+  margin-left: auto;
+  font-size: 11px;
+  font-weight: 400;
+  color: #999;
+}
+.drawer-att-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+.drawer-att-card {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 8px 12px;
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #e8eaee;
+  cursor: pointer;
+  transition: all 0.15s;
+}
+.drawer-att-card:hover {
+  border-color: #1976d2;
+  background: #f0f4ff;
+}
+.att-icon-wrap {
+  width: 34px; height: 34px; border-radius: 8px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.att-icon-wrap mat-icon { font-size: 18px; width: 18px; height: 18px; color: #fff; }
+.att-color-pdf { background: linear-gradient(135deg, #e53935, #c62828); }
+.att-color-doc { background: linear-gradient(135deg, #1976d2, #1565c0); }
+.att-color-xls { background: linear-gradient(135deg, #43a047, #2e7d32); }
+.att-color-zip { background: linear-gradient(135deg, #ff9800, #ef6c00); }
+.att-color-img { background: linear-gradient(135deg, #8e24aa, #6a1b9a); }
+.att-color-other { background: linear-gradient(135deg, #78909c, #546e7a); }
+
+.att-info { flex: 1; min-width: 0; }
+.att-name {
+  display: block;
+  font-size: 12px;
+  font-weight: 500;
+  color: #333;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.att-size { font-size: 11px; color: #999; }
+.att-download-icon {
+  font-size: 18px; width: 18px; height: 18px;
+  color: #bbb; flex-shrink: 0;
+}
+.drawer-att-card:hover .att-download-icon { color: #1976d2; }
+
+.drawer-email-body { padding-top: 16px; }
+.email-body-text {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+  font-size: 14px;
+  line-height: 1.7;
+  color: #333;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+  margin: 0;
+  padding: 0;
+  background: transparent;
+  border: none;
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
+/* ===== Header Actions ===== */
+.header-actions { display: flex; align-items: center; gap: 12px; }
+.inbox-btn {
+  display: flex; align-items: center; gap: 6px;
+  font-size: 14px; font-weight: 500;
+  border-radius: 10px; padding: 6px 16px;
+}
+.inbox-btn mat-icon { font-size: 20px; width: 20px; height: 20px; }
+
+/* ===== Inbox Modal (centered in content area) ===== */
+:host {
+  position: relative;
+}
+.inbox-overlay {
+  position: fixed;
+  top: 0; left: 0; right: 0; bottom: 0;
+  background: rgba(0,0,0,0.35);
+  z-index: 1100;
+  animation: fadeIn 0.2s;
+}
+.inbox-modal {
+  position: fixed;
+  top: 50%;
+  left: calc((100vw + 220px) / 2);
+  transform: translate(-50%, -50%);
+  width: calc(100vw - 220px - 80px);
+  max-width: 1100px;
+  height: calc(100vh - 140px);
+  max-height: 720px;
+  background: #fff;
+  border-radius: 16px;
+  z-index: 1101;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
+  animation: inboxSlideIn 0.3s ease;
+}
+@keyframes inboxSlideIn {
+  from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
+  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
+}
+
+.inbox-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 24px;
+  border-bottom: 1px solid #eee;
+  flex-shrink: 0;
+  background: linear-gradient(135deg, #f8fafc, #f0f4ff);
+}
+.inbox-title {
+  display: flex; align-items: center; gap: 10px;
+}
+.inbox-title mat-icon { font-size: 24px; width: 24px; height: 24px; color: #1976d2; }
+.inbox-title h2 { margin: 0; font-size: 18px; font-weight: 700; color: #1a1a2e; }
+.inbox-count {
+  font-size: 12px; color: #666;
+  background: #e3f2fd; padding: 2px 10px; border-radius: 10px;
+}
+.inbox-header-actions { display: flex; align-items: center; gap: 8px; }
+
+/* Inbox Body: Split Layout */
+.inbox-body {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+/* Left: Email List */
+.inbox-list {
+  width: 380px;
+  flex-shrink: 0;
+  border-right: 1px solid #eee;
+  overflow-y: auto;
+  background: #fafbfc;
+}
+.inbox-list-item {
+  display: flex;
+  gap: 12px;
+  padding: 14px 18px;
+  cursor: pointer;
+  border-bottom: 1px solid #f0f0f0;
+  transition: all 0.15s;
+}
+.inbox-list-item:hover { background: #f0f4ff; }
+.inbox-list-item.active {
+  background: #e3f2fd;
+  border-left: 3px solid #1976d2;
+  padding-left: 15px;
+}
+.inbox-item-avatar {
+  width: 36px; height: 36px; border-radius: 50%;
+  display: flex; align-items: center; justify-content: center;
+  background: #e8eaf6; flex-shrink: 0; margin-top: 2px;
+}
+.inbox-item-avatar mat-icon { font-size: 18px; width: 18px; height: 18px; color: #5c6bc0; }
+
+.inbox-item-content { flex: 1; min-width: 0; }
+.inbox-item-top {
+  display: flex; justify-content: space-between; align-items: center;
+}
+.inbox-item-sender { font-size: 13px; color: #333; font-weight: 500; }
+.inbox-item-time { font-size: 11px; color: #999; flex-shrink: 0; }
+.inbox-item-subject {
+  font-size: 13px; color: #555; margin-top: 2px;
+  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.inbox-item-preview {
+  font-size: 12px; color: #999; margin-top: 2px;
+  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.inbox-item-tags {
+  display: flex; gap: 6px; margin-top: 4px;
+}
+.inbox-att-tag, .inbox-lead-tag {
+  display: inline-flex; align-items: center; gap: 2px;
+  font-size: 10px; padding: 1px 6px; border-radius: 6px;
+}
+.inbox-att-tag {
+  background: #f5f5f5; color: #888;
+}
+.inbox-att-tag mat-icon, .inbox-lead-tag mat-icon {
+  font-size: 12px; width: 12px; height: 12px;
+}
+.inbox-lead-tag {
+  background: #e8f5e9; color: #2e7d32;
+}
+
+/* Right: Email Detail */
+.inbox-detail {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px 28px;
+  display: flex;
+  flex-direction: column;
+}
+.inbox-empty-detail {
+  align-items: center;
+  justify-content: center;
+  color: #bbb;
+}
+.inbox-empty-detail mat-icon {
+  font-size: 64px; width: 64px; height: 64px; color: #ddd; margin-bottom: 12px;
+}
+.inbox-empty-detail p { font-size: 15px; }
+
+.inbox-detail-subject {
+  display: flex; align-items: flex-start; gap: 10px;
+  font-size: 20px; font-weight: 700; color: #1a1a2e;
+  margin-bottom: 20px; line-height: 1.4;
+}
+.subject-icon {
+  color: #1976d2; font-size: 24px; width: 24px; height: 24px;
+  flex-shrink: 0; margin-top: 2px;
+}
+
+.inbox-detail-meta {
+  background: #f8f9fb;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 16px;
+  border: 1px solid #eef0f4;
+}
+.inbox-detail-sender-row {
+  display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
+}
+.sender-avatar {
+  width: 40px; height: 40px; border-radius: 50%;
+  background: linear-gradient(135deg, #1976d2, #1565c0);
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.sender-avatar mat-icon { color: #fff; font-size: 20px; width: 20px; height: 20px; }
+.sender-info { flex: 1; }
+.sender-name { font-size: 15px; font-weight: 600; color: #1a1a2e; display: block; }
+.sender-email { font-size: 12px; color: #888; }
+.detail-time { font-size: 12px; color: #999; flex-shrink: 0; }
+
+.inbox-detail-recipients {
+  font-size: 13px; color: #666; padding: 4px 0 4px 52px;
+}
+.recipient-label { color: #999; font-size: 12px; margin-right: 4px; }
+
+.inbox-detail-attachments {
+  background: #f8f9fb;
+  border-radius: 12px;
+  padding: 14px 16px;
+  margin-bottom: 16px;
+  border: 1px solid #eef0f4;
+}
+.inbox-att-header {
+  display: flex; align-items: center; gap: 6px;
+  font-size: 13px; font-weight: 600; color: #555;
+  margin-bottom: 10px;
+}
+.inbox-att-header mat-icon { font-size: 18px; width: 18px; height: 18px; color: #888; }
+.inbox-att-list {
+  display: flex; flex-direction: column; gap: 6px;
+}
+.inbox-att-chip {
+  display: flex; align-items: center; gap: 10px;
+  padding: 8px 12px; background: #fff; border-radius: 8px;
+  border: 1px solid #e8eaee; cursor: pointer; transition: all 0.15s;
+}
+.inbox-att-chip:hover { border-color: #1976d2; background: #f0f4ff; }
+.inbox-att-chip:hover .att-download-icon { color: #1976d2; }
+
+.inbox-detail-body {
+  flex: 1; padding-top: 16px;
+}
+
+.inbox-detail-actions {
+  padding: 16px 0;
+  border-top: 1px solid #eee;
+  margin-top: 16px;
+}
+
+/* ===== Import Source Tabs ===== */
+.import-source-tabs {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 16px;
+  background: #fff;
+  border-radius: 14px;
+  padding: 6px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
+}
+.source-tab {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 10px 20px;
+  border-radius: 10px;
+  border: 1px solid transparent;
+  background: transparent;
+  font-size: 14px;
+  font-weight: 500;
+  color: #666;
+  cursor: pointer;
+  transition: all 0.2s;
+  position: relative;
+}
+.source-tab:hover { background: #f0f4ff; color: #1976d2; }
+.source-tab.active {
+  background: #e3f2fd;
+  color: #1565c0;
+  border-color: #bbdefb;
+  font-weight: 600;
+}
+.source-tab mat-icon { font-size: 20px; width: 20px; height: 20px; }
+.source-tab-badge {
+  font-size: 9px;
+  font-weight: 700;
+  padding: 1px 6px;
+  border-radius: 6px;
+  position: absolute;
+  top: 4px;
+  right: 4px;
+}
+.source-tab-badge.new {
+  background: linear-gradient(135deg, #ff6a00, #ee0979);
+  color: #fff;
+}
+
+/* ===== Source Description ===== */
+.source-description {
+  display: flex;
+  gap: 16px;
+  align-items: flex-start;
+  padding: 16px 18px;
+  background: linear-gradient(135deg, #f8fafc 0%, #f0f4ff 100%);
+  border-radius: 12px;
+  border: 1px solid #e8edf5;
+  margin-bottom: 16px;
+}
+.desc-icon-wrap {
+  width: 44px; height: 44px; border-radius: 12px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.desc-icon-wrap mat-icon { font-size: 22px; width: 22px; height: 22px; color: #fff; }
+.desc-icon-wrap.exhibition { background: linear-gradient(135deg, #ff9800, #ef6c00); }
+.desc-icon-wrap.platform { background: linear-gradient(135deg, #1976d2, #0d47a1); }
+.desc-text { font-size: 13px; color: #555; line-height: 1.7; }
+.desc-text strong { color: #1a1a2e; }
+
+/* ===== OCR Section ===== */
+.ocr-section { margin-bottom: 16px; }
+.ocr-divider {
+  text-align: center;
+  position: relative;
+  margin: 12px 0;
+}
+.ocr-divider::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 0; right: 0;
+  height: 1px;
+  background: #e5e7eb;
+}
+.ocr-or {
+  position: relative;
+  z-index: 1;
+  background: #fff;
+  padding: 0 16px;
+  font-size: 13px;
+  color: #999;
+  font-weight: 500;
+}
+.ocr-banner {
+  border-color: #e5d4c0;
+  background: linear-gradient(135deg, #fffcf8 0%, #fff8f0 100%);
+}
+.ocr-banner:hover { border-color: #ef6c00; background: linear-gradient(135deg, #fff8f0 0%, #fff3e0 100%); }
+.ocr-icon { color: #ef6c00 !important; }
+
+/* ===== Batch Import Progress ===== */
+.batch-import-progress {
+  padding: 20px;
+  background: #f8fafc;
+  border-radius: 14px;
+  border: 1px solid #e5e7eb;
+  margin-top: 16px;
+}
+.batch-progress-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  color: #1a1a2e;
+  margin-bottom: 12px;
+}
+.batch-progress-header mat-icon { color: #1976d2; }
+.batch-progress-pct {
+  margin-left: auto;
+  font-size: 14px;
+  font-weight: 700;
+  color: #1976d2;
+}
+.batch-progress-steps {
+  display: flex;
+  gap: 4px;
+  margin-top: 14px;
+  flex-wrap: wrap;
+}
+.batch-step {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+  color: #bbb;
+  padding: 4px 10px;
+  border-radius: 8px;
+  background: #f5f5f5;
+  transition: all 0.3s;
+}
+.batch-step mat-icon { font-size: 16px; width: 16px; height: 16px; }
+.batch-step.active {
+  color: #1976d2;
+  background: #e3f2fd;
+  font-weight: 600;
+}
+.batch-step.done {
+  color: #2e7d32;
+  background: #e8f5e9;
+}
+.batch-step.done mat-icon { color: #2e7d32; }
+.batch-progress-detail {
+  margin-top: 10px;
+  font-size: 13px;
+  color: #666;
+}
+.spin-icon {
+  animation: spin 1.2s linear infinite;
+}
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
+
+/* ===== Batch Import Result ===== */
+.batch-import-result {
+  padding: 20px;
+  background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
+  border-radius: 14px;
+  border: 1px solid #bbf7d0;
+  margin-top: 16px;
+}
+.batch-result-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 16px;
+  color: #166534;
+  margin-bottom: 16px;
+}
+.batch-result-header mat-icon { font-size: 24px; width: 24px; height: 24px; color: #16a34a; }
+.batch-result-stats {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+  margin-bottom: 14px;
+}
+.batch-stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 12px 18px;
+  background: #fff;
+  border-radius: 10px;
+  border: 1px solid #e5e7eb;
+  min-width: 80px;
+}
+.batch-stat-num {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1a1a2e;
+}
+.batch-stat-label {
+  font-size: 11px;
+  color: #888;
+  margin-top: 2px;
+}
+.batch-stat-item.highlight-s {
+  border-color: #fecaca;
+  background: #fff5f5;
+}
+.batch-stat-item.highlight-s .batch-stat-num { color: #c62828; }
+.batch-stat-item.highlight-a {
+  border-color: #fed7aa;
+  background: #fffbf0;
+}
+.batch-stat-item.highlight-a .batch-stat-num { color: #e65100; }
+.batch-stat-item.dim {
+  opacity: 0.6;
+}
+.batch-result-personas {
+  display: flex;
+  gap: 6px;
+  flex-wrap: wrap;
+  margin-bottom: 14px;
+}
+.persona-chip {
+  font-size: 11px;
+  padding: 3px 10px;
+  border-radius: 8px;
+  background: #e8eaf6;
+  color: #5c6bc0;
+  font-weight: 500;
+}
+.batch-result-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.platform-result {
+  background: linear-gradient(135deg, #eff6ff 0%, #e0f2fe 100%);
+  border-color: #93c5fd;
+}
+.platform-result .batch-result-header { color: #1e40af; }
+.platform-result .batch-result-header mat-icon { color: #2563eb; }
+
+/* ===== Platform Grid ===== */
+.platform-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 14px;
+  margin-bottom: 16px;
+}
+.platform-card {
+  border-radius: 14px;
+  border: 1px solid #e5e7eb;
+  background: #fff;
+  overflow: hidden;
+  transition: all 0.2s;
+}
+.platform-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); transform: translateY(-1px); }
+.platform-card.connected { border-color: #bbdefb; }
+.platform-card-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+.platform-logo {
+  width: 40px; height: 40px; border-radius: 10px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.platform-logo mat-icon { font-size: 22px; width: 22px; height: 22px; color: #fff; }
+.platform-info { flex: 1; min-width: 0; }
+.platform-name { font-size: 14px; font-weight: 600; color: #1a1a2e; }
+.platform-desc {
+  font-size: 11px; color: #888; margin-top: 2px;
+  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.platform-status-tag {
+  font-size: 11px;
+  padding: 2px 10px;
+  border-radius: 8px;
+  font-weight: 600;
+  white-space: nowrap;
+}
+.platform-status-tag.connected { background: #e8f5e9; color: #2e7d32; }
+.platform-status-tag.disconnected { background: #f5f5f5; color: #999; }
+.platform-card-body { padding: 12px 16px; }
+.platform-meta-row {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 12px;
+  color: #666;
+  padding: 3px 0;
+}
+.platform-meta-row mat-icon { font-size: 16px; width: 16px; height: 16px; color: #999; }
+.platform-meta-row strong { color: #1a1a2e; }
+.platform-pending-msg {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 12px;
+  color: #999;
+  font-style: italic;
+}
+.platform-pending-msg mat-icon { font-size: 16px; width: 16px; height: 16px; }
+.platform-card-actions {
+  padding: 12px 16px;
+  border-top: 1px solid #f0f0f0;
+}
+.platform-card-actions button { width: 100%; font-size: 13px; }
+
+.platform-sync-progress {
+  border-color: #93c5fd;
+  background: #eff6ff;
+}
+
+/* ===== Platform Schedule ===== */
+.platform-schedule-card {
+  padding: 18px 20px;
+  background: linear-gradient(135deg, #f8fafc 0%, #eff6ff 100%);
+  border-radius: 14px;
+  border: 1px solid #dbeafe;
+  margin-bottom: 16px;
+}
+.schedule-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  color: #1e40af;
+  margin-bottom: 14px;
+}
+.schedule-header mat-icon { color: #2563eb; }
+.schedule-timeline {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  padding-left: 4px;
+}
+.schedule-item {
+  display: flex;
+  gap: 12px;
+  align-items: flex-start;
+  padding: 10px 14px;
+  background: #fff;
+  border-radius: 10px;
+  border: 1px solid #e5e7eb;
+}
+.schedule-time {
+  font-size: 13px;
+  font-weight: 600;
+  color: #1a1a2e;
+  white-space: nowrap;
+  min-width: 110px;
+}
+.schedule-desc {
+  font-size: 12px;
+  color: #666;
+  line-height: 1.5;
+}
+
+/* ===== 批量导入 ===== */
+.batch-import-container {
+  max-width: 1200px;
+}
+
+.batch-method-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 6px;
+  padding: 20px;
+  border: 2px dashed #ddd;
+  border-radius: 12px;
+  cursor: pointer;
+  transition: all 0.2s;
+  text-align: center;
+
+  mat-icon { font-size: 32px; width: 32px; height: 32px; color: #1976d2; }
+  span { font-weight: 500; font-size: 14px; }
+  small { color: #888; font-size: 12px; }
+
+  &:hover {
+    border-color: #1976d2;
+    background: #e3f2fd;
+  }
+}
+
+.batch-text-area {
+  margin-bottom: 20px;
+}
+
+.batch-text-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+  font-size: 13px;
+  color: #555;
+
+  mat-icon { font-size: 18px; width: 18px; height: 18px; }
+}
+
+.batch-textarea {
+  width: 100%;
+  min-height: 120px;
+  padding: 12px;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+  font-family: 'Roboto Mono', monospace;
+  font-size: 13px;
+  line-height: 1.6;
+  resize: vertical;
+  box-sizing: border-box;
+
+  &:focus { outline: none; border-color: #1976d2; box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1); }
+}
+
+.batch-text-actions {
+  display: flex;
+  gap: 12px;
+  margin-top: 12px;
+}
+
+.batch-preview {
+  margin-top: 20px;
+}
+
+.batch-preview-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+
+  mat-icon { color: #1976d2; }
+  h4 { margin: 0; font-size: 15px; }
+
+  .batch-count {
+    padding: 2px 10px;
+    background: #e3f2fd;
+    border-radius: 12px;
+    font-size: 12px;
+    color: #1976d2;
+    font-weight: 500;
+
+    &.duplicate { background: #fff3e0; color: #e65100; }
+  }
+
+  .batch-preview-actions {
+    margin-left: auto;
+    display: flex;
+    gap: 4px;
+  }
+}
+
+.batch-table-wrap {
+  overflow-x: auto;
+  border: 1px solid #eee;
+  border-radius: 8px;
+}
+
+.batch-table {
+  width: 100%;
+  border-collapse: collapse;
+  font-size: 13px;
+
+  th {
+    background: #f5f7fa;
+    padding: 10px 12px;
+    text-align: left;
+    font-weight: 500;
+    color: #555;
+    white-space: nowrap;
+    border-bottom: 1px solid #eee;
+  }
+
+  td {
+    padding: 10px 12px;
+    border-bottom: 1px solid #f0f0f0;
+    max-width: 200px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .col-check { width: 40px; text-align: center; }
+
+  tr.selected { background: #e3f2fd; }
+  tr.low-confidence { opacity: 0.7; }
+  tr:hover { background: #f5f7fa; }
+}
+
+.confidence-badge {
+  padding: 2px 8px;
+  border-radius: 10px;
+  font-size: 12px;
+  font-weight: 500;
+
+  &.confidence-high { background: #e8f5e9; color: #2e7d32; }
+  &.confidence-medium { background: #fff8e1; color: #f57f17; }
+  &.confidence-low { background: #ffebee; color: #c62828; }
+}
+
+.source-tag {
+  padding: 2px 8px;
+  border-radius: 10px;
+  font-size: 11px;
+  background: #f5f5f5;
+  color: #666;
+  text-transform: uppercase;
+}
+
+.batch-execute-bar {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+  padding-top: 16px;
+  border-top: 1px solid #eee;
+}
+
+.batch-execute-btn {
+  padding: 8px 32px !important;
+  font-size: 15px !important;
+}
+
+/* 第一轮筛查结果 */
+.batch-summary-bar {
+  display: flex;
+  align-items: center;
+  gap: 24px;
+  padding: 16px 20px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 20px;
+
+  .summary-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 14px;
+
+    strong { font-size: 20px; color: #1976d2; }
+    &.new-entities strong { color: #2e7d32; }
+    &.duplicate-entities strong { color: #e65100; }
+  }
+
+  .summary-actions { margin-left: auto; }
+}
+
+.batch-entity-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.batch-entity-card {
+  border: 1px solid #e0e0e0;
+  border-radius: 12px;
+  padding: 20px;
+  transition: all 0.2s;
+
+  &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
+  &.is-duplicate { border-left: 4px solid #e65100; }
+}
+
+.entity-card-top {
+  display: flex;
+  align-items: flex-start;
+  gap: 14px;
+  margin-bottom: 12px;
+}
+
+.entity-avatar {
+  width: 44px;
+  height: 44px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+
+  mat-icon { color: #fff; font-size: 22px; }
+}
+
+.entity-info { flex: 1; }
+
+.entity-company {
+  font-size: 16px;
+  font-weight: 600;
+  margin-bottom: 6px;
+}
+
+.entity-tags {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  align-items: center;
+}
+
+.entity-status {
+  flex-shrink: 0;
+
+  .entity-badge {
+    padding: 4px 12px;
+    border-radius: 16px;
+    font-size: 12px;
+    font-weight: 500;
+
+    &.new { background: #e8f5e9; color: #2e7d32; }
+    &.duplicate { background: #fff3e0; color: #e65100; }
+
+    .dup-count { font-weight: 400; opacity: 0.8; }
+  }
+}
+
+.entity-duplicate-warn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: #fff8e1;
+  border-radius: 6px;
+  margin-bottom: 12px;
+  font-size: 13px;
+  color: #e65100;
+
+  mat-icon { font-size: 18px; width: 18px; height: 18px; }
+}
+
+.entity-details {
+  margin-bottom: 10px;
+}
+
+.entity-detail-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 13px;
+  color: #666;
+  margin-bottom: 4px;
+
+  mat-icon { font-size: 16px; width: 16px; height: 16px; color: #999; }
+
+  .contact-list { color: #999; font-size: 12px; }
+}
+
+.entity-keywords {
+  display: flex;
+  gap: 6px;
+  flex-wrap: wrap;
+  margin-bottom: 12px;
+}
+
+.entity-actions {
+  display: flex;
+  gap: 8px;
+  margin-top: 4px;
+
+  .action-btn {
+    font-size: 13px;
+  }
+}
+
+.tab-count.new {
+  background: #2e7d32;
+}
+
+.batch-screen-result-section {
+  margin-top: 20px;
+
+  .section-header {
+    margin-bottom: 16px;
+  }
+}
+
+.batch-input-methods {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+/* ===== Responsive ===== */
+@media (max-width: 1000px) {
+  .stats-row { grid-template-columns: repeat(2, 1fr); }
+  .content-grid { grid-template-columns: 1fr; }
+  .email-drawer { width: 100%; right: -100%; }
+  .inbox-modal {
+    left: 50%; width: calc(100vw - 32px); max-height: calc(100vh - 80px);
+    border-radius: 12px;
+  }
+  .inbox-list { width: 280px; }
+  .batch-input-methods { grid-template-columns: 1fr !important; }
+}
+@media (max-width: 600px) {
+  .stats-row { grid-template-columns: 1fr; }
+  .import-form-grid { grid-template-columns: 1fr; }
+  .inbox-body { flex-direction: column; }
+  .inbox-list { width: 100%; max-height: 40%; border-right: none; border-bottom: 1px solid #eee; }
+  .import-source-tabs { flex-direction: column; }
+  .platform-grid { grid-template-columns: 1fr; }
+  .batch-result-stats { flex-direction: row; flex-wrap: wrap; }
+  .batch-summary-bar { flex-direction: column; gap: 12px; align-items: flex-start; }
+  .entity-card-top { flex-direction: column; }
+}
+

Разница между файлами не показана из-за своего большого размера
+ 6 - 1346
lead-discovery/src/app/pages/dashboard/dashboard.component.ts


+ 151 - 0
lead-discovery/src/app/pages/kanban/kanban.component.html

@@ -0,0 +1,151 @@
+<div class="page-container">
+  <div class="page-header">
+    <div>
+      <h1><mat-icon>view_kanban</mat-icon> 线索看板</h1>
+      <p class="subtitle">多维度看板视图,快速定位高优先级线索</p>
+    </div>
+    <div class="header-stats">
+      <div class="stat-item">
+        <span class="stat-value">{{ leads.length }}</span>
+        <span class="stat-label">总线索</span>
+      </div>
+      <div class="stat-item highlight">
+        <span class="stat-value">{{ getSGradeCount() }}</span>
+        <span class="stat-label">S级</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- View Mode Toggle -->
+  <div class="view-bar card">
+    <div class="view-toggle">
+      <span class="toggle-label">视图:</span>
+      <mat-button-toggle-group [(ngModel)]="viewMode" (change)="onViewChange()">
+        <mat-button-toggle value="grade">
+          <mat-icon>grade</mat-icon> 按等级
+        </mat-button-toggle>
+        <mat-button-toggle value="persona">
+          <mat-icon>category</mat-icon> 按画像
+        </mat-button-toggle>
+        <mat-button-toggle value="stage">
+          <mat-icon>linear_scale</mat-icon> 按阶段
+        </mat-button-toggle>
+      </mat-button-toggle-group>
+    </div>
+
+    <!-- Category Filter -->
+    <mat-button-toggle-group [(ngModel)]="selectedCategory" (change)="filterLeads()">
+      <mat-button-toggle value="all">全部 ({{ leads.length }})</mat-button-toggle>
+      <mat-button-toggle value="A">A-医疗 ({{ countByCategory('A') }})</mat-button-toggle>
+      <mat-button-toggle value="B">B-工业 ({{ countByCategory('B') }})</mat-button-toggle>
+      <mat-button-toggle value="C">C-政府 ({{ countByCategory('C') }})</mat-button-toggle>
+      <mat-button-toggle value="D">D-OEM ({{ countByCategory('D') }})</mat-button-toggle>
+    </mat-button-toggle-group>
+  </div>
+
+  <!-- ===== GRADE VIEW ===== -->
+  <div class="kanban-board" *ngIf="viewMode === 'grade'">
+    <div class="kanban-column" *ngFor="let grade of grades">
+      <div class="column-header" [class]="'col-header-' + grade.toLowerCase()">
+        <div class="col-title">
+          <span class="grade-dot" [class]="'dot-' + grade.toLowerCase()"></span>
+          {{ grade }}级
+          <span class="col-subtitle">{{ gradeLabels[grade] }}</span>
+        </div>
+        <span class="col-count">{{ getFilteredByGrade(grade).length }}</span>
+      </div>
+      <div class="column-body">
+        <div class="lead-card" *ngFor="let lead of getFilteredByGrade(grade)"
+             [style.border-left-color]="getPersonaColor(lead.persona)"
+             (click)="openLead(lead)">
+          <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
+        </div>
+        <div class="empty-column" *ngIf="getFilteredByGrade(grade).length === 0">
+          <mat-icon>inbox</mat-icon><span>暂无{{ grade }}级线索</span>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- ===== PERSONA VIEW ===== -->
+  <div class="kanban-board persona-board" *ngIf="viewMode === 'persona'">
+    <div class="kanban-column" *ngFor="let p of activePersonas">
+      <div class="column-header persona-col-header" [style.border-bottom-color]="p.color">
+        <div class="col-title">
+          <div class="persona-dot" [style.background]="p.color">
+            <mat-icon>{{ p.icon }}</mat-icon>
+          </div>
+          {{ p.id }}
+          <span class="col-subtitle">{{ p.name }}</span>
+        </div>
+        <span class="col-count">{{ getFilteredByPersona(p.id).length }}</span>
+      </div>
+      <div class="column-body">
+        <div class="lead-card" *ngFor="let lead of getFilteredByPersona(p.id)"
+             [style.border-left-color]="p.color"
+             (click)="openLead(lead)">
+          <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
+        </div>
+        <div class="empty-column" *ngIf="getFilteredByPersona(p.id).length === 0">
+          <mat-icon>inbox</mat-icon><span>暂无线索</span>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- ===== STAGE VIEW ===== -->
+  <div class="kanban-board stage-board" *ngIf="viewMode === 'stage'">
+    <div class="kanban-column" *ngFor="let stage of stageColumns">
+      <div class="column-header" [style.border-bottom-color]="stage.color">
+        <div class="col-title">
+          <span class="stage-dot" [style.background]="stage.color"></span>
+          {{ stage.label }}
+        </div>
+        <span class="col-count">{{ getFilteredByStage(stage.key).length }}</span>
+      </div>
+      <div class="column-body">
+        <div class="lead-card" *ngFor="let lead of getFilteredByStage(stage.key)"
+             [style.border-left-color]="getPersonaColor(lead.persona)"
+             (click)="openLead(lead)">
+          <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
+        </div>
+        <div class="empty-column" *ngIf="getFilteredByStage(stage.key).length === 0">
+          <mat-icon>inbox</mat-icon><span>暂无线索</span>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- Shared Lead Card Template -->
+  <ng-template #leadCardTpl let-lead>
+    <div class="lead-card-top">
+      <span class="company-name">{{ lead.companyName }}</span>
+      <span class="country-flag">{{ getFlag(lead.country) }}</span>
+    </div>
+    <div class="lead-card-tags">
+      <span class="tag persona-tag" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
+        {{ lead.persona }}
+      </span>
+      <span class="tag" [class]="'cat-' + lead.customerCategory.toLowerCase()">
+        {{ lead.categoryLabel }}
+      </span>
+      <span class="tag source-tag">{{ lead.sourceLabel }}</span>
+    </div>
+    <div class="lead-card-meta">
+      <span class="meta"><mat-icon>person</mat-icon> {{ lead.contactName }}</span>
+    </div>
+    <div class="lead-card-bottom">
+      <span class="stage-badge" [class]="'stage-' + lead.followUpStage">
+        {{ getStageLabel(lead.followUpStage) }}
+      </span>
+      <span class="next-date" [class.overdue]="isOverdue(lead.nextFollowUpDate)">
+        <mat-icon>event</mat-icon>
+        {{ lead.nextFollowUpDate | date:'MM/dd' }}
+      </span>
+    </div>
+    <div class="lead-card-verify" *ngIf="!lead.personaVerified">
+      <mat-icon>pending</mat-icon> 待验证
+    </div>
+  </ng-template>
+</div>
+

+ 103 - 0
lead-discovery/src/app/pages/kanban/kanban.component.scss

@@ -0,0 +1,103 @@
+.page-header {
+  display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;
+}
+.page-header h1 { display: flex; align-items: center; gap: 8px; font-size: 24px; font-weight: 600; margin: 0; }
+.page-header h1 mat-icon { color: #1976d2; }
+.subtitle { font-size: 14px; color: #666; margin: 4px 0 0; }
+
+.header-stats { display: flex; gap: 20px; }
+.stat-item { text-align: center; padding: 8px 16px; background: #fff; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
+.stat-item.highlight { background: #ffebee; }
+.stat-value { display: block; font-size: 22px; font-weight: 700; color: #1a1a2e; }
+.stat-label { font-size: 11px; color: #888; }
+
+.view-bar { padding: 12px 16px; margin-bottom: 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
+.view-toggle { display: flex; align-items: center; gap: 8px; }
+.toggle-label { font-size: 13px; color: #888; font-weight: 500; }
+
+.kanban-board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; min-height: calc(100vh - 360px); }
+.persona-board { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
+.stage-board { grid-template-columns: repeat(6, 1fr); }
+
+.kanban-column { background: #f8f9fb; border-radius: 12px; display: flex; flex-direction: column; min-height: 300px; }
+.column-header {
+  display: flex; justify-content: space-between; align-items: center;
+  padding: 14px 16px; border-radius: 12px 12px 0 0; border-bottom: 2px solid transparent;
+}
+.col-header-s { background: #fff5f5; border-bottom-color: #c62828; }
+.col-header-a { background: #fff8f0; border-bottom-color: #e65100; }
+.col-header-b { background: #fffef5; border-bottom-color: #f9a825; }
+.col-header-c { background: #fafafa; border-bottom-color: #bdbdbd; }
+.persona-col-header { background: #fafbfc; }
+
+.col-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 15px; }
+.col-subtitle { font-size: 11px; color: #888; font-weight: 400; }
+.grade-dot { width: 10px; height: 10px; border-radius: 50%; }
+.dot-s { background: #c62828; }
+.dot-a { background: #e65100; }
+.dot-b { background: #f9a825; }
+.dot-c { background: #bdbdbd; }
+.stage-dot { width: 10px; height: 10px; border-radius: 50%; }
+.persona-dot {
+  width: 24px; height: 24px; border-radius: 6px;
+  display: flex; align-items: center; justify-content: center;
+}
+.persona-dot mat-icon { font-size: 14px; width: 14px; height: 14px; color: #fff; }
+.col-count { background: rgba(0,0,0,0.08); padding: 2px 10px; border-radius: 10px; font-size: 12px; font-weight: 600; }
+
+.column-body { flex: 1; padding: 12px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
+
+.lead-card {
+  background: #fff; border-radius: 10px; padding: 14px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.06); cursor: pointer;
+  transition: all 0.2s; border-left: 3px solid #ccc;
+}
+.lead-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.12); transform: translateY(-1px); }
+
+.lead-card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
+.company-name { font-size: 14px; font-weight: 600; color: #1a1a2e; }
+.country-flag { font-size: 16px; }
+
+.lead-card-tags { display: flex; gap: 4px; margin-bottom: 8px; flex-wrap: wrap; }
+.tag { font-size: 10px; padding: 1px 7px; border-radius: 8px; font-weight: 600; }
+.persona-tag { font-weight: 700; }
+.cat-a { background: #e3f2fd; color: #1565c0; }
+.cat-b { background: #e8f5e9; color: #2e7d32; }
+.cat-c { background: #f3e5f5; color: #6a1b9a; }
+.cat-d { background: #fff3e0; color: #e65100; }
+.source-tag { background: #f5f5f5; color: #777; }
+
+.lead-card-meta { display: flex; justify-content: space-between; margin-bottom: 8px; }
+.meta { display: flex; align-items: center; gap: 3px; font-size: 12px; color: #666; }
+.meta mat-icon { font-size: 14px; width: 14px; height: 14px; color: #999; }
+
+.lead-card-bottom { display: flex; justify-content: space-between; align-items: center; }
+.stage-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
+.stage-screened { background: #e3f2fd; color: #1565c0; }
+.stage-contacted { background: #e8f5e9; color: #2e7d32; }
+.stage-replied { background: #fff3e0; color: #e65100; }
+.stage-sample { background: #f3e5f5; color: #6a1b9a; }
+.stage-quote { background: #e1f5fe; color: #0277bd; }
+.stage-order { background: #e8f5e9; color: #1b5e20; }
+
+.next-date { display: flex; align-items: center; gap: 3px; font-size: 11px; color: #888; }
+.next-date mat-icon { font-size: 13px; width: 13px; height: 13px; }
+.next-date.overdue { color: #c62828; font-weight: 600; }
+
+.lead-card-verify {
+  display: flex; align-items: center; gap: 4px;
+  margin-top: 6px; font-size: 11px; color: #e65100;
+  background: #fff3e0; padding: 2px 8px; border-radius: 6px;
+  width: fit-content;
+}
+.lead-card-verify mat-icon { font-size: 13px; width: 13px; height: 13px; }
+
+.empty-column {
+  display: flex; flex-direction: column; align-items: center; justify-content: center;
+  padding: 40px 10px; color: #ccc; font-size: 13px;
+}
+.empty-column mat-icon { font-size: 32px; width: 32px; height: 32px; margin-bottom: 8px; }
+
+@media (max-width: 1100px) { .kanban-board { grid-template-columns: repeat(2, 1fr); } .stage-board { grid-template-columns: repeat(3, 1fr); } }
+@media (max-width: 600px) { .kanban-board { grid-template-columns: 1fr; } .header-stats { display: none; } }
+

+ 2 - 256
lead-discovery/src/app/pages/kanban/kanban.component.ts

@@ -22,262 +22,8 @@ import {
         MatCardModule, MatIconModule, MatButtonModule,
         MatCardModule, MatIconModule, MatButtonModule,
         MatChipsModule, MatButtonToggleModule, MatTooltipModule,
         MatChipsModule, MatButtonToggleModule, MatTooltipModule,
     ],
     ],
-    template: `
-    <div class="page-container">
-      <div class="page-header">
-        <div>
-          <h1><mat-icon>view_kanban</mat-icon> 线索看板</h1>
-          <p class="subtitle">多维度看板视图,快速定位高优先级线索</p>
-        </div>
-        <div class="header-stats">
-          <div class="stat-item">
-            <span class="stat-value">{{ leads.length }}</span>
-            <span class="stat-label">总线索</span>
-          </div>
-          <div class="stat-item highlight">
-            <span class="stat-value">{{ getSGradeCount() }}</span>
-            <span class="stat-label">S级</span>
-          </div>
-        </div>
-      </div>
-
-      <!-- View Mode Toggle -->
-      <div class="view-bar card">
-        <div class="view-toggle">
-          <span class="toggle-label">视图:</span>
-          <mat-button-toggle-group [(ngModel)]="viewMode" (change)="onViewChange()">
-            <mat-button-toggle value="grade">
-              <mat-icon>grade</mat-icon> 按等级
-            </mat-button-toggle>
-            <mat-button-toggle value="persona">
-              <mat-icon>category</mat-icon> 按画像
-            </mat-button-toggle>
-            <mat-button-toggle value="stage">
-              <mat-icon>linear_scale</mat-icon> 按阶段
-            </mat-button-toggle>
-          </mat-button-toggle-group>
-        </div>
-
-        <!-- Category Filter -->
-        <mat-button-toggle-group [(ngModel)]="selectedCategory" (change)="filterLeads()">
-          <mat-button-toggle value="all">全部 ({{ leads.length }})</mat-button-toggle>
-          <mat-button-toggle value="A">A-医疗 ({{ countByCategory('A') }})</mat-button-toggle>
-          <mat-button-toggle value="B">B-工业 ({{ countByCategory('B') }})</mat-button-toggle>
-          <mat-button-toggle value="C">C-政府 ({{ countByCategory('C') }})</mat-button-toggle>
-          <mat-button-toggle value="D">D-OEM ({{ countByCategory('D') }})</mat-button-toggle>
-        </mat-button-toggle-group>
-      </div>
-
-      <!-- ===== GRADE VIEW ===== -->
-      <div class="kanban-board" *ngIf="viewMode === 'grade'">
-        <div class="kanban-column" *ngFor="let grade of grades">
-          <div class="column-header" [class]="'col-header-' + grade.toLowerCase()">
-            <div class="col-title">
-              <span class="grade-dot" [class]="'dot-' + grade.toLowerCase()"></span>
-              {{ grade }}级
-              <span class="col-subtitle">{{ gradeLabels[grade] }}</span>
-            </div>
-            <span class="col-count">{{ getFilteredByGrade(grade).length }}</span>
-          </div>
-          <div class="column-body">
-            <div class="lead-card" *ngFor="let lead of getFilteredByGrade(grade)"
-                 [style.border-left-color]="getPersonaColor(lead.persona)"
-                 (click)="openLead(lead)">
-              <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
-            </div>
-            <div class="empty-column" *ngIf="getFilteredByGrade(grade).length === 0">
-              <mat-icon>inbox</mat-icon><span>暂无{{ grade }}级线索</span>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- ===== PERSONA VIEW ===== -->
-      <div class="kanban-board persona-board" *ngIf="viewMode === 'persona'">
-        <div class="kanban-column" *ngFor="let p of activePersonas">
-          <div class="column-header persona-col-header" [style.border-bottom-color]="p.color">
-            <div class="col-title">
-              <div class="persona-dot" [style.background]="p.color">
-                <mat-icon>{{ p.icon }}</mat-icon>
-              </div>
-              {{ p.id }}
-              <span class="col-subtitle">{{ p.name }}</span>
-            </div>
-            <span class="col-count">{{ getFilteredByPersona(p.id).length }}</span>
-          </div>
-          <div class="column-body">
-            <div class="lead-card" *ngFor="let lead of getFilteredByPersona(p.id)"
-                 [style.border-left-color]="p.color"
-                 (click)="openLead(lead)">
-              <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
-            </div>
-            <div class="empty-column" *ngIf="getFilteredByPersona(p.id).length === 0">
-              <mat-icon>inbox</mat-icon><span>暂无线索</span>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- ===== STAGE VIEW ===== -->
-      <div class="kanban-board stage-board" *ngIf="viewMode === 'stage'">
-        <div class="kanban-column" *ngFor="let stage of stageColumns">
-          <div class="column-header" [style.border-bottom-color]="stage.color">
-            <div class="col-title">
-              <span class="stage-dot" [style.background]="stage.color"></span>
-              {{ stage.label }}
-            </div>
-            <span class="col-count">{{ getFilteredByStage(stage.key).length }}</span>
-          </div>
-          <div class="column-body">
-            <div class="lead-card" *ngFor="let lead of getFilteredByStage(stage.key)"
-                 [style.border-left-color]="getPersonaColor(lead.persona)"
-                 (click)="openLead(lead)">
-              <ng-container *ngTemplateOutlet="leadCardTpl; context: { $implicit: lead }"></ng-container>
-            </div>
-            <div class="empty-column" *ngIf="getFilteredByStage(stage.key).length === 0">
-              <mat-icon>inbox</mat-icon><span>暂无线索</span>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- Shared Lead Card Template -->
-      <ng-template #leadCardTpl let-lead>
-        <div class="lead-card-top">
-          <span class="company-name">{{ lead.companyName }}</span>
-          <span class="country-flag">{{ getFlag(lead.country) }}</span>
-        </div>
-        <div class="lead-card-tags">
-          <span class="tag persona-tag" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
-            {{ lead.persona }}
-          </span>
-          <span class="tag" [class]="'cat-' + lead.customerCategory.toLowerCase()">
-            {{ lead.categoryLabel }}
-          </span>
-          <span class="tag source-tag">{{ lead.sourceLabel }}</span>
-        </div>
-        <div class="lead-card-meta">
-          <span class="meta"><mat-icon>person</mat-icon> {{ lead.contactName }}</span>
-        </div>
-        <div class="lead-card-bottom">
-          <span class="stage-badge" [class]="'stage-' + lead.followUpStage">
-            {{ getStageLabel(lead.followUpStage) }}
-          </span>
-          <span class="next-date" [class.overdue]="isOverdue(lead.nextFollowUpDate)">
-            <mat-icon>event</mat-icon>
-            {{ lead.nextFollowUpDate | date:'MM/dd' }}
-          </span>
-        </div>
-        <div class="lead-card-verify" *ngIf="!lead.personaVerified">
-          <mat-icon>pending</mat-icon> 待验证
-        </div>
-      </ng-template>
-    </div>
-  `,
-    styles: [`
-    .page-header {
-      display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;
-    }
-    .page-header h1 { display: flex; align-items: center; gap: 8px; font-size: 24px; font-weight: 600; margin: 0; }
-    .page-header h1 mat-icon { color: #1976d2; }
-    .subtitle { font-size: 14px; color: #666; margin: 4px 0 0; }
-
-    .header-stats { display: flex; gap: 20px; }
-    .stat-item { text-align: center; padding: 8px 16px; background: #fff; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
-    .stat-item.highlight { background: #ffebee; }
-    .stat-value { display: block; font-size: 22px; font-weight: 700; color: #1a1a2e; }
-    .stat-label { font-size: 11px; color: #888; }
-
-    .view-bar { padding: 12px 16px; margin-bottom: 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
-    .view-toggle { display: flex; align-items: center; gap: 8px; }
-    .toggle-label { font-size: 13px; color: #888; font-weight: 500; }
-
-    .kanban-board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; min-height: calc(100vh - 360px); }
-    .persona-board { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
-    .stage-board { grid-template-columns: repeat(6, 1fr); }
-
-    .kanban-column { background: #f8f9fb; border-radius: 12px; display: flex; flex-direction: column; min-height: 300px; }
-    .column-header {
-      display: flex; justify-content: space-between; align-items: center;
-      padding: 14px 16px; border-radius: 12px 12px 0 0; border-bottom: 2px solid transparent;
-    }
-    .col-header-s { background: #fff5f5; border-bottom-color: #c62828; }
-    .col-header-a { background: #fff8f0; border-bottom-color: #e65100; }
-    .col-header-b { background: #fffef5; border-bottom-color: #f9a825; }
-    .col-header-c { background: #fafafa; border-bottom-color: #bdbdbd; }
-    .persona-col-header { background: #fafbfc; }
-
-    .col-title { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 15px; }
-    .col-subtitle { font-size: 11px; color: #888; font-weight: 400; }
-    .grade-dot { width: 10px; height: 10px; border-radius: 50%; }
-    .dot-s { background: #c62828; }
-    .dot-a { background: #e65100; }
-    .dot-b { background: #f9a825; }
-    .dot-c { background: #bdbdbd; }
-    .stage-dot { width: 10px; height: 10px; border-radius: 50%; }
-    .persona-dot {
-      width: 24px; height: 24px; border-radius: 6px;
-      display: flex; align-items: center; justify-content: center;
-    }
-    .persona-dot mat-icon { font-size: 14px; width: 14px; height: 14px; color: #fff; }
-    .col-count { background: rgba(0,0,0,0.08); padding: 2px 10px; border-radius: 10px; font-size: 12px; font-weight: 600; }
-
-    .column-body { flex: 1; padding: 12px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
-
-    .lead-card {
-      background: #fff; border-radius: 10px; padding: 14px;
-      box-shadow: 0 1px 4px rgba(0,0,0,0.06); cursor: pointer;
-      transition: all 0.2s; border-left: 3px solid #ccc;
-    }
-    .lead-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.12); transform: translateY(-1px); }
-
-    .lead-card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
-    .company-name { font-size: 14px; font-weight: 600; color: #1a1a2e; }
-    .country-flag { font-size: 16px; }
-
-    .lead-card-tags { display: flex; gap: 4px; margin-bottom: 8px; flex-wrap: wrap; }
-    .tag { font-size: 10px; padding: 1px 7px; border-radius: 8px; font-weight: 600; }
-    .persona-tag { font-weight: 700; }
-    .cat-a { background: #e3f2fd; color: #1565c0; }
-    .cat-b { background: #e8f5e9; color: #2e7d32; }
-    .cat-c { background: #f3e5f5; color: #6a1b9a; }
-    .cat-d { background: #fff3e0; color: #e65100; }
-    .source-tag { background: #f5f5f5; color: #777; }
-
-    .lead-card-meta { display: flex; justify-content: space-between; margin-bottom: 8px; }
-    .meta { display: flex; align-items: center; gap: 3px; font-size: 12px; color: #666; }
-    .meta mat-icon { font-size: 14px; width: 14px; height: 14px; color: #999; }
-
-    .lead-card-bottom { display: flex; justify-content: space-between; align-items: center; }
-    .stage-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
-    .stage-screened { background: #e3f2fd; color: #1565c0; }
-    .stage-contacted { background: #e8f5e9; color: #2e7d32; }
-    .stage-replied { background: #fff3e0; color: #e65100; }
-    .stage-sample { background: #f3e5f5; color: #6a1b9a; }
-    .stage-quote { background: #e1f5fe; color: #0277bd; }
-    .stage-order { background: #e8f5e9; color: #1b5e20; }
-
-    .next-date { display: flex; align-items: center; gap: 3px; font-size: 11px; color: #888; }
-    .next-date mat-icon { font-size: 13px; width: 13px; height: 13px; }
-    .next-date.overdue { color: #c62828; font-weight: 600; }
-
-    .lead-card-verify {
-      display: flex; align-items: center; gap: 4px;
-      margin-top: 6px; font-size: 11px; color: #e65100;
-      background: #fff3e0; padding: 2px 8px; border-radius: 6px;
-      width: fit-content;
-    }
-    .lead-card-verify mat-icon { font-size: 13px; width: 13px; height: 13px; }
-
-    .empty-column {
-      display: flex; flex-direction: column; align-items: center; justify-content: center;
-      padding: 40px 10px; color: #ccc; font-size: 13px;
-    }
-    .empty-column mat-icon { font-size: 32px; width: 32px; height: 32px; margin-bottom: 8px; }
-
-    @media (max-width: 1100px) { .kanban-board { grid-template-columns: repeat(2, 1fr); } .stage-board { grid-template-columns: repeat(3, 1fr); } }
-    @media (max-width: 600px) { .kanban-board { grid-template-columns: 1fr; } .header-stats { display: none; } }
-  `]
+    templateUrl: './kanban.component.html',
+    styleUrls: ['./kanban.component.scss'],
 })
 })
 export class KanbanComponent implements OnInit {
 export class KanbanComponent implements OnInit {
   leads: Lead[] = [];
   leads: Lead[] = [];

+ 1113 - 0
lead-discovery/src/app/pages/lead-detail/lead-detail.component.html

@@ -0,0 +1,1113 @@
+<div class="page-container" *ngIf="lead">
+  <!-- Back -->
+  <a class="back-link" (click)="goBack()">
+    <mat-icon>arrow_back</mat-icon> 返回上一页
+  </a>
+
+  <!-- Header Card -->
+  <div class="header-card card">
+    <div class="header-top">
+      <div class="header-left">
+        <div class="company-avatar" [style.background]="getPersonaColor(lead.persona)">
+          <mat-icon>{{ getPersonaIcon(lead.persona) }}</mat-icon>
+        </div>
+        <div>
+          <h1>{{ lead.companyName }}</h1>
+          <div class="header-tags">
+            <span class="persona-badge" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
+              <mat-icon class="badge-sm-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
+              {{ lead.persona }}-{{ lead.personaLabel }}
+            </span>
+            <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">
+              {{ lead.valueGrade }}级
+            </span>
+            <span class="badge stage-badge" [class]="'stage-' + lead.followUpStage">
+              {{ getStageLabel(lead.followUpStage) }}
+            </span>
+            <span class="verify-badge" [class.verified]="lead.personaVerified">
+              <mat-icon>{{ lead.personaVerified ? 'verified' : 'pending' }}</mat-icon>
+              {{ lead.personaVerified ? '已验证' : '待验证' }}
+            </span>
+          </div>
+        </div>
+      </div>
+      <div class="header-right">
+        <div class="value-display">
+        </div>
+        <button
+          mat-raised-button
+          [color]="isInvalidLead() && !lead.hasBackgroundCheck ? 'warn' : 'primary'"
+          (click)="startDeepAnalysis()"
+          [disabled]="isBgChecking || !lead.personaVerified"
+          [matTooltip]="!lead.personaVerified ? '请先完成销售验证' : isInvalidLead() && !lead.hasBackgroundCheck ? 'AI建议不进行背调,但您仍可手动启动' : ''">
+          <mat-icon>{{ lead.hasBackgroundCheck ? 'assessment' : isBgChecking ? 'sync' : isInvalidLead() ? 'warning' : 'search' }}</mat-icon>
+          {{ lead.hasBackgroundCheck ? '查看背调报告' : isBgChecking ? '背调进行中...' : '启动深度分析' }}
+        </button>
+        <button
+          *ngIf="lead.hasBackgroundCheck"
+          mat-raised-button
+          class="generate-quote-btn"
+          (click)="generateQuotation()"
+          matTooltip="基于背调结果生成产品报价方案">
+          <mat-icon>request_quote</mat-icon>
+          生成报价单
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- Content Grid -->
+  <div class="content-grid">
+    <!-- Left Column -->
+    <div class="left-col">
+      <!-- Persona Card (NEW) -->
+      <div class="section-card card persona-insight-card"
+           [style.border-left-color]="getPersonaColor(lead.persona)">
+        <div class="section-header">
+          <mat-icon [style.color]="getPersonaColor(lead.persona)">psychology</mat-icon>
+          <h3>客户画像分析</h3>
+          <span class="confidence-badge">置信度 {{ lead.personaConfidence }}%</span>
+        </div>
+
+        <div class="persona-insight-body">
+          <div class="insight-row">
+            <span class="insight-label">画像类型</span>
+            <span class="persona-badge lg" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
+              <mat-icon class="badge-sm-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
+              {{ lead.persona }}-{{ lead.personaLabel }}
+            </span>
+          </div>
+
+          <div class="insight-row">
+            <span class="insight-label">AI 判断依据</span>
+            <div class="insight-keywords">
+              <span class="kw-chip" *ngFor="let kw of lead.quickScreenResult.matchedKeywords">{{ kw }}</span>
+            </div>
+          </div>
+
+          <div class="insight-row">
+            <span class="insight-label">信息获取策略</span>
+            <span class="insight-value">
+              <mat-icon>{{ getPersonaStrategyIcon(lead.persona) }}</mat-icon>
+              {{ getPersonaStrategyLabel(lead.persona) }}
+            </span>
+          </div>
+
+          <div class="insight-row">
+            <span class="insight-label">推荐首要动作</span>
+            <span class="insight-value highlight">{{ getPersonaFirstAction(lead.persona) }}</span>
+          </div>
+
+          <div class="insight-row">
+            <span class="insight-label">销售策略</span>
+            <span class="insight-value">{{ getPersonaSalesStrategy(lead.persona) }}</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- Quick Screen Result -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>bolt</mat-icon>
+          <h3>快速筛选结果</h3>
+          <span class="time-badge">{{ lead.quickScreenResult.processingTime / 1000 | number:'1.1-1' }}s</span>
+        </div>
+
+        <!-- Company Info Section -->
+        <div class="quick-screen-subsection">
+          <div class="subsection-header">
+            <mat-icon class="subsection-icon">business</mat-icon>
+            <h4>公司信息</h4>
+          </div>
+          <div class="info-grid">
+            <div class="info-item">
+              <span class="info-label">公司名称</span>
+              <span class="info-value">{{ lead.companyName || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">域名</span>
+              <span class="info-value">{{ lead.quickScreenResult.domain || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">国家</span>
+              <span class="info-value">{{ getFlag(lead.country) }} {{ lead.country }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">主营业务</span>
+              <span class="info-value">{{ lead.quickScreenResult.mainBusiness }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">预估规模</span>
+              <span class="info-value">{{ lead.quickScreenResult.estimatedScale }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">产品相关性</span>
+              <span class="info-value rel-tag" [class]="'rel-' + lead.quickScreenResult.productRelevance">
+                {{ lead.quickScreenResult.productRelevance === 'high' ? '高' : lead.quickScreenResult.productRelevance === 'medium' ? '中' : '低' }}
+              </span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">置信度</span>
+              <span class="info-value">{{ lead.quickScreenResult.confidence }}%</span>
+            </div>
+          </div>
+          <div class="certs-row" *ngIf="lead.quickScreenResult.certRequirements.length">
+            <span class="info-label">需要认证</span>
+            <div class="cert-chips">
+              <span class="cert-chip" *ngFor="let c of lead.quickScreenResult.certRequirements">{{ c }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- Contact Info Section -->
+        <div class="quick-screen-subsection">
+          <div class="subsection-header">
+            <mat-icon class="subsection-icon">person</mat-icon>
+            <h4>联系人信息</h4>
+          </div>
+          <div class="info-grid">
+            <div class="info-item">
+              <span class="info-label">姓名</span>
+              <span class="info-value">{{ lead.contactName || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">来源</span>
+              <span class="info-value">{{ lead.sourceLabel || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">邮箱</span>
+              <span class="info-value email-link">{{ lead.contactEmail || '-' }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">创建时间</span>
+              <span class="info-value">{{ lead.createdAt | date:'yyyy-MM-dd HH:mm' }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Demand Summary -->
+      <div class="section-card card demand-section" *ngIf="getDemandSnapshot()">
+        <div class="section-header">
+          <mat-icon>shopping_bag</mat-icon>
+          <h3>需求摘要</h3>
+        </div>
+        <div class="demand-content">
+          <div class="demand-list">
+            <div class="demand-item" *ngFor="let p of getDemandSnapshot()?.products">
+              <span class="demand-product">· {{ p.name }} x {{ p.quantity }}{{ p.unit }}</span>
+            </div>
+          </div>
+          <div class="estimated-value" *ngIf="getDemandSnapshot()?.estimatedAnnualValue">
+            <mat-icon class="money-icon">account_balance_wallet</mat-icon>
+            预估年采购额:${{ formatNumber(getDemandSnapshot()!.estimatedAnnualValue) }}+
+          </div>
+        </div>
+      </div>
+
+      <!-- Original Email -->
+      <div class="section-card card email-card" *ngIf="getEmailForLead(lead) as email">
+        <div class="section-header">
+          <mat-icon>email</mat-icon>
+          <h3>原始邮件信息</h3>
+          <span class="time-badge">{{ email.receivedAt | date:'yyyy-MM-dd HH:mm' }}</span>
+        </div>
+
+        <div class="email-subject-row">
+          <mat-icon class="email-meta-icon">subject</mat-icon>
+          <span class="email-subject">{{ email.subject }}</span>
+        </div>
+
+        <div class="email-meta-grid">
+          <div class="email-meta-item">
+            <span class="email-meta-label">发件人</span>
+            <span class="email-meta-value">
+              <strong>{{ email.fromName }}</strong>
+              &lt;{{ email.from }}&gt;
+            </span>
+          </div>
+          <div class="email-meta-item">
+            <span class="email-meta-label">收件人</span>
+            <span class="email-meta-value">{{ email.to }}</span>
+          </div>
+          <div class="email-meta-item" *ngIf="email.cc">
+            <span class="email-meta-label">抄送</span>
+            <span class="email-meta-value">{{ email.cc }}</span>
+          </div>
+        </div>
+
+        <mat-divider style="margin: 12px 0;"></mat-divider>
+
+        <div class="email-body-section">
+          <div class="email-body" [class.collapsed]="!emailBodyExpanded">
+            <pre class="email-body-text">{{ email.body }}</pre>
+          </div>
+          <button mat-button class="expand-btn" (click)="emailBodyExpanded = !emailBodyExpanded">
+            <mat-icon>{{ emailBodyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>
+            {{ emailBodyExpanded ? '收起' : '展开全文' }}
+          </button>
+        </div>
+
+        <div class="email-attachments" *ngIf="email.attachments.length">
+          <mat-divider style="margin: 8px 0 12px;"></mat-divider>
+          <div class="attachments-header">
+            <mat-icon>attach_file</mat-icon>
+            <span>附件 ({{ email.attachments.length }})</span>
+          </div>
+          <div class="attachment-list">
+            <div class="attachment-chip" *ngFor="let att of email.attachments">
+              <mat-icon class="att-icon">{{ att.icon }}</mat-icon>
+              <span class="att-name">{{ att.fileName }}</span>
+              <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- No Email Found -->
+      <div class="section-card card no-email-card" *ngIf="!getEmailForLead(lead)">
+        <div class="section-header">
+          <mat-icon>email</mat-icon>
+          <h3>原始邮件信息</h3>
+        </div>
+        <div class="no-email-placeholder">
+          <mat-icon class="placeholder-sm">mail_outline</mat-icon>
+          <span>该线索无关联邮件(来源:{{ lead.sourceLabel }})</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- Right Column -->
+    <div class="right-col">
+      <!-- Product Recommendations -->
+      <div class="section-card card">
+        <div class="section-header">
+          <mat-icon>inventory_2</mat-icon>
+          <h3>推荐产品</h3>
+        </div>
+        <div class="product-list">
+          <div class="product-card" *ngFor="let p of lead.quickScreenResult.suggestedProducts; let i = index">
+            <div class="product-rank" [class]="'rank-' + (i < 3 ? i + 1 : 'other')">{{ i + 1 }}</div>
+            <div class="product-info">
+              <div class="product-name">{{ p.name }}</div>
+              <div class="product-en">{{ p.nameEn }}</div>
+              <div class="product-sku-row">
+                <span class="sku-tag">{{ p.sku }}</span>
+                <span class="product-reason">{{ p.reason }}</span>
+              </div>
+            </div>
+            <div class="product-pricing">
+              <span class="wholesale-price">${{ p.wholesalePrice }}</span>
+              <span class="price-label">批发价</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Credibility + Scores + AI Next Steps (combined into one card) -->
+      <div class="section-card card credibility-score-ai-card">
+        <!-- Credibility -->
+        <div class="credibility-section">
+          <div class="section-header">
+            <mat-icon>verified</mat-icon>
+            <h3>可信度:{{ getCredibilityLevel() }}</h3>
+          </div>
+          <div class="credibility-content">
+            <div class="credibility-signals">
+              <span class="signal-item" *ngFor="let signal of getCredibilitySignals()">
+                · {{ signal }}
+              </span>
+            </div>
+          </div>
+        </div>
+
+        <!-- Score Details (Expandable) -->
+        <div class="scores-section">
+          <mat-expansion-panel class="scores-panel">
+            <mat-expansion-panel-header>
+              <mat-panel-title>
+                <mat-icon>assessment</mat-icon>
+                <span>评分详情</span>
+              </mat-panel-title>
+            </mat-expansion-panel-header>
+            <div class="scores-grid">
+              <div class="score-item">
+                <span class="score-label">需求明确度</span>
+                <div class="score-bar-wrap">
+                  <div class="score-bar" [style.width]="(getScores()?.demandClarity || 0) + '%'"></div>
+                </div>
+                <span class="score-value">{{ getScores()?.demandClarity || 0 }}分</span>
+              </div>
+              <div class="score-item">
+                <span class="score-label">可信度</span>
+                <div class="score-bar-wrap">
+                  <div class="score-bar" [style.width]="(getScores()?.credibility || 0) + '%'"></div>
+                </div>
+                <span class="score-value">{{ getScores()?.credibility || 0 }}分</span>
+              </div>
+              <div class="score-item">
+                <span class="score-label">商业规模</span>
+                <div class="score-bar-wrap">
+                  <div class="score-bar" [style.width]="(getScores()?.businessScale || 0) + '%'"></div>
+                </div>
+                <span class="score-value">{{ getScores()?.businessScale || 0 }}分</span>
+              </div>
+              <div class="score-item">
+                <span class="score-label">供需匹配</span>
+                <div class="score-bar-wrap">
+                  <div class="score-bar" [style.width]="(getScores()?.productFit || 0) + '%'"></div>
+                </div>
+                <span class="score-value">{{ getScores()?.productFit || 0 }}分</span>
+              </div>
+            </div>
+          </mat-expansion-panel>
+        </div>
+
+        <!-- AI Recommended Next Steps -->
+        <div class="ai-next-steps-section">
+          <div class="section-header">
+            <mat-icon>lightbulb</mat-icon>
+            <h3>AI推荐的下一步操作</h3>
+          </div>
+          <div class="next-steps-content">
+            <!-- 无效线索警告 -->
+            <div class="next-step-invalid-warning" *ngIf="isInvalidLead()">
+              <div class="step-header">
+                <mat-icon class="step-icon-warn">warning</mat-icon>
+                <span class="step-title-warn">该线索被AI判定为无效线索</span>
+              </div>
+              <p class="step-warn-desc">AI 分析认为该线索为低质量/无效线索(P6),建议自动归档,不投入人工跟进。</p>
+            </div>
+
+            <div class="next-step-main" *ngIf="getRecommendedNextStep()">
+              <div class="step-header">
+                <mat-icon class="step-icon">check_circle</mat-icon>
+                <span class="step-title">推荐操作</span>
+              </div>
+              <p class="step-description">{{ getRecommendedNextStep() }}</p>
+            </div>
+
+            <div class="next-steps-details" *ngIf="getRecommendedNextStepsDetails()">
+              <div class="detail-item" *ngIf="getRecommendedNextStepsDetails()?.timeframe">
+                <mat-icon class="detail-icon">schedule</mat-icon>
+                <div class="detail-content">
+                  <span class="detail-label">建议时间</span>
+                  <span class="detail-value">{{ getRecommendedNextStepsDetails()?.timeframe }}</span>
+                </div>
+              </div>
+
+              <div class="detail-item" *ngIf="getRecommendedNextStepsDetails()?.filesToSend?.length">
+                <mat-icon class="detail-icon">attach_file</mat-icon>
+                <div class="detail-content">
+                  <span class="detail-label">建议发送文件</span>
+                  <div class="files-list">
+                    <span class="file-tag" *ngFor="let file of getRecommendedNextStepsDetails()?.filesToSend">{{ file }}</span>
+                  </div>
+                </div>
+              </div>
+
+              <div class="detail-item" *ngIf="getRecommendedNextStepsDetails()?.keyPoints?.length">
+                <mat-icon class="detail-icon">highlight</mat-icon>
+                <div class="detail-content">
+                  <span class="detail-label">重点强调</span>
+                  <ul class="key-points-list">
+                    <li *ngFor="let point of getRecommendedNextStepsDetails()?.keyPoints">{{ point }}</li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+
+            <div class="no-recommendation" *ngIf="!getRecommendedNextStep() && !getRecommendedNextStepsDetails()">
+              <mat-icon>info</mat-icon>
+              <span>暂无AI推荐操作</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Sales Verification (NEW) -->
+      <div class="section-card card verify-section">
+        <div class="section-header">
+          <mat-icon>fact_check</mat-icon>
+          <h3>销售验证</h3>
+          <span class="verify-status-badge" [class.verified]="lead.personaVerified">
+            {{ lead.personaVerified ? '✅ 已验证' : '⏳ 待验证' }}
+          </span>
+        </div>
+
+        <div class="verify-form" *ngIf="!lead.personaVerified">
+          <p class="verify-prompt">请确认 AI 的判断结果:</p>
+
+          <div class="verify-row">
+            <span class="verify-label">画像分类</span>
+            <span class="verify-current">{{ lead.persona }}-{{ lead.personaLabel }}</span>
+            <mat-form-field appearance="outline" class="verify-select">
+              <mat-label>修改为</mat-label>
+              <mat-select [(ngModel)]="correctedPersona">
+                <mat-option *ngFor="let p of personaOptions" [value]="p.id">{{ p.id }}-{{ p.name }}</mat-option>
+              </mat-select>
+            </mat-form-field>
+          </div>
+
+          <div class="verify-row">
+            <span class="verify-label">价值等级</span>
+            <span class="verify-current badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">{{ lead.valueGrade }}级</span>
+            <mat-form-field appearance="outline" class="verify-select">
+              <mat-label>修改为</mat-label>
+              <mat-select [(ngModel)]="correctedGrade">
+                <mat-option value="S">S级</mat-option>
+                <mat-option value="A">A级</mat-option>
+                <mat-option value="B">B级</mat-option>
+                <mat-option value="C">C级</mat-option>
+              </mat-select>
+            </mat-form-field>
+          </div>
+
+          <mat-form-field appearance="outline" class="verify-notes-field">
+            <mat-label>备注(可选)</mat-label>
+            <textarea matInput [(ngModel)]="verifyNotes" rows="2"
+                      placeholder="记录您的判断依据或补充信息..."></textarea>
+          </mat-form-field>
+
+          <div class="verify-actions">
+            <button mat-raised-button color="primary" class="action-btn" (click)="confirmLead()">
+              <mat-icon>check</mat-icon> 确认
+            </button>
+            <button mat-stroked-button class="action-btn" (click)="editLead()">
+              <mat-icon>edit</mat-icon> 修改
+            </button>
+            <button mat-stroked-button color="warn" class="action-btn" (click)="dismissLead()">
+              <mat-icon>close</mat-icon> 无效
+            </button>
+          </div>
+        </div>
+
+        <div class="verify-done" *ngIf="lead.personaVerified">
+          <mat-icon class="done-icon">verified</mat-icon>
+          <p>画像已验证为 <strong>{{ lead.persona }}-{{ lead.personaLabel }}</strong></p>
+          <p *ngIf="lead.salesFeedback?.notes" class="done-notes">
+            备注:{{ lead.salesFeedback!.notes }}
+          </p>
+        </div>
+      </div>
+
+      <!-- Deep Research Package (跟进准备包) -->
+      <app-deep-research-package
+        *ngIf="deepResearchResult"
+        [lead]="lead"
+        [researchResult]="deepResearchResult"
+        (editScript)="onEditScript()"
+        (sendEmail)="onSendEmail()"
+        (createLead)="onCreateLead()">
+      </app-deep-research-package>
+
+      <!-- AI Research Report (保留作为备用显示) -->
+      <div class="section-card card" *ngIf="lead.aiResearchReport && !deepResearchResult">
+        <div class="section-header">
+          <mat-icon>analytics</mat-icon>
+          <h3>AI 调研报告</h3>
+          <span class="badge grade-a">已完成</span>
+        </div>
+        <div class="deep-report-preview">
+          <div class="report-item">
+            <mat-icon>business</mat-icon>
+            <div>
+              <strong>官网分析</strong>
+              <p>{{ lead.aiResearchReport.websiteAnalysis }}</p>
+            </div>
+          </div>
+          <div class="report-item" *ngIf="lead.aiResearchReport.productMatching.length">
+            <mat-icon>shopping_cart</mat-icon>
+            <div>
+              <strong>产品匹配</strong>
+              <div class="match-list">
+                <div class="match-item" *ngFor="let m of lead.aiResearchReport.productMatching">
+                  <span>{{ m.ourProduct }} ({{ m.ourSku }}) → {{ m.matchesNeed }}</span>
+                  <span class="match-margin">{{ m.profitMargin }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="report-item">
+            <mat-icon>groups</mat-icon>
+            <div>
+              <strong>竞品分析</strong>
+              <p>{{ lead.aiResearchReport.competitorAnalysis }}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Background Check Progress -->
+      <div class="section-card card" *ngIf="isBgChecking">
+        <div class="section-header">
+          <mat-icon>sync</mat-icon>
+          <h3>背调进行中</h3>
+        </div>
+        <app-bgcheck-progress
+          [stages]="bgCheckStages"
+          [overallProgress]="bgCheckProgress">
+        </app-bgcheck-progress>
+      </div>
+
+      <!-- Background Check Result -->
+      <div class="section-card card" *ngIf="lead.hasBackgroundCheck && !isBgChecking">
+        <div class="section-header">
+          <mat-icon>assessment</mat-icon>
+          <h3>背调报告</h3>
+          <span class="badge grade-a">已完成</span>
+        </div>
+        <div class="bgcheck-summary">
+          <div class="summary-item">
+            <span class="summary-label">完成时间</span>
+            <span class="summary-value">{{ lead.backgroundCheckAt | date:'yyyy-MM-dd HH:mm' }}</span>
+          </div>
+          <div class="summary-item" *ngIf="lead.backgroundCheckReport">
+            <span class="summary-label">综合评级</span>
+            <span class="summary-value badge" [class]="'level-' + lead.backgroundCheckReport.rating.level.toLowerCase()">
+              {{ lead.backgroundCheckReport.rating.level }}级 ({{ lead.backgroundCheckReport.rating.overallScore }}分)
+            </span>
+          </div>
+          <div class="summary-item" *ngIf="lead.backgroundCheckReport">
+            <span class="summary-label">风险等级</span>
+            <span class="summary-value" [class]="'risk-' + lead.backgroundCheckReport.risk.level">
+              {{ lead.backgroundCheckReport.risk.level === 'low' ? '低' : lead.backgroundCheckReport.risk.level === 'medium' ? '中' : '高' }}风险
+            </span>
+          </div>
+        </div>
+        <button mat-raised-button color="primary" (click)="viewBgCheckReport()">
+          <mat-icon>description</mat-icon> 查看完整报告
+        </button>
+      </div>
+
+      <!-- Deep Analysis Placeholder -->
+      <div class="section-card card deep-analysis-placeholder" *ngIf="!lead.hasBackgroundCheck && !lead.aiResearchReport && !deepResearchResult && !isBgChecking"
+           [class.invalid-lead-placeholder]="isInvalidLead()">
+        <div class="placeholder-content">
+          <!-- 无效线索:不建议背调提示 -->
+          <ng-container *ngIf="isInvalidLead(); else normalPlaceholder">
+            <mat-icon class="placeholder-icon warn-icon">do_not_disturb</mat-icon>
+            <h3>AI 不建议进行深度背调</h3>
+            <div class="invalid-lead-notice">
+              <div class="notice-box">
+                <mat-icon>report_problem</mat-icon>
+                <div>
+                  <strong>该线索已被AI判定为低质量/无效线索(P6)</strong>
+                  <p>AI 分析认为该线索不值得投入背调资源。但 AI 分析可能不准确,如果您认为该线索有价值,仍可手动启动深度分析。</p>
+                </div>
+              </div>
+            </div>
+            <p class="estimate">预估耗时:3-5 分钟</p>
+            <button
+              mat-raised-button
+              color="warn"
+              (click)="startDeepAnalysis()"
+              [disabled]="!lead.personaVerified"
+              [matTooltip]="!lead.personaVerified ? '请先完成销售验证' : ''">
+              <mat-icon>play_arrow</mat-icon> 仍然启动深度分析
+            </button>
+            <p class="invalid-hint" *ngIf="lead.personaVerified">
+              <mat-icon>info</mat-icon>
+              AI 判断仅供参考,您可以根据实际情况决定是否进行背调
+            </p>
+            <p class="verify-hint" *ngIf="!lead.personaVerified" style="margin-top: 12px; font-size: 13px; color: #ff9800; text-align: center;">
+              <mat-icon style="font-size: 16px; width: 16px; height: 16px; vertical-align: middle;">info</mat-icon>
+              请先完成上方的"销售验证",确认画像分类和价值等级后再启动深度分析
+            </p>
+          </ng-container>
+
+          <!-- 正常线索:原有内容 -->
+          <ng-template #normalPlaceholder>
+            <mat-icon class="placeholder-icon">search</mat-icon>
+            <h3>AI 调研报告</h3>
+            <p>启动深度分析后,AI 将根据画像类型执行差异化调研:</p>
+            <ul>
+              <li>🌐 {{ getPersonaStrategyLabel(lead.persona) }}</li>
+              <li>📦 产品匹配与利润分析</li>
+              <li>🏭 竞品与供应链分析</li>
+              <li>📋 销售策略与话术生成</li>
+            </ul>
+            <p class="estimate">预估耗时:3-5 分钟</p>
+            <button
+              mat-raised-button
+              color="primary"
+              (click)="startDeepAnalysis()"
+              [disabled]="!lead.personaVerified"
+              [matTooltip]="!lead.personaVerified ? '请先完成销售验证' : ''">
+              <mat-icon>play_arrow</mat-icon> 启动深度分析
+            </button>
+            <p class="verify-hint" *ngIf="!lead.personaVerified" style="margin-top: 12px; font-size: 13px; color: #ff9800; text-align: center;">
+              <mat-icon style="font-size: 16px; width: 16px; height: 16px; vertical-align: middle;">info</mat-icon>
+              请先完成上方的"销售验证",确认画像分类和价值等级后再启动深度分析
+            </p>
+          </ng-template>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="page-container" *ngIf="!lead">
+  <div class="empty-state card">
+    <mat-icon>search_off</mat-icon>
+    <p>未找到该线索</p>
+    <a mat-button (click)="goBack()">返回上一页</a>
+  </div>
+</div>
+
+<!-- ===== QUOTATION MODAL ===== -->
+<div class="quotation-overlay" *ngIf="showQuotationModal" (click)="closeQuotation()">
+  <div class="quotation-modal" (click)="$event.stopPropagation()">
+    <!-- Header -->
+    <div class="q-header">
+      <div class="q-header-content">
+        <h2>📄 {{ quotationData?.title || '产品报价方案' }}</h2>
+        <div class="q-meta">
+          <span class="q-number">{{ quotationData?.proposalNumber }}</span>
+          <span class="q-date">{{ quotationData?.preparedDate }}</span>
+          <span class="q-validity">有效期至: {{ quotationData?.validUntil }}</span>
+        </div>
+      </div>
+      <button class="q-close" (click)="closeQuotation()">&times;</button>
+    </div>
+
+    <div class="q-body">
+      <!-- 客户信息 -->
+      <section class="q-section">
+        <h3>🏢 客户信息</h3>
+        <div class="q-info-grid">
+          <div class="q-info-item">
+            <span class="q-label">公司名称</span>
+            <span class="q-value">{{ quotationData?.customerSummary?.companyName }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">联系人</span>
+            <span class="q-value">{{ quotationData?.customerSummary?.contactPerson }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">邮箱</span>
+            <span class="q-value q-email">{{ quotationData?.customerSummary?.email }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">国家</span>
+            <span class="q-value">{{ quotationData?.customerSummary?.country }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">行业</span>
+            <span class="q-value">{{ quotationData?.customerSummary?.industry }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">客户画像</span>
+            <span class="q-value">{{ quotationData?.customerSummary?.persona }}</span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">价值等级</span>
+            <span class="q-value"><span class="badge" [class]="'grade-' + quotationData?.customerSummary?.valueGrade?.toLowerCase()">{{ quotationData?.customerSummary?.valueGrade }}级</span></span>
+          </div>
+          <div class="q-info-item">
+            <span class="q-label">预估订单价值</span>
+            <span class="q-value q-highlight-value">USD {{ quotationData?.customerSummary?.estimatedOrderValue?.toLocaleString() }}</span>
+          </div>
+        </div>
+        <div class="q-project-bg" *ngIf="quotationData?.customerSummary?.projectBackground">
+          <strong>客户背景:</strong>{{ quotationData?.customerSummary?.projectBackground }}
+        </div>
+      </section>
+
+      <!-- 背调洞察 -->
+      <section class="q-section" *ngIf="quotationData?.backgroundInsights">
+        <h3>🔍 背调洞察</h3>
+        <div class="q-insight-grid">
+          <div class="q-insight-card">
+            <div class="q-insight-icon">🏭</div>
+            <h4>企业概况</h4>
+            <p>{{ quotationData?.backgroundInsights?.companyOverview }}</p>
+          </div>
+          <div class="q-insight-card">
+            <div class="q-insight-icon">📊</div>
+            <h4>市场定位</h4>
+            <p>{{ quotationData?.backgroundInsights?.marketPosition }}</p>
+          </div>
+          <div class="q-insight-card">
+            <div class="q-insight-icon">🛒</div>
+            <h4>采购行为分析</h4>
+            <p>{{ quotationData?.backgroundInsights?.purchasingBehavior }}</p>
+          </div>
+          <div class="q-insight-card q-risk-card" *ngIf="quotationData?.backgroundInsights?.riskAssessment">
+            <div class="q-insight-icon">⚠️</div>
+            <h4>风险评估</h4>
+            <div class="q-risk-level" [style.color]="quotationData?.backgroundInsights?.riskAssessment?.color">
+              <span class="q-risk-dot" [style.background]="quotationData?.backgroundInsights?.riskAssessment?.color"></span>
+              {{ quotationData?.backgroundInsights?.riskAssessment?.level }}
+            </div>
+            <ul class="q-risk-factors">
+              <li *ngFor="let f of quotationData?.backgroundInsights?.riskAssessment?.factors">{{ f }}</li>
+            </ul>
+          </div>
+        </div>
+        <div class="q-competitor-section" *ngIf="quotationData?.backgroundInsights?.competitorAnalysis?.length">
+          <h4>📌 竞品分析</h4>
+          <ul class="q-competitor-list">
+            <li *ngFor="let c of quotationData?.backgroundInsights?.competitorAnalysis">{{ c }}</li>
+          </ul>
+        </div>
+      </section>
+
+      <!-- 方案亮点 -->
+      <section class="q-section q-highlights-section" *ngIf="quotationData?.solutionHighlights?.length">
+        <h3>✨ 方案亮点</h3>
+        <div class="q-highlights-grid">
+          <div class="q-highlight-card" *ngFor="let h of quotationData?.solutionHighlights">
+            <div class="q-highlight-icon">{{ h.icon }}</div>
+            <div class="q-highlight-body">
+              <strong>{{ h.title }}</strong>
+              <span>{{ h.desc }}</span>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <!-- 执行摘要 -->
+      <section class="q-section">
+        <h3>📋 执行摘要</h3>
+        <p class="q-summary">{{ quotationData?.executiveSummary }}</p>
+      </section>
+
+      <!-- 需求分析 -->
+      <section class="q-section" *ngIf="quotationData?.requirementAnalysis">
+        <div class="q-section-header">
+          <h3>📊 需求分析</h3>
+          <span class="q-completeness-badge">
+            完整度 {{ (quotationData?.requirementAnalysis?.completeness * 100).toFixed(0) }}%
+          </span>
+        </div>
+        <div class="q-req-categories">
+          <div class="q-req-category">
+            <div class="q-req-header">
+              <span class="q-req-icon">👤</span>
+              <h4>客户需求</h4>
+              <span class="q-req-count">{{ quotationData?.requirementAnalysis?.customerNeeds?.length }} 项</span>
+            </div>
+            <ul class="q-req-list">
+              <li *ngFor="let need of quotationData?.requirementAnalysis?.customerNeeds">{{ need }}</li>
+            </ul>
+          </div>
+          <div class="q-req-category">
+            <div class="q-req-header">
+              <span class="q-req-icon">⚙️</span>
+              <h4>技术要求</h4>
+              <span class="q-req-count">{{ quotationData?.requirementAnalysis?.technicalRequirements?.length }} 项</span>
+            </div>
+            <ul class="q-req-list">
+              <li *ngFor="let req of quotationData?.requirementAnalysis?.technicalRequirements">{{ req }}</li>
+            </ul>
+          </div>
+          <div class="q-req-category">
+            <div class="q-req-header">
+              <span class="q-req-icon">🛡️</span>
+              <h4>合规要求</h4>
+              <span class="q-req-count">{{ quotationData?.requirementAnalysis?.complianceRequirements?.length }} 项</span>
+            </div>
+            <ul class="q-req-list">
+              <li *ngFor="let comp of quotationData?.requirementAnalysis?.complianceRequirements">{{ comp }}</li>
+            </ul>
+          </div>
+        </div>
+      </section>
+
+      <!-- 产品推荐表格 -->
+      <section class="q-section">
+        <div class="q-section-header">
+          <h3>📦 产品推荐</h3>
+          <span class="q-badge">共 {{ quotationData?.productRecommendations?.length || 0 }} 个产品</span>
+        </div>
+        <div class="q-product-table">
+          <div class="q-table-header">
+            <div class="q-col-idx">#</div>
+            <div class="q-col-code">产品编码</div>
+            <div class="q-col-name">产品名称</div>
+            <div class="q-col-spec">规格</div>
+            <div class="q-col-qty">数量</div>
+            <div class="q-col-price">单价</div>
+            <div class="q-col-total">小计</div>
+          </div>
+          <div class="q-table-row" *ngFor="let product of quotationData?.productRecommendations; let i = index" [class.alt-row]="i % 2 === 1">
+            <div class="q-col-idx">{{ i + 1 }}</div>
+            <div class="q-col-code"><span class="q-code-badge">{{ product.productCode }}</span></div>
+            <div class="q-col-name">
+              <div class="q-product-name">{{ product.productName }}</div>
+              <div class="q-match-reason" *ngIf="product.matchReason">💡 {{ product.matchReason }}</div>
+              <div class="q-cert-badges" *ngIf="product.certifications?.length">
+                <span class="q-cert-badge" *ngFor="let cert of product.certifications">{{ cert }}</span>
+              </div>
+            </div>
+            <div class="q-col-spec">
+              <div class="q-spec-text">{{ product.specifications }}</div>
+              <div class="q-key-features" *ngIf="product.keyFeatures?.length">
+                <span class="q-feature-tag" *ngFor="let f of product.keyFeatures">{{ f }}</span>
+              </div>
+            </div>
+            <div class="q-col-qty">{{ product.quantity }}</div>
+            <div class="q-col-price">{{ quotationData?.pricingSummary?.currency }} {{ product.unitPrice?.toLocaleString() }}</div>
+            <div class="q-col-total">{{ quotationData?.pricingSummary?.currency }} {{ product.totalPrice?.toLocaleString() }}</div>
+          </div>
+          <div class="q-table-footer">
+            <span class="q-total-label">总计金额</span>
+            <span class="q-total-amount">{{ quotationData?.pricingSummary?.currency }} {{ quotationData?.pricingSummary?.totalAmount?.toLocaleString() }}</span>
+          </div>
+        </div>
+        <!-- 替代产品建议 -->
+        <div class="q-alternatives" *ngIf="quotationData?.productRecommendations?.length">
+          <h4>🔄 替代产品建议</h4>
+          <div class="q-alt-grid">
+            <div class="q-alt-item" *ngFor="let product of quotationData?.productRecommendations">
+              <span class="q-alt-source">{{ product.productCode }}:</span>
+              <span class="q-alt-list" *ngFor="let alt of product.alternatives; let last = last">{{ alt }}{{ last ? '' : '、' }}</span>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <!-- 价格汇总 -->
+      <section class="q-section">
+        <h3>💰 价格汇总</h3>
+        <table class="q-pricing-table">
+          <thead>
+            <tr>
+              <th>项目描述</th>
+              <th class="text-right">数量</th>
+              <th class="text-right">单价 ({{ quotationData?.pricingSummary?.currency }})</th>
+              <th class="text-right">总价 ({{ quotationData?.pricingSummary?.currency }})</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr *ngFor="let item of quotationData?.pricingSummary?.items">
+              <td>{{ item.description }}</td>
+              <td class="text-right">{{ item.quantity }}</td>
+              <td class="text-right">{{ item.unitPrice?.toLocaleString() }}</td>
+              <td class="text-right">{{ item.totalPrice?.toLocaleString() }}</td>
+            </tr>
+          </tbody>
+          <tfoot>
+            <tr class="subtotal-row">
+              <td colspan="3" class="text-right"><strong>小计</strong></td>
+              <td class="text-right"><strong>{{ quotationData?.pricingSummary?.currency }} {{ quotationData?.pricingSummary?.subtotal?.toLocaleString() }}</strong></td>
+            </tr>
+            <tr *ngIf="quotationData?.pricingSummary?.discount > 0">
+              <td colspan="3" class="text-right">折扣 ({{ quotationData?.pricingSummary?.discountRate }}%)</td>
+              <td class="text-right text-danger">-{{ quotationData?.pricingSummary?.currency }} {{ quotationData?.pricingSummary?.discount?.toLocaleString() }}</td>
+            </tr>
+            <tr class="total-row">
+              <td colspan="3" class="text-right"><strong>总计</strong></td>
+              <td class="text-right"><strong class="q-grand-total">{{ quotationData?.pricingSummary?.currency }} {{ quotationData?.pricingSummary?.totalAmount?.toLocaleString() }}</strong></td>
+            </tr>
+          </tfoot>
+        </table>
+        <div class="q-validity-note" *ngIf="quotationData?.pricingSummary?.priceValidityPeriod">
+          <strong>报价有效期:</strong> {{ quotationData?.pricingSummary?.priceValidityPeriod }}
+        </div>
+      </section>
+
+      <!-- 商务条款 -->
+      <section class="q-section">
+        <h3>📜 商务条款</h3>
+        <div class="q-terms-grid">
+          <div class="q-term-item">
+            <div class="q-term-label">最小起订量 (MOQ)</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.moq }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">交货期</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.leadTime }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">交货条款</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.deliveryTerms }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">付款条款</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.paymentTerms }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">包装详情</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.packingDetails }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">运输方式</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.shippingMethod }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">质保期</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.warranty }}</div>
+          </div>
+          <div class="q-term-item">
+            <div class="q-term-label">退换政策</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.returnPolicy }}</div>
+          </div>
+          <div class="q-term-item" *ngIf="quotationData?.commercialTerms?.samplePolicy">
+            <div class="q-term-label">样品政策</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.samplePolicy }}</div>
+          </div>
+          <div class="q-term-item" *ngIf="quotationData?.commercialTerms?.insuranceCoverage">
+            <div class="q-term-label">保险覆盖</div>
+            <div class="q-term-value">{{ quotationData?.commercialTerms?.insuranceCoverage }}</div>
+          </div>
+        </div>
+      </section>
+
+      <!-- 质量保证 -->
+      <section class="q-section" *ngIf="quotationData?.qualityAssurance">
+        <h3>✅ 质量保证</h3>
+        <div class="q-qa-content">
+          <div class="q-qa-certs">
+            <h4>认证资质</h4>
+            <div class="q-cert-list">
+              <span class="q-cert-item" *ngFor="let cert of quotationData?.qualityAssurance?.certifications">
+                <span class="q-cert-check">✓</span> {{ cert }}
+              </span>
+            </div>
+          </div>
+          <div class="q-qa-testing" *ngIf="quotationData?.qualityAssurance?.testingStandards?.length">
+            <h4>检测标准</h4>
+            <div class="q-testing-list">
+              <span class="q-testing-item" *ngFor="let std of quotationData?.qualityAssurance?.testingStandards">{{ std }}</span>
+            </div>
+          </div>
+          <div class="q-qa-process" *ngIf="quotationData?.qualityAssurance?.qualityProcess?.length">
+            <h4>质控流程</h4>
+            <div class="q-process-flow">
+              <div class="q-process-step" *ngFor="let step of quotationData?.qualityAssurance?.qualityProcess; let i = index; let last = last">
+                <div class="q-process-dot">{{ i + 1 }}</div>
+                <span>{{ step }}</span>
+                <span class="q-process-arrow" *ngIf="!last">→</span>
+              </div>
+            </div>
+          </div>
+          <div class="q-qa-trace" *ngIf="quotationData?.qualityAssurance?.traceability">
+            <h4>追溯体系</h4>
+            <p>{{ quotationData?.qualityAssurance?.traceability }}</p>
+          </div>
+        </div>
+      </section>
+
+      <!-- 售后服务 -->
+      <section class="q-section">
+        <h3>🛠️ 售后服务</h3>
+        <div class="q-service-grid">
+          <div class="q-service-item" *ngIf="quotationData?.afterSalesService?.technicalSupport">
+            <h4>技术支持</h4>
+            <p>{{ quotationData?.afterSalesService?.technicalSupport }}</p>
+          </div>
+          <div class="q-service-item" *ngIf="quotationData?.afterSalesService?.training">
+            <h4>培训服务</h4>
+            <p>{{ quotationData?.afterSalesService?.training }}</p>
+          </div>
+          <div class="q-service-item" *ngIf="quotationData?.afterSalesService?.warranty">
+            <h4>质保详情</h4>
+            <p>{{ quotationData?.afterSalesService?.warranty }}</p>
+          </div>
+          <div class="q-service-item" *ngIf="quotationData?.afterSalesService?.returnService">
+            <h4>退换服务</h4>
+            <p>{{ quotationData?.afterSalesService?.returnService }}</p>
+          </div>
+          <div class="q-service-item" *ngIf="quotationData?.afterSalesService?.customization">
+            <h4>定制服务</h4>
+            <p>{{ quotationData?.afterSalesService?.customization }}</p>
+          </div>
+        </div>
+      </section>
+
+      <!-- 下一步行动 -->
+      <section class="q-section" *ngIf="quotationData?.nextSteps?.length">
+        <h3>🚀 下一步行动</h3>
+        <div class="q-steps">
+          <div class="q-step" *ngFor="let step of quotationData?.nextSteps; let i = index" [class]="'q-step-priority-' + step.priority">
+            <div class="q-step-num">{{ i + 1 }}</div>
+            <div class="q-step-content">
+              <div class="q-step-action">{{ step.action }}</div>
+              <div class="q-step-meta">
+                <span>负责人: {{ step.responsible }}</span>
+                <span>截止: {{ step.deadline }}</span>
+                <span class="q-priority-tag" [class]="'q-priority-' + step.priority">{{ step.priority === 'high' ? '紧急' : step.priority === 'medium' ? '重要' : '常规' }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <!-- 内部销售指导 -->
+      <section class="q-section q-sales-guidance-section q-internal-only" *ngIf="showSalesGuidance && quotationData?.salesGuidance">
+        <div class="q-internal-banner">
+          <strong>🔒 内部销售指导</strong> (仅销售团队可见)
+        </div>
+        <div class="q-sg-subsection" *ngIf="quotationData?.salesGuidance?.keyTalkingPoints?.length">
+          <h4>关键话术</h4>
+          <ul class="q-sg-list">
+            <li *ngFor="let point of quotationData?.salesGuidance?.keyTalkingPoints">{{ point }}</li>
+          </ul>
+        </div>
+        <div class="q-sg-subsection" *ngIf="quotationData?.salesGuidance?.anticipatedObjections?.length">
+          <h4>预期异议及应对</h4>
+          <div class="q-objection-card" *ngFor="let obj of quotationData?.salesGuidance?.anticipatedObjections">
+            <div class="q-objection-question">
+              <strong>异议:</strong> {{ obj.objection }}
+            </div>
+            <div class="q-objection-response">
+              <strong>应对:</strong> {{ obj.response }}
+            </div>
+          </div>
+        </div>
+        <div class="q-sg-subsection" *ngIf="quotationData?.salesGuidance?.negotiationTips?.length">
+          <h4>谈判技巧</h4>
+          <ul class="q-sg-list">
+            <li *ngFor="let tip of quotationData?.salesGuidance?.negotiationTips">{{ tip }}</li>
+          </ul>
+        </div>
+        <div class="q-sg-subsection" *ngIf="quotationData?.salesGuidance?.closingStrategies?.length">
+          <h4>成交策略</h4>
+          <ul class="q-sg-list q-sg-strategies">
+            <li *ngFor="let strategy of quotationData?.salesGuidance?.closingStrategies">{{ strategy }}</li>
+          </ul>
+        </div>
+        <div class="q-sg-subsection" *ngIf="quotationData?.salesGuidance?.followUpActions?.length">
+          <h4>跟进行动计划</h4>
+          <ul class="q-sg-list q-sg-followup">
+            <li *ngFor="let action of quotationData?.salesGuidance?.followUpActions">{{ action }}</li>
+          </ul>
+        </div>
+      </section>
+
+      <!-- Footer -->
+      <section class="q-footer-section">
+        <div class="q-footer-company" *ngIf="quotationData?.companyInfo">
+          <strong>{{ quotationData?.companyInfo?.name }}</strong>
+          <span>{{ quotationData?.companyInfo?.address }}</span>
+          <span>{{ quotationData?.companyInfo?.website }} | {{ quotationData?.companyInfo?.phone }}</span>
+        </div>
+        <div class="q-footer-info">
+          <span><strong>制作人:</strong> {{ quotationData?.preparedBy }}</span>
+          <span><strong>制作日期:</strong> {{ quotationData?.preparedDate }}</span>
+          <span><strong>报价单号:</strong> {{ quotationData?.proposalNumber }}</span>
+        </div>
+      </section>
+    </div>
+
+    <!-- Action Footer -->
+    <div class="q-actions">
+      <button class="q-btn q-btn-warning" (click)="toggleSalesGuidance()">
+        <mat-icon>{{ showSalesGuidance ? 'visibility_off' : 'visibility' }}</mat-icon>
+        {{ showSalesGuidance ? '隐藏' : '显示' }}销售指导
+      </button>
+      <button class="q-btn q-btn-secondary" (click)="exportQuotationPDF()">
+        <mat-icon>picture_as_pdf</mat-icon> 导出 PDF
+      </button>
+      <button class="q-btn q-btn-primary" (click)="sendQuotation()">
+        <mat-icon>send</mat-icon> 发送给客户
+      </button>
+      <button class="q-btn q-btn-default" (click)="closeQuotation()">
+        关闭
+      </button>
+    </div>
+  </div>
+</div>
+

+ 898 - 0
lead-discovery/src/app/pages/lead-detail/lead-detail.component.scss

@@ -0,0 +1,898 @@
+.back-link {
+  display: inline-flex; align-items: center; gap: 4px;
+  color: #1976d2; text-decoration: none; font-size: 14px; margin-bottom: 16px; cursor: pointer;
+}
+.back-link:hover { text-decoration: underline; }
+
+.header-card { padding: 24px; margin-bottom: 20px; }
+.header-top { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; }
+.header-left { display: flex; gap: 16px; align-items: center; }
+.company-avatar {
+  width: 56px; height: 56px; border-radius: 14px;
+  display: flex; align-items: center; justify-content: center;
+}
+.company-avatar mat-icon { font-size: 28px; width: 28px; height: 28px; color: #fff; }
+.header-card h1 { margin: 0 0 8px; font-size: 22px; }
+.header-tags { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
+
+.persona-badge {
+  display: inline-flex; align-items: center; gap: 4px;
+  padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;
+}
+.persona-badge.lg { padding: 4px 14px; font-size: 13px; }
+.badge-sm-icon { font-size: 14px; width: 14px; height: 14px; }
+.badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; }
+.grade-s { background: #ffebee; color: #c62828; }
+.grade-a { background: #fff3e0; color: #e65100; }
+.grade-b { background: #fffde7; color: #f9a825; }
+.grade-c { background: #fafafa; color: #757575; }
+.stage-badge { }
+.stage-screened { background: #e3f2fd; color: #1565c0; }
+.stage-contacted { background: #e8f5e9; color: #2e7d32; }
+.stage-replied { background: #fff3e0; color: #e65100; }
+.stage-sample { background: #f3e5f5; color: #6a1b9a; }
+.stage-quote { background: #e1f5fe; color: #0277bd; }
+.stage-order { background: #e8f5e9; color: #1b5e20; }
+
+.verify-badge {
+  display: inline-flex; align-items: center; gap: 4px;
+  padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;
+  background: #fff3e0; color: #e65100;
+}
+.verify-badge mat-icon { font-size: 14px; width: 14px; height: 14px; }
+.verify-badge.verified { background: #e8f5e9; color: #2e7d32; }
+
+.header-right { text-align: right; }
+.value-display { margin-bottom: 12px; }
+.value-amount { font-size: 28px; font-weight: 700; color: #2e7d32; }
+.value-label { display: block; font-size: 12px; color: #888; }
+
+.content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; row-gap: 24px; }
+.left-col, .right-col { display: flex; flex-direction: column; gap: 24px; }
+.section-card { padding: 20px; margin-bottom: 0; }
+.section-header { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
+.section-header mat-icon { color: #1976d2; }
+.section-header h3 { margin: 0; font-size: 16px; flex: 1; }
+.time-badge { background: #e3f2fd; color: #1976d2; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; }
+.confidence-badge { background: #e8f5e9; color: #2e7d32; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; }
+
+/* Persona Insight Card */
+.persona-insight-card { border-left: 4px solid #ccc; }
+.persona-insight-body { display: flex; flex-direction: column; gap: 12px; }
+.insight-row { display: flex; align-items: flex-start; gap: 10px; }
+.insight-label { font-size: 12px; color: #888; min-width: 90px; flex-shrink: 0; padding-top: 4px; }
+.insight-value { font-size: 14px; color: #333; display: flex; align-items: center; gap: 4px; }
+.insight-value mat-icon { font-size: 16px; width: 16px; height: 16px; color: #999; }
+.insight-value.highlight { color: #1565c0; font-weight: 600; }
+.insight-keywords { display: flex; gap: 6px; flex-wrap: wrap; }
+.kw-chip { font-size: 11px; padding: 2px 8px; background: #e8eaf6; color: #5c6bc0; border-radius: 6px; font-family: monospace; }
+
+.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
+.info-label { display: block; font-size: 11px; color: #888; margin-bottom: 2px; }
+.info-value { font-size: 14px; color: #333; font-weight: 500; }
+.email-link { color: #1976d2; }
+.rel-tag { padding: 1px 6px; border-radius: 4px; font-size: 12px; }
+.rel-high { background: #e8f5e9; color: #2e7d32; }
+.rel-medium { background: #fff8e1; color: #f57f17; }
+.rel-low { background: #fafafa; color: #999; }
+
+.certs-row { margin-top: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
+.cert-chips { display: flex; gap: 6px; }
+.cert-chip { background: #e3f2fd; color: #1565c0; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
+
+/* Quick Screen Subsections */
+.quick-screen-subsection { margin-bottom: 20px; }
+.quick-screen-subsection:last-child { margin-bottom: 0; }
+.subsection-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #e0e0e0; }
+.subsection-icon { color: #666; font-size: 18px; width: 18px; height: 18px; }
+.subsection-header h4 { margin: 0; font-size: 14px; font-weight: 600; color: #333; }
+
+/* Email Card */
+.email-card { border-left: 4px solid #1976d2; }
+.email-subject-row {
+  display: flex; align-items: flex-start; gap: 8px; margin-bottom: 14px;
+  padding: 10px 14px; background: #f0f4ff; border-radius: 8px;
+}
+.email-meta-icon { font-size: 18px; width: 18px; height: 18px; color: #1976d2; flex-shrink: 0; margin-top: 2px; }
+.email-subject { font-size: 15px; font-weight: 600; color: #1a237e; line-height: 1.4; }
+
+.email-meta-grid { display: flex; flex-direction: column; gap: 8px; }
+.email-meta-item { display: flex; align-items: flex-start; gap: 8px; }
+.email-meta-label {
+  font-size: 12px; color: #888; min-width: 48px; flex-shrink: 0;
+  padding: 2px 8px; background: #f5f5f5; border-radius: 4px; text-align: center;
+}
+.email-meta-value { font-size: 13px; color: #444; word-break: break-all; line-height: 1.5; }
+.email-meta-value strong { color: #333; }
+
+.email-body-section { position: relative; }
+.email-body { overflow: hidden; transition: max-height 0.3s ease; }
+.email-body.collapsed { max-height: 120px; }
+.email-body-text {
+  font-size: 13px; color: #555; line-height: 1.7; white-space: pre-wrap;
+  word-wrap: break-word; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+  margin: 0; padding: 0;
+}
+.expand-btn { display: flex; align-items: center; gap: 4px; margin: 4px auto 0; font-size: 13px; color: #1976d2; }
+
+.attachments-header { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; margin-bottom: 10px; }
+.attachments-header mat-icon { font-size: 18px; width: 18px; height: 18px; color: #888; }
+.attachment-list { display: flex; flex-direction: column; gap: 6px; }
+.attachment-chip {
+  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
+  background: #f8f9fa; border-radius: 8px; border: 1px solid #eee; transition: background 0.15s;
+}
+.attachment-chip:hover { background: #e3f2fd; border-color: #bbdefb; }
+.att-icon { font-size: 20px; width: 20px; height: 20px; color: #e53935; flex-shrink: 0; }
+.att-name { font-size: 13px; color: #333; font-weight: 500; flex: 1; word-break: break-all; }
+.att-size { font-size: 11px; color: #999; flex-shrink: 0; }
+
+.no-email-card { border-left: 4px solid #e0e0e0; }
+.no-email-placeholder {
+  display: flex; align-items: center; gap: 10px; padding: 20px; color: #999; font-size: 14px;
+}
+.placeholder-sm { font-size: 32px; width: 32px; height: 32px; color: #ddd; }
+
+/* Verify Section */
+.verify-section { }
+.verify-status-badge { font-size: 12px; padding: 2px 10px; border-radius: 10px; background: #fff3e0; color: #e65100; }
+.verify-status-badge.verified { background: #e8f5e9; color: #2e7d32; }
+.verify-prompt { font-size: 14px; color: #555; margin: 0 0 12px; }
+.verify-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
+.verify-label { font-size: 13px; color: #888; min-width: 70px; }
+.verify-current { font-size: 14px; font-weight: 600; color: #333; }
+.verify-btn { font-size: 12px; }
+.verify-btn.correct mat-icon { font-size: 16px; width: 16px; height: 16px; color: #2e7d32; }
+.verify-select {
+  max-width: 200px;
+  --mat-form-field-container-height: 36px;
+  --mat-form-field-container-vertical-padding: 4px;
+  font-size: 12px;
+}
+.verify-notes-field { width: 100%; margin-top: 8px; }
+.verify-done { text-align: center; padding: 20px; }
+.done-icon { font-size: 48px; width: 48px; height: 48px; color: #2e7d32; }
+.verify-done p { font-size: 14px; color: #555; margin: 8px 0 0; }
+.done-notes { font-size: 13px; color: #888; font-style: italic; }
+.verify-actions {
+  display: flex;
+  gap: 8px;
+  margin-top: 12px;
+  flex-wrap: wrap;
+}
+.verify-actions .action-btn {
+  font-size: 12px;
+}
+.verify-actions .action-btn mat-icon {
+  font-size: 16px;
+  width: 16px;
+  height: 16px;
+  margin-right: 2px;
+}
+
+/* Products */
+.product-list { display: flex; flex-direction: column; gap: 10px; }
+.product-card { display: flex; gap: 12px; align-items: center; padding: 12px; background: #fafafa; border-radius: 10px; transition: background 0.15s; }
+.product-card:hover { background: #f0f4ff; }
+.product-rank { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex-shrink: 0; }
+.rank-1 { background: #fff3e0; color: #e65100; }
+.rank-2 { background: #e3f2fd; color: #1565c0; }
+.rank-3 { background: #e8f5e9; color: #2e7d32; }
+.rank-other { background: #f5f5f5; color: #999; }
+.product-info { flex: 1; }
+.product-name { font-size: 14px; font-weight: 600; color: #333; }
+.product-en { font-size: 11px; color: #999; }
+.product-sku-row { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
+.sku-tag { font-size: 10px; padding: 1px 6px; background: #e8eaf6; color: #5c6bc0; border-radius: 4px; font-family: monospace; }
+.product-reason { font-size: 11px; color: #888; }
+.product-pricing { text-align: right; }
+.wholesale-price { font-size: 18px; font-weight: 700; color: #2e7d32; display: block; }
+.price-label { font-size: 10px; color: #999; }
+
+/* Report */
+.deep-report-preview { display: flex; flex-direction: column; gap: 14px; }
+.report-item { display: flex; gap: 12px; padding: 12px; background: #f8f9fa; border-radius: 8px; }
+.report-item mat-icon { color: #1976d2; flex-shrink: 0; margin-top: 2px; }
+.report-item strong { font-size: 14px; }
+.report-item p { font-size: 13px; color: #555; margin: 4px 0 0; }
+.match-list { margin-top: 6px; }
+.match-item { font-size: 13px; color: #555; padding: 4px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }
+.match-margin { font-size: 12px; color: #2e7d32; font-weight: 500; }
+
+.deep-analysis-placeholder { border: 2px dashed #e0e0e0; background: #fafafa; }
+.placeholder-content { text-align: center; padding: 20px; }
+.placeholder-icon { font-size: 48px; width: 48px; height: 48px; color: #ccc; }
+.placeholder-content h3 { margin: 8px 0; }
+.placeholder-content p { font-size: 14px; color: #666; }
+.placeholder-content ul { text-align: left; max-width: 300px; margin: 12px auto; font-size: 13px; color: #555; line-height: 1.8; }
+.estimate { font-size: 12px; color: #999; margin-bottom: 16px !important; }
+
+/* Background Check */
+.bgcheck-summary {
+  display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px;
+  padding: 16px; background: #f8f9fa; border-radius: 8px;
+}
+.summary-item {
+  display: flex; justify-content: space-between; align-items: center;
+  font-size: 14px;
+}
+.summary-label { color: #666; }
+.summary-value { color: #333; font-weight: 500; }
+
+.empty-state { display: flex; flex-direction: column; align-items: center; padding: 60px 20px; color: #999; }
+.empty-state mat-icon { font-size: 64px; width: 64px; height: 64px; color: #ddd; }
+
+/* Demand Summary */
+.demand-section { }
+.demand-content { }
+.demand-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
+.demand-item { font-size: 14px; color: #333; }
+.demand-product { }
+.estimated-value {
+  display: flex; align-items: center; gap: 6px;
+  font-size: 15px; font-weight: 600; color: #2e7d32;
+  margin-top: 8px; padding-top: 12px; border-top: 1px solid #e0e0e0;
+}
+.money-icon { font-size: 18px; width: 18px; height: 18px; color: #2e7d32; }
+
+/* Credibility + Scores + AI Next Steps combined card */
+.credibility-score-ai-card {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* Credibility */
+.credibility-section { }
+.credibility-content { }
+.credibility-signals { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: #555; }
+.signal-item { display: block; }
+
+/* Score Details */
+.scores-section { border-top: 1px solid #f0f0f0; padding-top: 8px; }
+.scores-panel { box-shadow: none; border: 1px solid #e0e0e0; }
+.scores-grid { display: flex; flex-direction: column; gap: 12px; padding: 12px 0; }
+.score-item { display: flex; align-items: center; gap: 12px; }
+.score-label { font-size: 13px; color: #666; min-width: 90px; }
+.score-bar-wrap { flex: 1; height: 8px; background: #f0f0f0; border-radius: 4px; overflow: hidden; }
+.score-bar { height: 100%; background: linear-gradient(90deg, #1976d2, #42a5f5); border-radius: 4px; transition: width 0.3s; }
+.score-value { font-size: 13px; font-weight: 600; color: #1976d2; min-width: 50px; text-align: right; }
+
+/* AI Recommended Next Steps */
+.ai-next-steps-section { border-top: 1px solid #f0f0f0; padding-top: 8px; }
+.next-steps-content {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+.next-step-main {
+  background: linear-gradient(135deg, #f0f7ff 0%, #e6f2ff 100%);
+  border-left: 4px solid #1890ff;
+  border-radius: 8px;
+  padding: 16px;
+}
+.step-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+.step-icon {
+  color: #1890ff;
+  font-size: 20px;
+  width: 20px;
+  height: 20px;
+}
+.step-title {
+  font-weight: 600;
+  color: #1890ff;
+  font-size: 15px;
+}
+.step-description {
+  margin: 0;
+  color: #333;
+  font-size: 14px;
+  line-height: 1.6;
+  padding-left: 28px;
+}
+.next-steps-details {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+.detail-item {
+  display: flex;
+  gap: 12px;
+  padding: 12px;
+  background: #fafafa;
+  border-radius: 6px;
+}
+.detail-icon {
+  color: #666;
+  font-size: 20px;
+  width: 20px;
+  height: 20px;
+  margin-top: 2px;
+}
+.detail-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+.detail-label {
+  font-size: 12px;
+  color: #666;
+  font-weight: 500;
+}
+.detail-value {
+  font-size: 14px;
+  color: #333;
+  font-weight: 500;
+}
+.files-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+}
+.file-tag {
+  background: #e6f2ff;
+  color: #1890ff;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 500;
+}
+.key-points-list {
+  margin: 0;
+  padding-left: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.key-points-list li {
+  font-size: 13px;
+  color: #333;
+  line-height: 1.5;
+}
+.no-recommendation {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 20px;
+  color: #999;
+  font-size: 14px;
+  justify-content: center;
+}
+.no-recommendation mat-icon {
+  color: #ccc;
+}
+
+/* Invalid Lead Warning - AI Next Steps */
+.next-step-invalid-warning {
+  background: linear-gradient(135deg, #fff8e1 0%, #fff3e0 100%);
+  border-left: 4px solid #ff9800;
+  border-radius: 8px;
+  padding: 16px;
+}
+.step-icon-warn {
+  color: #e65100;
+  font-size: 20px;
+  width: 20px;
+  height: 20px;
+}
+.step-title-warn {
+  font-weight: 600;
+  color: #e65100;
+  font-size: 15px;
+}
+.step-warn-desc {
+  margin: 8px 0 0;
+  color: #5d4037;
+  font-size: 13px;
+  line-height: 1.6;
+  padding-left: 28px;
+}
+
+/* Invalid Lead - Deep Analysis Placeholder */
+.invalid-lead-placeholder {
+  border: 2px dashed #ffb74d !important;
+  background: #fffde7 !important;
+}
+.invalid-lead-placeholder .placeholder-content h3 {
+  color: #e65100;
+}
+.warn-icon {
+  color: #ff9800 !important;
+}
+.invalid-lead-notice {
+  max-width: 420px;
+  margin: 12px auto 16px;
+  text-align: left;
+}
+.notice-box {
+  display: flex;
+  gap: 12px;
+  padding: 14px 16px;
+  background: #fff3e0;
+  border: 1px solid #ffcc80;
+  border-radius: 10px;
+}
+.notice-box mat-icon {
+  color: #e65100;
+  font-size: 24px;
+  width: 24px;
+  height: 24px;
+  flex-shrink: 0;
+  margin-top: 2px;
+}
+.notice-box strong {
+  font-size: 14px;
+  color: #bf360c;
+  display: block;
+  margin-bottom: 6px;
+}
+.notice-box p {
+  font-size: 13px;
+  color: #6d4c41;
+  line-height: 1.6;
+  margin: 0;
+}
+.invalid-hint {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  margin-top: 12px;
+  font-size: 13px;
+  color: #8d6e63;
+}
+.invalid-hint mat-icon {
+  font-size: 16px;
+  width: 16px;
+  height: 16px;
+  color: #ff9800;
+}
+
+/* ===== Generate Quote Button ===== */
+.generate-quote-btn {
+  margin-top: 8px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+  color: #fff !important;
+}
+
+/* ===== Quotation Modal ===== */
+.quotation-overlay {
+  position: fixed;
+  top: 0; left: 0; right: 0; bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  animation: fadeIn 0.2s ease;
+}
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+.quotation-modal {
+  background: #fff;
+  border-radius: 16px;
+  width: 90vw;
+  max-width: 960px;
+  max-height: 90vh;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+  animation: slideUp 0.3s ease;
+}
+@keyframes slideUp {
+  from { transform: translateY(30px); opacity: 0; }
+  to { transform: translateY(0); opacity: 1; }
+}
+
+.q-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  padding: 24px 32px;
+  border-radius: 16px 16px 0 0;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+}
+.q-header h2 { margin: 0 0 8px; font-size: 22px; font-weight: 700; }
+.q-meta { display: flex; gap: 12px; font-size: 13px; opacity: 0.9; flex-wrap: wrap; }
+.q-number { background: rgba(255,255,255,0.2); padding: 2px 10px; border-radius: 4px; font-weight: 600; }
+.q-close {
+  background: rgba(255,255,255,0.2); border: none; color: #fff;
+  font-size: 28px; width: 40px; height: 40px; border-radius: 50%;
+  cursor: pointer; display: flex; align-items: center; justify-content: center;
+  transition: all 0.2s;
+}
+.q-close:hover { background: rgba(255,255,255,0.35); }
+
+.q-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 28px 32px;
+  background: #f9fafb;
+}
+
+.q-section {
+  background: #fff;
+  border-radius: 12px;
+  padding: 24px;
+  margin-bottom: 20px;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
+}
+.q-section h3 {
+  font-size: 18px; font-weight: 700; margin: 0 0 16px;
+  padding-bottom: 10px; border-bottom: 2px solid #e5e7eb; color: #1f2937;
+}
+.q-section h4 { font-size: 14px; font-weight: 600; margin: 0 0 6px; color: #374151; }
+.q-section p { line-height: 1.7; color: #374151; margin: 0; }
+.q-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
+.q-section-header h3 { margin: 0; border: none; padding: 0; }
+.q-badge { background: #e0e7ff; color: #3730a3; padding: 4px 12px; border-radius: 10px; font-size: 12px; font-weight: 600; }
+
+.q-info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 14px;
+}
+.q-info-item { display: flex; flex-direction: column; gap: 2px; }
+.q-label { font-size: 12px; color: #6b7280; font-weight: 500; }
+.q-value { font-size: 14px; color: #111827; font-weight: 600; }
+.q-summary { line-height: 1.8; color: #374151; }
+
+.q-product-table {
+  border: 1px solid #e5e7eb;
+  border-radius: 12px;
+  overflow: hidden;
+}
+.q-table-header {
+  display: grid;
+  grid-template-columns: 36px 90px 1.8fr 1.5fr 60px 90px 100px;
+  gap: 12px;
+  padding: 12px 16px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  font-weight: 600;
+  font-size: 13px;
+}
+.q-table-row {
+  display: grid;
+  grid-template-columns: 36px 90px 1.8fr 1.5fr 60px 90px 100px;
+  gap: 12px;
+  padding: 14px 16px;
+  border-bottom: 1px solid #f0f0f0;
+  align-items: center;
+  font-size: 13px;
+}
+.q-table-row.alt-row { background: #f9fafb; }
+.q-col-idx { text-align: center; font-weight: 600; color: #6b7280; }
+.q-code-badge {
+  display: inline-block; font-family: 'Courier New', monospace;
+  font-weight: 600; color: #fff; background: #3b82f6;
+  padding: 3px 8px; border-radius: 4px; font-size: 11px;
+}
+.q-product-name { font-weight: 600; color: #1f2937; }
+.q-match-reason {
+  font-size: 11px; color: #065f46; background: #d1fae5;
+  padding: 2px 8px; border-radius: 4px; margin-top: 4px; display: inline-block;
+}
+.q-table-footer {
+  display: flex; justify-content: space-between; align-items: center;
+  padding: 16px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: #fff;
+}
+.q-total-label { font-size: 16px; font-weight: 600; }
+.q-total-amount { font-size: 24px; font-weight: 700; }
+
+.q-col-spec { font-size: 12px; }
+.q-spec-text { color: #4b5563; line-height: 1.4; }
+.q-key-features { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
+.q-feature-tag {
+  display: inline-block; font-size: 10px; padding: 1px 6px;
+  background: #dbeafe; color: #1e40af; border-radius: 4px; font-weight: 500;
+}
+.q-cert-badges { display: flex; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
+.q-cert-badge {
+  display: inline-block; font-size: 9px; padding: 1px 6px;
+  background: #fef3c7; color: #92400e; border-radius: 4px; font-weight: 600;
+  border: 1px solid #fbbf24;
+}
+
+.q-pricing-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
+.q-pricing-table th {
+  background: #f3f4f6; padding: 10px 12px; text-align: left;
+  font-weight: 600; color: #374151; border-bottom: 2px solid #d1d5db; font-size: 13px;
+}
+.q-pricing-table td { padding: 10px 12px; border-bottom: 1px solid #e5e7eb; font-size: 13px; }
+.q-pricing-table .text-right { text-align: right; }
+.q-pricing-table .text-danger { color: #dc2626; }
+.q-pricing-table .subtotal-row { font-weight: 600; }
+.q-pricing-table .total-row { background: #f9fafb; border-top: 2px solid #d1d5db; font-weight: 600; }
+.q-grand-total { color: #7c3aed; font-size: 18px; }
+.q-validity-note {
+  margin-top: 12px; padding: 10px 14px; background: #fef3c7;
+  border-radius: 6px; color: #92400e; font-size: 13px;
+}
+
+.q-terms-grid {
+  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;
+}
+.q-term-item {
+  padding: 14px; background: #f9fafb; border-radius: 8px; border-left: 3px solid #667eea;
+}
+.q-term-label { font-size: 11px; color: #6b7280; margin-bottom: 4px; }
+.q-term-value { font-size: 13px; font-weight: 600; color: #111827; }
+
+.q-email { color: #3b82f6; font-size: 13px; }
+.q-highlight-value { color: #059669; font-size: 16px; }
+.q-project-bg {
+  margin-top: 14px; padding: 12px 16px; background: #f0f9ff;
+  border-left: 3px solid #3b82f6; border-radius: 4px;
+  font-size: 13px; line-height: 1.7; color: #1e3a5f;
+}
+
+.q-insight-grid {
+  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px;
+}
+.q-insight-card {
+  padding: 16px; background: #fafbfc; border-radius: 10px;
+  border: 1px solid #e5e7eb; transition: box-shadow 0.2s;
+}
+.q-insight-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
+.q-insight-icon { font-size: 24px; margin-bottom: 8px; }
+.q-insight-card h4 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1f2937; }
+.q-insight-card p { margin: 0; font-size: 13px; color: #4b5563; line-height: 1.6; }
+.q-risk-level {
+  display: flex; align-items: center; gap: 6px;
+  font-weight: 700; font-size: 14px; margin-bottom: 8px;
+}
+.q-risk-dot { width: 10px; height: 10px; border-radius: 50%; }
+.q-risk-factors { margin: 0; padding-left: 20px; font-size: 12px; color: #6b7280; }
+.q-risk-factors li { margin: 3px 0; }
+.q-competitor-section { margin-top: 16px; padding: 14px 16px; background: #fef3c7; border-radius: 8px; }
+.q-competitor-section h4 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #92400e; }
+.q-competitor-list { margin: 0; padding-left: 20px; font-size: 13px; color: #78350f; }
+.q-competitor-list li { margin: 4px 0; line-height: 1.5; }
+.q-competitor-list li:last-child { color: #059669; font-weight: 600; }
+
+.q-highlights-section { background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%) !important; }
+.q-highlights-grid {
+  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px;
+}
+.q-highlight-card {
+  display: flex; gap: 12px; align-items: flex-start;
+  padding: 14px; background: #fff; border-radius: 10px;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.06); transition: transform 0.2s;
+}
+.q-highlight-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
+.q-highlight-icon { font-size: 28px; flex-shrink: 0; }
+.q-highlight-body { display: flex; flex-direction: column; gap: 2px; }
+.q-highlight-body strong { font-size: 14px; color: #1f2937; }
+.q-highlight-body span { font-size: 12px; color: #6b7280; line-height: 1.5; }
+
+.q-completeness-badge {
+  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  color: #fff; padding: 4px 14px; border-radius: 12px;
+  font-size: 12px; font-weight: 700;
+}
+.q-req-categories {
+  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px;
+}
+.q-req-category {
+  padding: 16px; border-radius: 10px; border: 1px solid #e5e7eb; background: #fafbfc;
+}
+.q-req-header {
+  display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
+  padding-bottom: 10px; border-bottom: 1px solid #e5e7eb;
+}
+.q-req-icon { font-size: 20px; }
+.q-req-header h4 { margin: 0; font-size: 15px; font-weight: 600; color: #1f2937; flex: 1; }
+.q-req-count {
+  background: #e0e7ff; color: #3730a3; padding: 2px 10px;
+  border-radius: 10px; font-size: 11px; font-weight: 600;
+}
+.q-req-list {
+  margin: 0; padding-left: 18px; font-size: 13px; color: #374151;
+}
+.q-req-list li { margin: 6px 0; line-height: 1.6; }
+.q-req-list li::marker { color: #667eea; }
+
+.q-alternatives {
+  margin-top: 14px; padding: 14px 16px; background: #f0f9ff;
+  border-radius: 8px; border: 1px dashed #93c5fd;
+}
+.q-alternatives h4 { margin: 0 0 10px; font-size: 14px; font-weight: 600; color: #1e40af; }
+.q-alt-grid { display: flex; flex-direction: column; gap: 6px; }
+.q-alt-item { font-size: 12px; color: #4b5563; }
+.q-alt-source { font-weight: 600; color: #1f2937; margin-right: 6px; }
+
+.q-qa-content { display: flex; flex-direction: column; gap: 16px; }
+.q-qa-content h4 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1f2937; }
+.q-qa-content p { margin: 0; font-size: 13px; color: #4b5563; line-height: 1.6; }
+.q-cert-list { display: flex; flex-wrap: wrap; gap: 8px; }
+.q-cert-item {
+  display: inline-flex; align-items: center; gap: 4px;
+  padding: 6px 14px; background: #ecfdf5; border-radius: 8px;
+  font-size: 13px; font-weight: 600; color: #065f46; border: 1px solid #a7f3d0;
+}
+.q-cert-check { color: #10b981; font-weight: 700; }
+.q-testing-list { display: flex; flex-wrap: wrap; gap: 8px; }
+.q-testing-item {
+  padding: 4px 12px; background: #eff6ff; border-radius: 6px;
+  font-size: 12px; color: #1e40af; font-weight: 500; border: 1px solid #bfdbfe;
+}
+.q-process-flow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
+.q-process-step {
+  display: inline-flex; align-items: center; gap: 6px;
+  padding: 6px 12px; background: #f9fafb; border-radius: 8px;
+  font-size: 12px; color: #374151; border: 1px solid #e5e7eb;
+}
+.q-process-dot {
+  width: 22px; height: 22px; background: #667eea; color: #fff;
+  border-radius: 50%; display: flex; align-items: center; justify-content: center;
+  font-size: 11px; font-weight: 700; flex-shrink: 0;
+}
+.q-process-arrow { color: #9ca3af; font-weight: 700; }
+
+.q-service-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
+.q-service-item {
+  padding: 14px; background: #f0fdf4; border-radius: 8px; border-left: 3px solid #10b981;
+}
+.q-service-item h4 { margin: 0 0 6px; font-size: 13px; font-weight: 600; color: #065f46; }
+.q-service-item p { margin: 0; font-size: 13px; color: #374151; line-height: 1.6; }
+
+.q-steps { display: flex; flex-direction: column; gap: 10px; }
+.q-step {
+  display: flex; gap: 14px; padding: 14px; background: #f9fafb;
+  border-radius: 8px; border-left: 4px solid #667eea; align-items: flex-start;
+}
+.q-step-priority-high { border-left-color: #ef4444; background: #fef2f2; }
+.q-step-priority-medium { border-left-color: #f59e0b; }
+.q-step-priority-low { border-left-color: #10b981; }
+.q-step-num {
+  width: 32px; height: 32px; background: #667eea; color: #fff;
+  border-radius: 50%; display: flex; align-items: center; justify-content: center;
+  font-weight: 700; flex-shrink: 0; font-size: 14px;
+}
+.q-step-priority-high .q-step-num { background: #ef4444; }
+.q-step-priority-medium .q-step-num { background: #f59e0b; }
+.q-step-priority-low .q-step-num { background: #10b981; }
+.q-step-action { font-weight: 600; color: #111827; margin-bottom: 4px; font-size: 14px; }
+.q-step-meta { display: flex; gap: 12px; font-size: 12px; color: #6b7280; align-items: center; flex-wrap: wrap; }
+.q-priority-tag {
+  display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 10px; font-weight: 700;
+}
+.q-priority-high { background: #fef2f2; color: #dc2626; border: 1px solid #fecaca; }
+.q-priority-medium { background: #fffbeb; color: #d97706; border: 1px solid #fde68a; }
+.q-priority-low { background: #f0fdf4; color: #16a34a; border: 1px solid #bbf7d0; }
+
+.q-footer-section {
+  background: #f3f4f6; padding: 20px 24px; border-radius: 8px;
+}
+.q-footer-company {
+  display: flex; flex-direction: column; gap: 2px;
+  margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #d1d5db;
+  font-size: 13px; color: #4b5563;
+}
+.q-footer-company strong { font-size: 15px; color: #1f2937; }
+.q-footer-info { display: flex; justify-content: space-between; font-size: 13px; color: #6b7280; flex-wrap: wrap; gap: 12px; }
+
+.q-actions {
+  display: flex; gap: 10px; padding: 16px 32px;
+  border-top: 1px solid #e5e7eb; background: #fff;
+  justify-content: flex-end; border-radius: 0 0 16px 16px;
+}
+.q-btn {
+  display: inline-flex; align-items: center; gap: 6px;
+  padding: 10px 20px; border-radius: 8px; border: none;
+  font-weight: 600; cursor: pointer; transition: all 0.2s; font-size: 13px;
+}
+.q-btn mat-icon { font-size: 18px; width: 18px; height: 18px; }
+.q-btn-primary { background: #667eea; color: #fff; }
+.q-btn-primary:hover { background: #5568d3; }
+.q-btn-secondary { background: #e5e7eb; color: #374151; }
+.q-btn-secondary:hover { background: #d1d5db; }
+.q-btn-default { background: #fff; color: #374151; border: 1px solid #d1d5db; }
+.q-btn-default:hover { background: #f9fafb; }
+.q-btn-warning { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
+.q-btn-warning:hover { background: #fde68a; }
+
+/* Internal Sales Guidance */
+.q-internal-only {
+  background: #fef2f2 !important;
+  border: 2px solid #fca5a5 !important;
+  border-radius: 12px;
+}
+.q-internal-banner {
+  background: #dc2626;
+  color: #fff;
+  padding: 10px 18px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  text-align: center;
+  font-size: 15px;
+  letter-spacing: 1px;
+}
+.q-sg-subsection {
+  margin-bottom: 20px;
+  h4 {
+    font-size: 15px;
+    font-weight: 700;
+    color: #1f2937;
+    margin: 0 0 10px 0;
+    padding-bottom: 6px;
+    border-bottom: 1px dashed #fca5a5;
+  }
+}
+.q-sg-list {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+  li {
+    position: relative;
+    padding: 6px 0 6px 18px;
+    font-size: 13.5px;
+    color: #374151;
+    line-height: 1.6;
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 13px;
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background: #ef4444;
+    }
+  }
+}
+.q-sg-strategies li::before { background: #f59e0b; }
+.q-sg-followup li {
+  padding-left: 28px;
+  counter-increment: followup;
+  &::before {
+    content: counter(followup);
+    width: 18px;
+    height: 18px;
+    border-radius: 50%;
+    background: #667eea;
+    color: #fff;
+    font-size: 11px;
+    font-weight: 700;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    top: 8px;
+  }
+}
+.q-sg-followup { counter-reset: followup; }
+
+.q-objection-card {
+  padding: 14px 16px;
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 12px;
+  border-left: 4px solid #ef4444;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
+}
+.q-objection-question {
+  margin-bottom: 8px;
+  font-size: 14px;
+  color: #991b1b;
+  line-height: 1.6;
+  strong { color: #dc2626; margin-right: 4px; }
+}
+.q-objection-response {
+  font-size: 13.5px;
+  color: #059669;
+  line-height: 1.6;
+  strong { color: #047857; margin-right: 4px; }
+}
+
+@media (max-width: 900px) { .content-grid { grid-template-columns: 1fr; } }
+

+ 589 - 564
lead-discovery/src/app/pages/lead-detail/lead-detail.component.ts

@@ -14,11 +14,15 @@ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
 import { MatFormFieldModule } from '@angular/material/form-field';
 import { MatFormFieldModule } from '@angular/material/form-field';
 import { MatInputModule } from '@angular/material/input';
 import { MatInputModule } from '@angular/material/input';
 import { MatSelectModule } from '@angular/material/select';
 import { MatSelectModule } from '@angular/material/select';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatTooltipModule } from '@angular/material/tooltip';
 import { MockDataService } from '../../services/mock-data.service';
 import { MockDataService } from '../../services/mock-data.service';
 import {
 import {
   Lead, Email, CATEGORY_ICONS, GRADE_LABELS, Product,
   Lead, Email, CATEGORY_ICONS, GRADE_LABELS, Product,
-  CustomerPersona, PERSONA_DEFINITIONS, PERSONA_LIST
+  CustomerPersona, PERSONA_DEFINITIONS, PERSONA_LIST, ValueGrade
 } from '../../models/lead.model';
 } from '../../models/lead.model';
+import { DeepResearchPackageComponent, DeepResearchResult } from '../../components/deep-research-package/deep-research-package.component';
+import { BgCheckProgressComponent, BgCheckStage } from '../../components/bgcheck-progress/bgcheck-progress.component';
 
 
 @Component({
 @Component({
     selector: 'app-lead-detail',
     selector: 'app-lead-detail',
@@ -26,573 +30,27 @@ import {
         CommonModule, FormsModule,
         CommonModule, FormsModule,
         MatCardModule, MatIconModule, MatButtonModule, MatChipsModule,
         MatCardModule, MatIconModule, MatButtonModule, MatChipsModule,
         MatDividerModule, MatTabsModule, MatProgressBarModule, MatSnackBarModule,
         MatDividerModule, MatTabsModule, MatProgressBarModule, MatSnackBarModule,
-        MatFormFieldModule, MatInputModule, MatSelectModule,
+        MatFormFieldModule, MatInputModule, MatSelectModule, MatExpansionModule,
+        MatTooltipModule,
+        DeepResearchPackageComponent,
+        BgCheckProgressComponent,
     ],
     ],
-    template: `
-    <div class="page-container" *ngIf="lead">
-      <!-- Back -->
-      <a class="back-link" (click)="goBack()">
-        <mat-icon>arrow_back</mat-icon> 返回上一页
-      </a>
-
-      <!-- Header Card -->
-      <div class="header-card card">
-        <div class="header-top">
-          <div class="header-left">
-            <div class="company-avatar" [style.background]="getPersonaColor(lead.persona)">
-              <mat-icon>{{ getPersonaIcon(lead.persona) }}</mat-icon>
-            </div>
-            <div>
-              <h1>{{ lead.companyName }}</h1>
-              <div class="header-tags">
-                <span class="persona-badge" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
-                  <mat-icon class="badge-sm-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
-                  {{ lead.persona }}-{{ lead.personaLabel }}
-                </span>
-                <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">
-                  {{ lead.valueGrade }}级
-                </span>
-                <span class="badge stage-badge" [class]="'stage-' + lead.followUpStage">
-                  {{ getStageLabel(lead.followUpStage) }}
-                </span>
-                <span class="verify-badge" [class.verified]="lead.personaVerified">
-                  <mat-icon>{{ lead.personaVerified ? 'verified' : 'pending' }}</mat-icon>
-                  {{ lead.personaVerified ? '已验证' : '待验证' }}
-                </span>
-              </div>
-            </div>
-          </div>
-          <div class="header-right">
-            <div class="value-display">
-            </div>
-            <button mat-raised-button color="primary" (click)="startDeepAnalysis()">
-              <mat-icon>search</mat-icon> 启动深度分析
-            </button>
-          </div>
-        </div>
-      </div>
-
-      <!-- Content Grid -->
-      <div class="content-grid">
-        <!-- Left Column -->
-        <div class="left-col">
-          <!-- Persona Card (NEW) -->
-          <div class="section-card card persona-insight-card"
-               [style.border-left-color]="getPersonaColor(lead.persona)">
-            <div class="section-header">
-              <mat-icon [style.color]="getPersonaColor(lead.persona)">psychology</mat-icon>
-              <h3>客户画像分析</h3>
-              <span class="confidence-badge">置信度 {{ lead.personaConfidence }}%</span>
-            </div>
-
-            <div class="persona-insight-body">
-              <div class="insight-row">
-                <span class="insight-label">画像类型</span>
-                <span class="persona-badge lg" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
-                  <mat-icon class="badge-sm-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
-                  {{ lead.persona }}-{{ lead.personaLabel }}
-                </span>
-              </div>
-
-              <div class="insight-row">
-                <span class="insight-label">AI 判断依据</span>
-                <div class="insight-keywords">
-                  <span class="kw-chip" *ngFor="let kw of lead.quickScreenResult.matchedKeywords">{{ kw }}</span>
-                </div>
-              </div>
-
-              <div class="insight-row">
-                <span class="insight-label">信息获取策略</span>
-                <span class="insight-value">
-                  <mat-icon>{{ getPersonaStrategyIcon(lead.persona) }}</mat-icon>
-                  {{ getPersonaStrategyLabel(lead.persona) }}
-                </span>
-              </div>
-
-              <div class="insight-row">
-                <span class="insight-label">推荐首要动作</span>
-                <span class="insight-value highlight">{{ getPersonaFirstAction(lead.persona) }}</span>
-              </div>
-
-              <div class="insight-row">
-                <span class="insight-label">销售策略</span>
-                <span class="insight-value">{{ getPersonaSalesStrategy(lead.persona) }}</span>
-              </div>
-            </div>
-          </div>
-
-          <!-- Quick Screen Result -->
-          <div class="section-card card">
-            <div class="section-header">
-              <mat-icon>bolt</mat-icon>
-              <h3>快速筛选结果</h3>
-              <span class="time-badge">{{ lead.quickScreenResult.processingTime / 1000 | number:'1.1-1' }}s</span>
-            </div>
-            <div class="info-grid">
-              <div class="info-item">
-                <span class="info-label">域名</span>
-                <span class="info-value">{{ lead.quickScreenResult.domain || '-' }}</span>
-              </div>
-              <div class="info-item">
-                <span class="info-label">国家</span>
-                <span class="info-value">{{ getFlag(lead.country) }} {{ lead.country }}</span>
-              </div>
-              <div class="info-item">
-                <span class="info-label">主营业务</span>
-                <span class="info-value">{{ lead.quickScreenResult.mainBusiness }}</span>
-              </div>
-              <div class="info-item">
-                <span class="info-label">预估规模</span>
-                <span class="info-value">{{ lead.quickScreenResult.estimatedScale }}</span>
-              </div>
-              <div class="info-item">
-                <span class="info-label">产品相关性</span>
-                <span class="info-value rel-tag" [class]="'rel-' + lead.quickScreenResult.productRelevance">
-                  {{ lead.quickScreenResult.productRelevance === 'high' ? '高' : lead.quickScreenResult.productRelevance === 'medium' ? '中' : '低' }}
-                </span>
-              </div>
-              <div class="info-item">
-                <span class="info-label">置信度</span>
-                <span class="info-value">{{ lead.quickScreenResult.confidence }}%</span>
-              </div>
-            </div>
-            <div class="certs-row" *ngIf="lead.quickScreenResult.certRequirements.length">
-              <span class="info-label">需要认证</span>
-              <div class="cert-chips">
-                <span class="cert-chip" *ngFor="let c of lead.quickScreenResult.certRequirements">{{ c }}</span>
-              </div>
-            </div>
-          </div>
-
-          <!-- Contact Info -->
-          <div class="section-card card">
-            <div class="section-header">
-              <mat-icon>person</mat-icon>
-              <h3>联系人信息</h3>
-            </div>
-            <div class="info-grid">
-              <div class="info-item"><span class="info-label">姓名</span><span class="info-value">{{ lead.contactName || '-' }}</span></div>
-              <div class="info-item"><span class="info-label">邮箱</span><span class="info-value email-link">{{ lead.contactEmail }}</span></div>
-              <div class="info-item"><span class="info-label">来源</span><span class="info-value">{{ lead.sourceLabel }}</span></div>
-              <div class="info-item"><span class="info-label">创建时间</span><span class="info-value">{{ lead.createdAt | date:'yyyy-MM-dd HH:mm' }}</span></div>
-            </div>
-          </div>
-
-          <!-- Original Email -->
-          <div class="section-card card email-card" *ngIf="getEmailForLead(lead) as email">
-            <div class="section-header">
-              <mat-icon>email</mat-icon>
-              <h3>原始邮件信息</h3>
-              <span class="time-badge">{{ email.receivedAt | date:'yyyy-MM-dd HH:mm' }}</span>
-            </div>
-
-            <div class="email-subject-row">
-              <mat-icon class="email-meta-icon">subject</mat-icon>
-              <span class="email-subject">{{ email.subject }}</span>
-            </div>
-
-            <div class="email-meta-grid">
-              <div class="email-meta-item">
-                <span class="email-meta-label">发件人</span>
-                <span class="email-meta-value">
-                  <strong>{{ email.fromName }}</strong>
-                  &lt;{{ email.from }}&gt;
-                </span>
-              </div>
-              <div class="email-meta-item">
-                <span class="email-meta-label">收件人</span>
-                <span class="email-meta-value">{{ email.to }}</span>
-              </div>
-              <div class="email-meta-item" *ngIf="email.cc">
-                <span class="email-meta-label">抄送</span>
-                <span class="email-meta-value">{{ email.cc }}</span>
-              </div>
-            </div>
-
-            <mat-divider style="margin: 12px 0;"></mat-divider>
-
-            <div class="email-body-section">
-              <div class="email-body" [class.collapsed]="!emailBodyExpanded">
-                <pre class="email-body-text">{{ email.body }}</pre>
-              </div>
-              <button mat-button class="expand-btn" (click)="emailBodyExpanded = !emailBodyExpanded">
-                <mat-icon>{{ emailBodyExpanded ? 'expand_less' : 'expand_more' }}</mat-icon>
-                {{ emailBodyExpanded ? '收起' : '展开全文' }}
-              </button>
-            </div>
-
-            <div class="email-attachments" *ngIf="email.attachments.length">
-              <mat-divider style="margin: 8px 0 12px;"></mat-divider>
-              <div class="attachments-header">
-                <mat-icon>attach_file</mat-icon>
-                <span>附件 ({{ email.attachments.length }})</span>
-              </div>
-              <div class="attachment-list">
-                <div class="attachment-chip" *ngFor="let att of email.attachments">
-                  <mat-icon class="att-icon">{{ att.icon }}</mat-icon>
-                  <span class="att-name">{{ att.fileName }}</span>
-                  <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- No Email Found -->
-          <div class="section-card card no-email-card" *ngIf="!getEmailForLead(lead)">
-            <div class="section-header">
-              <mat-icon>email</mat-icon>
-              <h3>原始邮件信息</h3>
-            </div>
-            <div class="no-email-placeholder">
-              <mat-icon class="placeholder-sm">mail_outline</mat-icon>
-              <span>该线索无关联邮件(来源:{{ lead.sourceLabel }})</span>
-            </div>
-          </div>
-        </div>
-
-        <!-- Right Column -->
-        <div class="right-col">
-          <!-- Sales Verification (NEW) -->
-          <div class="section-card card verify-section">
-            <div class="section-header">
-              <mat-icon>fact_check</mat-icon>
-              <h3>销售验证</h3>
-              <span class="verify-status-badge" [class.verified]="lead.personaVerified">
-                {{ lead.personaVerified ? '✅ 已验证' : '⏳ 待验证' }}
-              </span>
-            </div>
-
-            <div class="verify-form" *ngIf="!lead.personaVerified">
-              <p class="verify-prompt">请确认 AI 的判断结果:</p>
-
-              <div class="verify-row">
-                <span class="verify-label">画像分类</span>
-                <span class="verify-current">{{ lead.persona }}-{{ lead.personaLabel }}</span>
-                <button mat-stroked-button class="verify-btn correct" (click)="confirmPersona()">
-                  <mat-icon>check</mat-icon> 正确
-                </button>
-                <mat-form-field appearance="outline" class="verify-select">
-                  <mat-label>修改为</mat-label>
-                  <mat-select [(ngModel)]="correctedPersona">
-                    <mat-option *ngFor="let p of personaOptions" [value]="p.id">{{ p.id }}-{{ p.name }}</mat-option>
-                  </mat-select>
-                </mat-form-field>
-              </div>
-
-              <div class="verify-row">
-                <span class="verify-label">价值等级</span>
-                <span class="verify-current badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">{{ lead.valueGrade }}级</span>
-                <button mat-stroked-button class="verify-btn correct" (click)="confirmGrade()">
-                  <mat-icon>check</mat-icon> 正确
-                </button>
-              </div>
-
-              <mat-form-field appearance="outline" class="verify-notes-field">
-                <mat-label>备注(可选)</mat-label>
-                <textarea matInput [(ngModel)]="verifyNotes" rows="2"
-                          placeholder="记录您的判断依据或补充信息..."></textarea>
-              </mat-form-field>
-
-              <button mat-raised-button color="primary" (click)="submitVerification()">
-                <mat-icon>save</mat-icon> 保存验证结果
-              </button>
-            </div>
-
-            <div class="verify-done" *ngIf="lead.personaVerified">
-              <mat-icon class="done-icon">verified</mat-icon>
-              <p>画像已验证为 <strong>{{ lead.persona }}-{{ lead.personaLabel }}</strong></p>
-              <p *ngIf="lead.salesFeedback?.notes" class="done-notes">
-                备注:{{ lead.salesFeedback!.notes }}
-              </p>
-            </div>
-          </div>
-
-          <!-- Product Recommendations -->
-          <div class="section-card card">
-            <div class="section-header">
-              <mat-icon>inventory_2</mat-icon>
-              <h3>推荐产品</h3>
-            </div>
-            <div class="product-list">
-              <div class="product-card" *ngFor="let p of lead.quickScreenResult.suggestedProducts; let i = index">
-                <div class="product-rank" [class]="'rank-' + (i < 3 ? i + 1 : 'other')">{{ i + 1 }}</div>
-                <div class="product-info">
-                  <div class="product-name">{{ p.name }}</div>
-                  <div class="product-en">{{ p.nameEn }}</div>
-                  <div class="product-sku-row">
-                    <span class="sku-tag">{{ p.sku }}</span>
-                    <span class="product-reason">{{ p.reason }}</span>
-                  </div>
-                </div>
-                <div class="product-pricing">
-                  <span class="wholesale-price">\${{ p.wholesalePrice }}</span>
-                  <span class="price-label">批发价</span>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- AI Research Report -->
-          <div class="section-card card" *ngIf="lead.aiResearchReport">
-            <div class="section-header">
-              <mat-icon>analytics</mat-icon>
-              <h3>AI 调研报告</h3>
-              <span class="badge grade-a">已完成</span>
-            </div>
-            <div class="deep-report-preview">
-              <div class="report-item">
-                <mat-icon>business</mat-icon>
-                <div>
-                  <strong>官网分析</strong>
-                  <p>{{ lead.aiResearchReport.websiteAnalysis }}</p>
-                </div>
-              </div>
-              <div class="report-item" *ngIf="lead.aiResearchReport.productMatching.length">
-                <mat-icon>shopping_cart</mat-icon>
-                <div>
-                  <strong>产品匹配</strong>
-                  <div class="match-list">
-                    <div class="match-item" *ngFor="let m of lead.aiResearchReport.productMatching">
-                      <span>{{ m.ourProduct }} ({{ m.ourSku }}) → {{ m.matchesNeed }}</span>
-                      <span class="match-margin">{{ m.profitMargin }}</span>
-                    </div>
-                  </div>
-                </div>
-              </div>
-              <div class="report-item">
-                <mat-icon>groups</mat-icon>
-                <div>
-                  <strong>竞品分析</strong>
-                  <p>{{ lead.aiResearchReport.competitorAnalysis }}</p>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- Deep Analysis Placeholder -->
-          <div class="section-card card deep-analysis-placeholder" *ngIf="!lead.aiResearchReport">
-            <div class="placeholder-content">
-              <mat-icon class="placeholder-icon">search</mat-icon>
-              <h3>AI 调研报告</h3>
-              <p>启动深度分析后,AI 将根据画像类型执行差异化调研:</p>
-              <ul>
-                <li>🌐 {{ getPersonaStrategyLabel(lead.persona) }}</li>
-                <li>📦 产品匹配与利润分析</li>
-                <li>🏭 竞品与供应链分析</li>
-                <li>📋 销售策略与话术生成</li>
-              </ul>
-              <p class="estimate">预估耗时:3-5 分钟</p>
-              <button mat-raised-button color="primary" (click)="startDeepAnalysis()">
-                <mat-icon>play_arrow</mat-icon> 启动深度分析
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="page-container" *ngIf="!lead">
-      <div class="empty-state card">
-        <mat-icon>search_off</mat-icon>
-        <p>未找到该线索</p>
-        <a mat-button (click)="goBack()">返回上一页</a>
-      </div>
-    </div>
-  `,
-    styles: [`
-    .back-link {
-      display: inline-flex; align-items: center; gap: 4px;
-      color: #1976d2; text-decoration: none; font-size: 14px; margin-bottom: 16px; cursor: pointer;
-    }
-    .back-link:hover { text-decoration: underline; }
-
-    .header-card { padding: 24px; margin-bottom: 20px; }
-    .header-top { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; }
-    .header-left { display: flex; gap: 16px; align-items: center; }
-    .company-avatar {
-      width: 56px; height: 56px; border-radius: 14px;
-      display: flex; align-items: center; justify-content: center;
-    }
-    .company-avatar mat-icon { font-size: 28px; width: 28px; height: 28px; color: #fff; }
-    .header-card h1 { margin: 0 0 8px; font-size: 22px; }
-    .header-tags { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
-
-    .persona-badge {
-      display: inline-flex; align-items: center; gap: 4px;
-      padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;
-    }
-    .persona-badge.lg { padding: 4px 14px; font-size: 13px; }
-    .badge-sm-icon { font-size: 14px; width: 14px; height: 14px; }
-    .badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; }
-    .grade-s { background: #ffebee; color: #c62828; }
-    .grade-a { background: #fff3e0; color: #e65100; }
-    .grade-b { background: #fffde7; color: #f9a825; }
-    .grade-c { background: #fafafa; color: #757575; }
-    .stage-badge { }
-    .stage-screened { background: #e3f2fd; color: #1565c0; }
-    .stage-contacted { background: #e8f5e9; color: #2e7d32; }
-    .stage-replied { background: #fff3e0; color: #e65100; }
-    .stage-sample { background: #f3e5f5; color: #6a1b9a; }
-    .stage-quote { background: #e1f5fe; color: #0277bd; }
-    .stage-order { background: #e8f5e9; color: #1b5e20; }
-
-    .verify-badge {
-      display: inline-flex; align-items: center; gap: 4px;
-      padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;
-      background: #fff3e0; color: #e65100;
-    }
-    .verify-badge mat-icon { font-size: 14px; width: 14px; height: 14px; }
-    .verify-badge.verified { background: #e8f5e9; color: #2e7d32; }
-
-    .header-right { text-align: right; }
-    .value-display { margin-bottom: 12px; }
-    .value-amount { font-size: 28px; font-weight: 700; color: #2e7d32; }
-    .value-label { display: block; font-size: 12px; color: #888; }
-
-    .content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
-    .section-card { padding: 20px; margin-bottom: 0; }
-    .section-header { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
-    .section-header mat-icon { color: #1976d2; }
-    .section-header h3 { margin: 0; font-size: 16px; flex: 1; }
-    .time-badge { background: #e3f2fd; color: #1976d2; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; }
-    .confidence-badge { background: #e8f5e9; color: #2e7d32; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600; }
-
-    /* Persona Insight Card */
-    .persona-insight-card { border-left: 4px solid #ccc; }
-    .persona-insight-body { display: flex; flex-direction: column; gap: 12px; }
-    .insight-row { display: flex; align-items: flex-start; gap: 10px; }
-    .insight-label { font-size: 12px; color: #888; min-width: 90px; flex-shrink: 0; padding-top: 4px; }
-    .insight-value { font-size: 14px; color: #333; display: flex; align-items: center; gap: 4px; }
-    .insight-value mat-icon { font-size: 16px; width: 16px; height: 16px; color: #999; }
-    .insight-value.highlight { color: #1565c0; font-weight: 600; }
-    .insight-keywords { display: flex; gap: 6px; flex-wrap: wrap; }
-    .kw-chip { font-size: 11px; padding: 2px 8px; background: #e8eaf6; color: #5c6bc0; border-radius: 6px; font-family: monospace; }
-
-    .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
-    .info-label { display: block; font-size: 11px; color: #888; margin-bottom: 2px; }
-    .info-value { font-size: 14px; color: #333; font-weight: 500; }
-    .email-link { color: #1976d2; }
-    .rel-tag { padding: 1px 6px; border-radius: 4px; font-size: 12px; }
-    .rel-high { background: #e8f5e9; color: #2e7d32; }
-    .rel-medium { background: #fff8e1; color: #f57f17; }
-    .rel-low { background: #fafafa; color: #999; }
-
-    .certs-row { margin-top: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
-    .cert-chips { display: flex; gap: 6px; }
-    .cert-chip { background: #e3f2fd; color: #1565c0; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
-
-    /* Email Card */
-    .email-card { border-left: 4px solid #1976d2; }
-    .email-subject-row {
-      display: flex; align-items: flex-start; gap: 8px; margin-bottom: 14px;
-      padding: 10px 14px; background: #f0f4ff; border-radius: 8px;
-    }
-    .email-meta-icon { font-size: 18px; width: 18px; height: 18px; color: #1976d2; flex-shrink: 0; margin-top: 2px; }
-    .email-subject { font-size: 15px; font-weight: 600; color: #1a237e; line-height: 1.4; }
-
-    .email-meta-grid { display: flex; flex-direction: column; gap: 8px; }
-    .email-meta-item { display: flex; align-items: flex-start; gap: 8px; }
-    .email-meta-label {
-      font-size: 12px; color: #888; min-width: 48px; flex-shrink: 0;
-      padding: 2px 8px; background: #f5f5f5; border-radius: 4px; text-align: center;
-    }
-    .email-meta-value { font-size: 13px; color: #444; word-break: break-all; line-height: 1.5; }
-    .email-meta-value strong { color: #333; }
-
-    .email-body-section { position: relative; }
-    .email-body { overflow: hidden; transition: max-height 0.3s ease; }
-    .email-body.collapsed { max-height: 120px; }
-    .email-body-text {
-      font-size: 13px; color: #555; line-height: 1.7; white-space: pre-wrap;
-      word-wrap: break-word; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-      margin: 0; padding: 0;
-    }
-    .expand-btn { display: flex; align-items: center; gap: 4px; margin: 4px auto 0; font-size: 13px; color: #1976d2; }
-
-    .attachments-header { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; margin-bottom: 10px; }
-    .attachments-header mat-icon { font-size: 18px; width: 18px; height: 18px; color: #888; }
-    .attachment-list { display: flex; flex-direction: column; gap: 6px; }
-    .attachment-chip {
-      display: flex; align-items: center; gap: 8px; padding: 8px 12px;
-      background: #f8f9fa; border-radius: 8px; border: 1px solid #eee; transition: background 0.15s;
-    }
-    .attachment-chip:hover { background: #e3f2fd; border-color: #bbdefb; }
-    .att-icon { font-size: 20px; width: 20px; height: 20px; color: #e53935; flex-shrink: 0; }
-    .att-name { font-size: 13px; color: #333; font-weight: 500; flex: 1; word-break: break-all; }
-    .att-size { font-size: 11px; color: #999; flex-shrink: 0; }
-
-    .no-email-card { border-left: 4px solid #e0e0e0; }
-    .no-email-placeholder {
-      display: flex; align-items: center; gap: 10px; padding: 20px; color: #999; font-size: 14px;
-    }
-    .placeholder-sm { font-size: 32px; width: 32px; height: 32px; color: #ddd; }
-
-    /* Verify Section */
-    .verify-section { }
-    .verify-status-badge { font-size: 12px; padding: 2px 10px; border-radius: 10px; background: #fff3e0; color: #e65100; }
-    .verify-status-badge.verified { background: #e8f5e9; color: #2e7d32; }
-    .verify-prompt { font-size: 14px; color: #555; margin: 0 0 12px; }
-    .verify-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
-    .verify-label { font-size: 13px; color: #888; min-width: 70px; }
-    .verify-current { font-size: 14px; font-weight: 600; color: #333; }
-    .verify-btn { font-size: 12px; }
-    .verify-btn.correct mat-icon { font-size: 16px; width: 16px; height: 16px; color: #2e7d32; }
-    .verify-select { max-width: 200px; }
-    .verify-notes-field { width: 100%; margin-top: 8px; }
-    .verify-done { text-align: center; padding: 20px; }
-    .done-icon { font-size: 48px; width: 48px; height: 48px; color: #2e7d32; }
-    .verify-done p { font-size: 14px; color: #555; margin: 8px 0 0; }
-    .done-notes { font-size: 13px; color: #888; font-style: italic; }
-
-    /* Products */
-    .product-list { display: flex; flex-direction: column; gap: 10px; }
-    .product-card { display: flex; gap: 12px; align-items: center; padding: 12px; background: #fafafa; border-radius: 10px; transition: background 0.15s; }
-    .product-card:hover { background: #f0f4ff; }
-    .product-rank { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex-shrink: 0; }
-    .rank-1 { background: #fff3e0; color: #e65100; }
-    .rank-2 { background: #e3f2fd; color: #1565c0; }
-    .rank-3 { background: #e8f5e9; color: #2e7d32; }
-    .rank-other { background: #f5f5f5; color: #999; }
-    .product-info { flex: 1; }
-    .product-name { font-size: 14px; font-weight: 600; color: #333; }
-    .product-en { font-size: 11px; color: #999; }
-    .product-sku-row { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
-    .sku-tag { font-size: 10px; padding: 1px 6px; background: #e8eaf6; color: #5c6bc0; border-radius: 4px; font-family: monospace; }
-    .product-reason { font-size: 11px; color: #888; }
-    .product-pricing { text-align: right; }
-    .wholesale-price { font-size: 18px; font-weight: 700; color: #2e7d32; display: block; }
-    .price-label { font-size: 10px; color: #999; }
-
-    /* Report */
-    .deep-report-preview { display: flex; flex-direction: column; gap: 14px; }
-    .report-item { display: flex; gap: 12px; padding: 12px; background: #f8f9fa; border-radius: 8px; }
-    .report-item mat-icon { color: #1976d2; flex-shrink: 0; margin-top: 2px; }
-    .report-item strong { font-size: 14px; }
-    .report-item p { font-size: 13px; color: #555; margin: 4px 0 0; }
-    .match-list { margin-top: 6px; }
-    .match-item { font-size: 13px; color: #555; padding: 4px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }
-    .match-margin { font-size: 12px; color: #2e7d32; font-weight: 500; }
-
-    .deep-analysis-placeholder { border: 2px dashed #e0e0e0; background: #fafafa; }
-    .placeholder-content { text-align: center; padding: 20px; }
-    .placeholder-icon { font-size: 48px; width: 48px; height: 48px; color: #ccc; }
-    .placeholder-content h3 { margin: 8px 0; }
-    .placeholder-content p { font-size: 14px; color: #666; }
-    .placeholder-content ul { text-align: left; max-width: 300px; margin: 12px auto; font-size: 13px; color: #555; line-height: 1.8; }
-    .estimate { font-size: 12px; color: #999; margin-bottom: 16px !important; }
-
-    .empty-state { display: flex; flex-direction: column; align-items: center; padding: 60px 20px; color: #999; }
-    .empty-state mat-icon { font-size: 64px; width: 64px; height: 64px; color: #ddd; }
-
-    @media (max-width: 900px) { .content-grid { grid-template-columns: 1fr; } }
-  `]
+    templateUrl: './lead-detail.component.html',
+    styleUrls: ['./lead-detail.component.scss'],
 })
 })
 export class LeadDetailComponent implements OnInit {
 export class LeadDetailComponent implements OnInit {
   lead: Lead | null = null;
   lead: Lead | null = null;
   personaOptions = PERSONA_LIST;
   personaOptions = PERSONA_LIST;
   correctedPersona: CustomerPersona | null = null;
   correctedPersona: CustomerPersona | null = null;
+  correctedGrade: ValueGrade | null = null;
   verifyNotes = '';
   verifyNotes = '';
   emailBodyExpanded = false;
   emailBodyExpanded = false;
+  deepResearchResult: DeepResearchResult | null = null;
+
+  // 背调相关
+  isBgChecking = false;
+  bgCheckStages: BgCheckStage[] = [];
+  bgCheckProgress = 0;
 
 
   private emails: Email[] = [];
   private emails: Email[] = [];
 
 
@@ -602,12 +60,21 @@ export class LeadDetailComponent implements OnInit {
     private location: Location,
     private location: Location,
     private mockData: MockDataService,
     private mockData: MockDataService,
     private snackBar: MatSnackBar,
     private snackBar: MatSnackBar,
-  ) {}
+  ) {
+    this.bgCheckStages = [
+      { id: 1, name: '初始化数据通过', status: 'waiting', icon: 'schedule', description: '验证输入信息' },
+      { id: 2, name: '公司基础信息', status: 'waiting', icon: 'business', description: '官网爬取、企业数据' },
+      { id: 3, name: '社媒平台验证', status: 'waiting', icon: 'share', description: 'LinkedIn/Twitter/Instagram/YouTube' },
+      { id: 4, name: '决策人调查', status: 'waiting', icon: 'person', description: 'LinkedIn 员工搜索、角色匹配' },
+      { id: 5, name: '综合评级与策略', status: 'waiting', icon: 'assessment', description: 'AI 综合分析' },
+    ];
+  }
 
 
   ngOnInit() {
   ngOnInit() {
     const id = this.route.snapshot.paramMap.get('id');
     const id = this.route.snapshot.paramMap.get('id');
     this.lead = this.mockData.leads.find(l => l.id === id) || null;
     this.lead = this.mockData.leads.find(l => l.id === id) || null;
     this.emails = this.mockData.emails;
     this.emails = this.mockData.emails;
+    this.deepResearchResult = this.getDeepResearchResult(this.lead);
   }
   }
 
 
   getPersonaColor(p: CustomerPersona): string { return PERSONA_DEFINITIONS[p]?.color || '#999'; }
   getPersonaColor(p: CustomerPersona): string { return PERSONA_DEFINITIONS[p]?.color || '#999'; }
@@ -651,6 +118,37 @@ export class LeadDetailComponent implements OnInit {
     this.snackBar.open('✅ 价值等级已确认', '', { duration: 1500 });
     this.snackBar.open('✅ 价值等级已确认', '', { duration: 1500 });
   }
   }
 
 
+  confirmLead() {
+    if (!this.lead) return;
+    this.lead.personaVerified = true;
+    this.lead.salesFeedback = {
+      isPersonaCorrect: true,
+      isGradeCorrect: true,
+      notes: this.verifyNotes,
+      feedbackAt: new Date(),
+    };
+    this.snackBar.open(`✅ 已确认:${this.lead.companyName} → ${this.lead.personaLabel}`, '', { duration: 2000 });
+  }
+
+  editLead() {
+    this.submitVerification();
+  }
+
+  dismissLead() {
+    if (!this.lead) return;
+    this.lead.personaVerified = true;
+    this.lead.persona = 'P6' as CustomerPersona;
+    this.lead.personaLabel = '低质量/无效线索';
+    this.lead.salesFeedback = {
+      isPersonaCorrect: false,
+      correctedPersona: 'P6' as CustomerPersona,
+      isGradeCorrect: true,
+      notes: this.verifyNotes || '标记为无效线索',
+      feedbackAt: new Date(),
+    };
+    this.snackBar.open(`❌ 已标记为无效:${this.lead.companyName}`, '', { duration: 2000 });
+  }
+
   submitVerification() {
   submitVerification() {
     if (!this.lead) return;
     if (!this.lead) return;
     this.lead.personaVerified = true;
     this.lead.personaVerified = true;
@@ -658,10 +156,14 @@ export class LeadDetailComponent implements OnInit {
       this.lead.persona = this.correctedPersona;
       this.lead.persona = this.correctedPersona;
       this.lead.personaLabel = PERSONA_DEFINITIONS[this.correctedPersona].name;
       this.lead.personaLabel = PERSONA_DEFINITIONS[this.correctedPersona].name;
     }
     }
+    if (this.correctedGrade) {
+      this.lead.valueGrade = this.correctedGrade;
+    }
     this.lead.salesFeedback = {
     this.lead.salesFeedback = {
       isPersonaCorrect: !this.correctedPersona,
       isPersonaCorrect: !this.correctedPersona,
       correctedPersona: this.correctedPersona || undefined,
       correctedPersona: this.correctedPersona || undefined,
-      isGradeCorrect: true,
+      isGradeCorrect: !this.correctedGrade,
+      correctedGrade: this.correctedGrade || undefined,
       notes: this.verifyNotes,
       notes: this.verifyNotes,
       feedbackAt: new Date(),
       feedbackAt: new Date(),
     };
     };
@@ -672,7 +174,530 @@ export class LeadDetailComponent implements OnInit {
     this.location.back();
     this.location.back();
   }
   }
 
 
-  startDeepAnalysis() {
-    this.snackBar.open('🔍 深度分析功能将在 Phase 3 上线(预估耗时 3-5 分钟)', '', { duration: 3000 });
+  async startDeepAnalysis() {
+    if (!this.lead) return;
+
+    if (!this.lead.personaVerified) {
+      this.snackBar.open('⚠️ 请先完成销售验证,才能启动深度分析', '', {
+        duration: 3000,
+        panelClass: ['warning-snackbar']
+      });
+      return;
+    }
+
+    if (this.lead.hasBackgroundCheck && this.lead.backgroundCheckReport) {
+      this.router.navigate(['/leads', this.lead.id, 'bgcheck']);
+      return;
+    }
+
+    this.isBgChecking = true;
+    this.bgCheckProgress = 0;
+    this.bgCheckStages.forEach(s => s.status = 'waiting');
+
+    try {
+      await this.updateStage(1, 'processing');
+      await this.delay(800);
+      await this.updateStage(1, 'completed');
+      this.bgCheckProgress = 20;
+
+      await this.updateStage(2, 'processing');
+      await this.delay(1500);
+      await this.updateStage(2, 'completed');
+      this.bgCheckProgress = 40;
+
+      await this.updateStage(3, 'processing');
+      await this.delay(1200);
+      await this.updateStage(3, 'completed');
+      this.bgCheckProgress = 60;
+
+      await this.updateStage(4, 'processing');
+      await this.delay(1000);
+      await this.updateStage(4, 'completed');
+      this.bgCheckProgress = 80;
+
+      await this.updateStage(5, 'processing');
+      await this.delay(1500);
+      await this.updateStage(5, 'completed');
+      this.bgCheckProgress = 100;
+
+      const report = this.mockData.generateBackgroundCheckReport(this.lead);
+      this.lead.hasBackgroundCheck = true;
+      this.lead.backgroundCheckAt = new Date();
+      this.lead.backgroundCheckReport = report;
+
+      this.snackBar.open('✅ 背调完成!', '查看报告', { duration: 3000 })
+        .onAction().subscribe(() => {
+          this.router.navigate(['/leads', this.lead!.id, 'bgcheck']);
+        });
+
+    } catch (error) {
+      console.error('背调失败:', error);
+      this.snackBar.open('❌ 背调失败,请重试', '', { duration: 3000 });
+    } finally {
+      this.isBgChecking = false;
+    }
+  }
+
+  private async updateStage(stageId: number, status: BgCheckStage['status']) {
+    const stage = this.bgCheckStages.find(s => s.id === stageId);
+    if (stage) {
+      stage.status = status;
+    }
+  }
+
+  private delay(ms: number): Promise<void> {
+    return new Promise(resolve => setTimeout(resolve, ms));
+  }
+
+  getDeepResearchResult(lead: Lead | null): DeepResearchResult | null {
+    if (!lead || !lead.hasDeepAnalysis) {
+      return null;
+    }
+
+    if (lead.aiResearchReport) {
+      const productMatching = lead.aiResearchReport.productMatching.map(m => ({
+        customerNeed: m.matchesNeed,
+        recommendedProduct: {
+          name: m.ourProduct,
+          sku: m.ourSku
+        },
+        annualValue: lead.estimatedOrderValue || 0
+      }));
+
+      const certificationMatch = lead.quickScreenResult.certRequirements.map(cert => ({
+        required: cert,
+        status: 'matched' as const,
+        ourCert: cert
+      }));
+
+      return {
+        leadId: lead.id,
+        researchedAt: lead.deepAnalysisAt?.toISOString() || new Date().toISOString(),
+        persona: lead.persona,
+        companyProfile: {
+          description: lead.aiResearchReport.websiteAnalysis || '暂无描述',
+          coverage: [lead.country],
+          productLines: lead.quickScreenResult.suggestedProducts.map(p => p.name)
+        },
+        contactProfile: {
+          name: lead.contactName,
+          title: 'Contact',
+          decisionPower: lead.valueGrade === 'S' || lead.valueGrade === 'A' ? 'high' :
+                        lead.valueGrade === 'B' ? 'medium' : 'low'
+        },
+        productMatching,
+        totalEstimatedValue: lead.estimatedOrderValue || 0,
+        certificationMatch,
+        followUpStrategy: {
+          timeframe: '24小时内',
+          filesToSend: ['产品目录', '认证证书', '公司介绍'],
+          keyPoints: [
+            `针对${lead.personaLabel}客户的定制化方案`,
+            '强调产品匹配度和认证优势',
+            '提供快速响应和优质服务'
+          ]
+        },
+        suggestedScript: {
+          draft: `尊敬的${lead.contactName},\n\n感谢您对我司产品的关注。根据您的需求,我们为您推荐以下产品方案...`,
+          editable: true
+        }
+      };
+    }
+
+    return null;
+  }
+
+  onEditScript() {
+    this.snackBar.open('✏️ 编辑话术功能开发中', '', { duration: 2000 });
+  }
+
+  onSendEmail() {
+    this.snackBar.open('📧 发送邮件功能开发中', '', { duration: 2000 });
+  }
+
+  onCreateLead() {
+    this.snackBar.open('✅ 建档功能开发中', '', { duration: 2000 });
+  }
+
+  viewBgCheckReport() {
+    if (this.lead) {
+      this.router.navigate(['/leads', this.lead.id, 'bgcheck']);
+    }
+  }
+
+  getDemandSnapshot(): { products: Array<{ name: string; quantity: number; unit: string }>; estimatedAnnualValue: number } | undefined {
+    if (!this.lead) return undefined;
+
+    if (this.lead.quickScreenResult.suggestedProducts.length > 0) {
+      return {
+        products: this.lead.quickScreenResult.suggestedProducts.map(p => ({
+          name: p.name,
+          quantity: 1000,
+          unit: '/年'
+        })),
+        estimatedAnnualValue: this.lead.estimatedOrderValue ||
+          this.lead.quickScreenResult.suggestedProducts.reduce((sum, p) => sum + p.wholesalePrice * 1000, 0)
+      };
+    }
+    return undefined;
+  }
+
+  getCredibilityLevel(): 'high' | 'medium' | 'low' {
+    if (!this.lead) return 'medium';
+
+    const domain = this.lead.quickScreenResult.domain;
+    if (domain && !domain.includes('gmail') && !domain.includes('hotmail') && !domain.includes('yahoo')) {
+      return 'high';
+    }
+    if (this.lead.quickScreenResult.confidence > 70) {
+      return 'medium';
+    }
+    return 'low';
+  }
+
+  getCredibilitySignals(): string[] {
+    if (!this.lead) return ['基础信息完整'];
+
+    const signals: string[] = [];
+    const domain = this.lead.quickScreenResult.domain;
+
+    if (domain && !domain.includes('gmail') && !domain.includes('hotmail') && !domain.includes('yahoo')) {
+      signals.push('企业邮箱');
+    }
+    if (this.lead.quickScreenResult.confidence > 70) {
+      signals.push('完整签名块');
+    }
+    if (this.lead.quickScreenResult.certRequirements.length > 0) {
+      signals.push('有认证要求');
+    }
+    if (this.lead.contactName && this.lead.contactName.length > 0) {
+      signals.push('联系人信息完整');
+    }
+
+    return signals.length > 0 ? signals : ['基础信息完整'];
+  }
+
+  getScores(): { demandClarity: number; credibility: number; businessScale: number; productFit: number } | undefined {
+    if (!this.lead) return undefined;
+
+    const gradeScores: Record<string, number> = { S: 90, A: 80, B: 70, C: 60 };
+    const baseScore = gradeScores[this.lead.valueGrade] || 70;
+
+    return {
+      demandClarity: this.lead.quickScreenResult.suggestedProducts.length > 0 ? 85 : 60,
+      credibility: this.getCredibilityLevel() === 'high' ? 85 : this.getCredibilityLevel() === 'medium' ? 70 : 55,
+      businessScale: baseScore,
+      productFit: this.lead.quickScreenResult.productRelevance === 'high' ? 85 :
+                   this.lead.quickScreenResult.productRelevance === 'medium' ? 70 : 55
+    };
+  }
+
+  formatNumber(value: number): string {
+    return new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value);
+  }
+
+  /** 判断当前线索是否为无效/低质量线索 */
+  isInvalidLead(): boolean {
+    return this.lead?.persona === 'P6';
+  }
+
+  getRecommendedNextStep(): string {
+    if (!this.lead) return '';
+
+    if (this.lead.quickScreenResult?.recommendedAction) {
+      return this.lead.quickScreenResult.recommendedAction;
+    }
+
+    if (this.lead.persona) {
+      return this.getPersonaFirstAction(this.lead.persona);
+    }
+
+    return '';
+  }
+
+  getRecommendedNextStepsDetails(): { timeframe?: string; filesToSend?: string[]; keyPoints?: string[] } | null {
+    if (!this.lead?.quickScreenResult?.recommendedActions) {
+      return null;
+    }
+    return this.lead.quickScreenResult.recommendedActions;
+  }
+
+  // ===== Quotation Generation =====
+  showQuotationModal = false;
+  showSalesGuidance = true;
+  quotationData: any = null;
+
+  generateQuotation() {
+    if (!this.lead) return;
+    this.quotationData = this.buildQuotationData(this.lead);
+    this.showQuotationModal = true;
+  }
+
+  closeQuotation() {
+    this.showQuotationModal = false;
+    this.quotationData = null;
+  }
+
+  toggleSalesGuidance() {
+    this.showSalesGuidance = !this.showSalesGuidance;
+  }
+
+  exportQuotationPDF() {
+    alert('PDF导出功能开发中...');
+  }
+
+  sendQuotation() {
+    alert('报价单已发送给客户!(模拟)');
+    this.closeQuotation();
+  }
+
+  private buildQuotationData(lead: Lead): any {
+    const now = new Date();
+    const validUntil = new Date(now.getTime() + 30 * 86400000);
+    const dateStr = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+
+    const certReqs = lead.quickScreenResult?.certRequirements || ['CE', 'ISO13485'];
+    const products = (lead.quickScreenResult?.suggestedProducts || []).map((p, i) => ({
+      productCode: p.sku,
+      productName: p.name,
+      category: lead.categoryLabel,
+      quantity: Math.max(100, Math.floor(lead.estimatedOrderValue / (p.wholesalePrice || 50) / (lead.quickScreenResult?.suggestedProducts?.length || 1))),
+      unitPrice: p.wholesalePrice,
+      totalPrice: Math.max(100, Math.floor(lead.estimatedOrderValue / (lead.quickScreenResult?.suggestedProducts?.length || 1))),
+      matchReason: p.reason,
+      specifications: `${p.nameEn} - 标准出口规格`,
+      keyFeatures: ['CE/FDA认证', 'ISO13485质量体系', '专业出口包装'],
+      certifications: certReqs,
+      alternatives: this.getAlternativeProducts(p.sku),
+    }));
+
+    const subtotal = products.reduce((s, p) => s + p.totalPrice, 0);
+    const discount = subtotal > 50000 ? Math.round(subtotal * 0.05) : 0;
+    const totalAmount = subtotal - discount;
+
+    const requirementAnalysis = {
+      completeness: 0.85,
+      customerNeeds: [
+        `${personaDef.name}场景下的急救/医疗产品采购需求`,
+        `面向${lead.country}市场的合规产品供应`,
+        `预算范围 USD ${(lead.estimatedOrderValue * 0.8).toLocaleString()} - ${(lead.estimatedOrderValue * 1.2).toLocaleString()}`,
+        `需要稳定的长期供应商合作关系`,
+        `要求产品具备${certReqs.join('/')}等国际认证`,
+      ],
+      technicalRequirements: [
+        `符合${lead.country}市场准入标准的产品规格`,
+        `产品需通过 ${certReqs.join(', ')} 认证`,
+        `包装需符合国际运输标准(ISTA 3A)`,
+        `产品标签需支持${lead.country === 'US' ? '英语' : lead.country === 'DE' ? '德语' : '当地语言'}标注`,
+        `保质期不低于3年(自生产日期起)`,
+        `产品需附带使用说明书(多语言版本)`,
+      ],
+      complianceRequirements: [
+        ...certReqs.map(c => `${c} 认证合规`),
+        '产品质量管理体系 ISO 13485',
+        `符合${lead.country === 'US' ? 'FDA 510(k)' : lead.country === 'DE' ? 'EU MDR 2017/745' : '目标市场'}法规要求`,
+        '出口商品检验检疫合格',
+        '产品责任保险覆盖',
+      ],
+    };
+
+    const backgroundInsights = {
+      companyOverview: `${lead.companyName} 是一家位于${lead.country}的${personaDef.description}企业,主要从事${personaDef.name}相关业务。`,
+      marketPosition: this.getMarketPosition(lead),
+      purchasingBehavior: this.getPurchasingBehavior(lead),
+      riskAssessment: this.getRiskAssessment(lead),
+      competitorAnalysis: this.getCompetitorAnalysis(lead),
+    };
+
+    const solutionHighlights = [
+      { icon: '🎯', title: '精准匹配', desc: `基于${personaDef.name}画像,精选${products.length}款高匹配度产品` },
+      { icon: '💰', title: '价格优势', desc: `批量采购优惠,${discount > 0 ? '享受5%大额折扣' : '量大价优可议'}` },
+      { icon: '🔒', title: '合规保障', desc: `全线产品通过${certReqs.slice(0, 3).join('/')}认证,确保市场准入` },
+      { icon: '🚀', title: '快速交付', desc: `15-25个工作日交付,支持分批发货` },
+      { icon: '🛡️', title: '售后无忧', desc: `2年质保 + 7×24技术支持 + 专属客户经理` },
+    ];
+
+    return {
+      title: `${lead.companyName} - 产品报价方案`,
+      proposalNumber: `NC-QT-${dateStr(now).replace(/-/g, '')}-${lead.id.replace('l', '')}`,
+      preparedDate: dateStr(now),
+      validUntil: dateStr(validUntil),
+      customerSummary: {
+        companyName: lead.companyName,
+        contactPerson: lead.contactName,
+        email: lead.contactEmail || `${lead.contactName.toLowerCase().replace(/\s/g, '.')}@${lead.companyName.toLowerCase().replace(/\s/g, '')}.com`,
+        country: lead.country,
+        industry: personaDef.name,
+        persona: lead.persona,
+        valueGrade: lead.valueGrade,
+        projectBackground: `${lead.companyName} 是一家位于${lead.country}的${personaDef.description}企业。`,
+        estimatedOrderValue: lead.estimatedOrderValue,
+      },
+      backgroundInsights,
+      requirementAnalysis,
+      solutionHighlights,
+      executiveSummary: `基于对 ${lead.companyName} 的深度背调分析,我们为贵司量身定制了以下产品报价方案。本方案涵盖 ${products.length} 款核心产品,总报价金额 USD ${totalAmount.toLocaleString()},充分匹配贵司的${personaDef.name}业务需求。方案已通过需求完整度评估(完整度 ${Math.round(requirementAnalysis.completeness * 100)}%),覆盖客户需求 ${requirementAnalysis.customerNeeds.length} 项、技术要求 ${requirementAnalysis.technicalRequirements.length} 项、合规要求 ${requirementAnalysis.complianceRequirements.length} 项。`,
+      productRecommendations: products,
+      pricingSummary: {
+        currency: 'USD',
+        subtotal,
+        discount,
+        discountRate: discount > 0 ? 5 : 0,
+        totalAmount,
+        priceValidityPeriod: '30天(自报价日期起)',
+        items: products.map(p => ({
+          description: p.productName,
+          quantity: p.quantity,
+          unitPrice: p.unitPrice,
+          totalPrice: p.totalPrice,
+        })),
+      },
+      commercialTerms: {
+        moq: `${products.length > 0 ? products[0].quantity : 100} 件`,
+        leadTime: '订单确认后 15-25 个工作日',
+        deliveryTerms: `FOB上海 / CIF ${lead.country}`,
+        paymentTerms: '30% 预付,70% 发货前付清(大额订单接受信用证)',
+        packingDetails: '标准出口纸箱,可定制包装(支持客户品牌印刷)',
+        shippingMethod: '海运/空运(根据客户选择)',
+        warranty: '2年质保(制造缺陷)',
+        returnPolicy: '收货30天内可退换(产品完好)',
+        samplePolicy: '首次合作可提供免费样品(运费到付)',
+        insuranceCoverage: '全程货运保险,CIF条款下含保险',
+      },
+      qualityAssurance: {
+        certifications: certReqs,
+        testingStandards: ['GB/T 19001', 'ISO 13485:2016', 'EN 1789'],
+        qualityProcess: [
+          '原材料入库检验(IQC)',
+          '生产过程巡检(IPQC)',
+          '成品出货检验(OQC)',
+          '第三方抽样检测报告',
+        ],
+        traceability: '全流程条码追溯系统,支持产品批次追溯',
+      },
+      afterSalesService: {
+        technicalSupport: '7×24小时在线技术支持,专属客户经理对接',
+        training: '免费产品使用培训(线上/线下),提供多语言操作手册',
+        warranty: '2年质保期内免费维修更换,质保期外提供优惠维护服务',
+        returnService: '30天无理由退换,质量问题全额赔付',
+        customization: '支持OEM/ODM定制,最低起订量可协商',
+      },
+      nextSteps: [
+        { action: '确认产品规格与数量', responsible: '客户', deadline: dateStr(new Date(now.getTime() + 7 * 86400000)), priority: 'high' },
+        { action: '寄送样品确认', responsible: '南驰医疗', deadline: dateStr(new Date(now.getTime() + 14 * 86400000)), priority: 'medium' },
+        { action: '签订正式合同', responsible: '双方', deadline: dateStr(new Date(now.getTime() + 21 * 86400000)), priority: 'medium' },
+        { action: '安排生产与发货', responsible: '南驰医疗', deadline: dateStr(new Date(now.getTime() + 45 * 86400000)), priority: 'low' },
+        { action: '到货验收 & 售后对接', responsible: '双方', deadline: dateStr(new Date(now.getTime() + 55 * 86400000)), priority: 'low' },
+      ],
+      salesGuidance: this.buildSalesGuidance(lead, personaDef, products, certReqs, totalAmount),
+      preparedBy: '南驰医疗销售团队',
+      companyInfo: {
+        name: '南驰(广州)医疗科技有限公司',
+        address: '广东省广州市黄埔区科学城',
+        website: 'www.rhinorescue.com',
+        phone: '+86-020-XXXXXXXX',
+      },
+    };
+  }
+
+  private buildSalesGuidance(lead: Lead, personaDef: any, products: any[], certReqs: string[], totalAmount: number) {
+    const countryName: Record<string, string> = {
+      US: '美国', GB: '英国', DE: '德国', PL: '波兰', CA: '加拿大', AU: '澳大利亚', SE: '瑞典', CN: '中国',
+    };
+    const cn = countryName[lead.country] || lead.country;
+    const certStr = certReqs.slice(0, 3).join('/');
+
+    const keyTalkingPoints: string[] = [
+      `我们的${certStr}认证确保完全符合${cn}及${lead.country === 'DE' ? '欧盟' : '目标市场'}医疗器械法规要求`,
+      `产品保质期长达5年,为贵司提供充足的库存管理灵活性`,
+      `直接工厂合作模式,消除中间商成本,同时确保品质管控`,
+      `在${personaDef.name}领域拥有丰富的大型项目供货经验,可提供成功案例参考`,
+      `灵活的分批发货方案,完美配合贵司的项目时间节点`,
+    ];
+
+    const anticipatedObjections: { objection: string; response: string }[] = [
+      {
+        objection: '为什么我们要更换现有供应商?',
+        response: `我们的直接制造模式在保持卓越品质的同时提供显著成本优势。我们提供专属客户经理和灵活的生产排期,这是许多大型制造商无法匹配的服务。`,
+      },
+      {
+        objection: `你们真的能在10周内交付${products.reduce((s, p) => s + p.quantity, 0).toLocaleString()}件产品吗?`,
+        response: `我们的月产能超过100,000件,并且已成功向${cn === '德国' ? '欧洲' : '全球'}多个${personaDef.name}机构交付同等规模的订单。我们可以提供在约定交期内完成交付的客户推荐信。`,
+      },
+      {
+        objection: '你们的价格似乎比某些竞争对手高',
+        response: `部分供应商虽然报价更低,但往往在材料品质或认证合规方面有所妥协。我们的产品在粘合强度和保质期方面达到最高标准,这在${personaDef.name}等关键应用场景中至关重要——产品失效可能造成严重后果。`,
+      },
+    ];
+
+    const negotiationTips: string[] = [
+      `强调${certStr}合规的价值,而非单纯比较价格`,
+      '准备好为未来订单承诺提供阶梯式数量折扣',
+      `突出我们在${cn}市场的本地化服务能力和快速响应优势`,
+      '如客户犹豫,可提出免费样品试用计划降低决策门槛',
+      `利用竞品分析数据,展示我们在${personaDef.name}领域的差异化优势`,
+    ];
+
+    const closingStrategies: string[] = [
+      `提供限时优惠:本报价有效期内下单可额外享受${lead.valueGrade === 'S' ? '3%' : '2%'}早鸟折扣`,
+      '建议先从小批量试单开始,建立信任后逐步扩大合作规模',
+      '主动提出安排工厂视频参观或实地考察,增强客户信心',
+      `强调USD ${totalAmount.toLocaleString()}的总报价已包含${certStr}认证产品的溢价价值`,
+      '如客户有预算限制,可灵活调整产品组合或分阶段采购方案',
+    ];
+
+    const followUpActions: string[] = [
+      '发送报价后24小时内进行首次电话跟进,确认客户已收到并解答初步疑问',
+      '3个工作日内安排技术团队进行产品规格详细说明会(线上)',
+      '1周内寄出样品包(含全系列推荐产品),附产品检测报告',
+      '2周内安排第二次商务洽谈,针对客户反馈调整方案',
+      '持续关注客户动态,及时响应竞品报价信息',
+    ];
+
+    return { keyTalkingPoints, anticipatedObjections, negotiationTips, closingStrategies, followUpActions };
+  }
+
+  private getAlternativeProducts(sku: string): string[] {
+    const altMap: Record<string, string[]> = {
+      'RR-IFAK-001': ['RR-IFAK-002 (升级版)', 'RR-IFAK-003 (迷你版)'],
+      'RR-TQ-001': ['RR-TQ-002 (宽版)', 'CAT Gen7'],
+      'RR-CS-001': ['RR-CS-002 (加压型)', 'QuikClot Combat'],
+      'RR-BD-001': ['RR-BD-002 (弹力型)', 'Israeli Bandage 6"'],
+    };
+    return altMap[sku] || [`${sku}-PRO (专业版)`, `${sku}-ECO (经济版)`];
+  }
+
+  private getMarketPosition(lead: Lead): string {
+    const positions: Record<string, string> = {
+      S: '行业头部企业,采购量大且稳定,属于战略级客户',
+      A: '行业中上游企业,有较强采购能力,属于重点培育客户',
+      B: '中等规模企业,采购潜力待挖掘,属于成长型客户',
+      C: '初创或小型企业,采购量有限,属于标准服务客户',
+    };
+    return positions[lead.valueGrade] || '待评估';
+  }
+
+  private getPurchasingBehavior(lead: Lead): string {
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+    return `基于${personaDef.name}画像特征,该客户倾向于${lead.estimatedOrderValue > 50000 ? '大批量集中采购' : '小批量多频次采购'},预计年采购额约 USD ${lead.estimatedOrderValue.toLocaleString()}。决策周期${lead.valueGrade === 'S' || lead.valueGrade === 'A' ? '较短(1-2周)' : '中等(2-4周)'}。`;
+  }
+
+  private getRiskAssessment(lead: Lead): { level: string; color: string; factors: string[] } {
+    if (lead.valueGrade === 'S' || lead.valueGrade === 'A') {
+      return { level: '低风险', color: '#10b981', factors: ['信用评级良好', '有稳定采购记录', '付款能力充足'] };
+    } else if (lead.valueGrade === 'B') {
+      return { level: '中风险', color: '#f59e0b', factors: ['信用评级中等', '首次合作建议小额试单', '建议预付款比例≥50%'] };
+    }
+    return { level: '待评估', color: '#6b7280', factors: ['信息不足,建议进一步背调', '首单建议全额预付', '密切关注付款情况'] };
+  }
+
+  private getCompetitorAnalysis(lead: Lead): string[] {
+    return [
+      'North American Rescue (NAR) - 美国市场主力品牌',
+      'TacMed Solutions - 军警领域竞品',
+      'SAM Medical - 急救固定产品线竞品',
+      '我司优势:性价比高30-40%,交期快50%,支持深度定制',
+    ];
   }
   }
 }
 }

+ 192 - 0
lead-discovery/src/app/pages/lead-list/lead-list.component.html

@@ -0,0 +1,192 @@
+<div class="page-container">
+  <div class="page-header">
+    <h1><mat-icon>people</mat-icon> 线索管理</h1>
+    <p class="subtitle">所有线索的多维度视图,支持按画像分组管理</p>
+  </div>
+
+  <!-- View Toggle -->
+  <div class="view-toggle card">
+    <mat-button-toggle-group [(ngModel)]="viewMode" (change)="onViewChange()">
+      <mat-button-toggle value="list">
+        <mat-icon>list</mat-icon> 列表
+      </mat-button-toggle>
+      <mat-button-toggle value="persona">
+        <mat-icon>category</mat-icon> 按画像分组
+      </mat-button-toggle>
+      <mat-button-toggle value="stats">
+        <mat-icon>bar_chart</mat-icon> 统计
+      </mat-button-toggle>
+    </mat-button-toggle-group>
+
+    <!-- Filters (for list view) -->
+    <div class="inline-filters" *ngIf="viewMode === 'list'">
+      <mat-form-field appearance="outline" class="search-field">
+        <mat-label>搜索</mat-label>
+        <mat-icon matPrefix>search</mat-icon>
+        <input matInput [(ngModel)]="searchText" (ngModelChange)="applyFilters()">
+      </mat-form-field>
+      <mat-form-field appearance="outline">
+        <mat-label>画像</mat-label>
+        <mat-select [(ngModel)]="filterPersona" (selectionChange)="applyFilters()">
+          <mat-option value="">全部</mat-option>
+          <mat-option *ngFor="let p of personaList" [value]="p.id">{{ p.id }}-{{ p.name }}</mat-option>
+        </mat-select>
+      </mat-form-field>
+      <mat-form-field appearance="outline">
+        <mat-label>等级</mat-label>
+        <mat-select [(ngModel)]="filterGrade" (selectionChange)="applyFilters()">
+          <mat-option value="">全部</mat-option>
+          <mat-option value="S">S级</mat-option>
+          <mat-option value="A">A级</mat-option>
+          <mat-option value="B">B级</mat-option>
+          <mat-option value="C">C级</mat-option>
+        </mat-select>
+      </mat-form-field>
+    </div>
+  </div>
+
+  <!-- ===== LIST VIEW ===== -->
+  <div class="table-card card" *ngIf="viewMode === 'list'">
+    <table class="lead-table">
+      <thead>
+        <tr>
+          <th>公司名称</th>
+          <th>画像</th>
+          <th>等级</th>
+          <th>联系人</th>
+          <th>国家</th>
+          <th>跟进阶段</th>
+          <th>下次跟进</th>
+          <th>验证</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr *ngFor="let lead of filteredLeads" (click)="openLead(lead)" class="clickable-row">
+          <td class="company-cell">
+            <span class="company-name">{{ lead.companyName }}</span>
+          </td>
+          <td>
+            <span class="persona-badge" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
+              <mat-icon class="badge-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
+              {{ lead.persona }}
+            </span>
+          </td>
+          <td>
+            <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">
+              {{ lead.valueGrade }}级
+            </span>
+          </td>
+          <td>{{ lead.contactName }}</td>
+          <td>{{ getFlag(lead.country) }} {{ lead.country }}</td>
+          <td>
+            <span class="stage-badge" [class]="'stage-' + lead.followUpStage">
+              {{ getStageLabel(lead.followUpStage) }}
+            </span>
+          </td>
+          <td [class.overdue]="isOverdue(lead.nextFollowUpDate)">
+            {{ lead.nextFollowUpDate | date:'MM/dd' }}
+          </td>
+          <td>
+            <mat-icon class="verify-icon" [class.verified]="lead.personaVerified">
+              {{ lead.personaVerified ? 'verified' : 'pending' }}
+            </mat-icon>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <div class="table-footer">
+      共 {{ filteredLeads.length }} 条线索
+    </div>
+  </div>
+
+  <!-- ===== PERSONA GROUP VIEW ===== -->
+  <div class="persona-groups" *ngIf="viewMode === 'persona'">
+    <div class="persona-group" *ngFor="let group of personaGroups"
+         [class.collapsed]="group.collapsed"
+         [class.low-quality]="group.persona === 'P6'">
+      <div class="group-header" (click)="group.collapsed = !group.collapsed"
+           [style.border-left-color]="getPersonaColor(group.persona)">
+        <mat-icon class="expand-icon">{{ group.collapsed ? 'chevron_right' : 'expand_more' }}</mat-icon>
+        <div class="group-avatar" [style.background]="getPersonaColor(group.persona)">
+          <mat-icon>{{ getPersonaIcon(group.persona) }}</mat-icon>
+        </div>
+        <div class="group-info">
+          <span class="group-name">{{ group.persona }}-{{ group.name }}</span>
+          <span class="group-strategy">{{ group.strategy }}</span>
+        </div>
+        <div class="group-stats">
+          <span class="group-count">{{ group.leads.length }} 条</span>
+        </div>
+      </div>
+
+      <div class="group-body" *ngIf="!group.collapsed">
+        <div class="group-lead" *ngFor="let lead of group.leads" (click)="openLead(lead)">
+          <span class="gl-company">{{ lead.companyName }}</span>
+          <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">{{ lead.valueGrade }}级</span>
+          <span class="stage-badge" [class]="'stage-' + lead.followUpStage">{{ getStageLabel(lead.followUpStage) }}</span>
+          <span class="gl-date" [class.overdue]="isOverdue(lead.nextFollowUpDate)">{{ lead.nextFollowUpDate | date:'MM/dd' }}</span>
+          <span class="gl-flag">{{ getFlag(lead.country) }}</span>
+          <mat-icon class="verify-icon sm" [class.verified]="lead.personaVerified">
+            {{ lead.personaVerified ? 'verified' : 'pending' }}
+          </mat-icon>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- ===== STATS VIEW ===== -->
+  <div class="stats-view" *ngIf="viewMode === 'stats'">
+    <div class="stats-grid">
+      <!-- By Persona -->
+      <div class="stats-card card">
+        <h3><mat-icon>category</mat-icon> 按画像分布</h3>
+        <div class="chart-bars">
+          <div class="chart-row" *ngFor="let s of personaStats">
+            <span class="chart-label">{{ s.id }}-{{ s.name }}</span>
+            <div class="chart-bar-wrap">
+              <div class="chart-bar" [style.width.%]="s.percent" [style.background]="s.color"></div>
+            </div>
+            <span class="chart-num">{{ s.count }} ({{ s.percent }}%)</span>
+          </div>
+        </div>
+      </div>
+      <!-- By Grade -->
+      <div class="stats-card card">
+        <h3><mat-icon>grade</mat-icon> 按等级分布</h3>
+        <div class="chart-bars">
+          <div class="chart-row" *ngFor="let g of gradeStats">
+            <span class="chart-label">{{ g.grade }}级</span>
+            <div class="chart-bar-wrap">
+              <div class="chart-bar" [style.width.%]="g.percent" [style.background]="g.color"></div>
+            </div>
+            <span class="chart-num">{{ g.count }} (${{ g.value | number:'1.0-0' }})</span>
+          </div>
+        </div>
+      </div>
+      <!-- By Stage -->
+      <div class="stats-card card">
+        <h3><mat-icon>linear_scale</mat-icon> 按跟进阶段</h3>
+        <div class="chart-bars">
+          <div class="chart-row" *ngFor="let st of stageStats">
+            <span class="chart-label">{{ st.label }}</span>
+            <div class="chart-bar-wrap">
+              <div class="chart-bar" [style.width.%]="st.percent" [style.background]="st.color"></div>
+            </div>
+            <span class="chart-num">{{ st.count }}</span>
+          </div>
+        </div>
+      </div>
+      <!-- Summary -->
+      <div class="stats-card card summary-card">
+        <h3><mat-icon>insights</mat-icon> 汇总</h3>
+        <div class="summary-items">
+          <div class="summary-row"><span>总线索数</span><strong>{{ leads.length }}</strong></div>
+          <div class="summary-row"><span>已验证</span><strong>{{ leads.filter(verifiedFilter).length }}</strong></div>
+          <div class="summary-row"><span>待验证</span><strong>{{ leads.filter(pendingFilter).length }}</strong></div>
+          <div class="summary-row"><span>S级线索</span><strong>{{ leads.filter(sGradeFilter).length }}</strong></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+

+ 140 - 0
lead-discovery/src/app/pages/lead-list/lead-list.component.scss

@@ -0,0 +1,140 @@
+.page-header h1 {
+  display: flex; align-items: center; gap: 8px;
+  font-size: 24px; font-weight: 600; margin: 0;
+}
+.page-header h1 mat-icon { color: #1976d2; }
+.subtitle { font-size: 14px; color: #666; margin: 4px 0 0; }
+
+.view-toggle {
+  display: flex; align-items: center; gap: 16px;
+  padding: 12px 16px; margin: 20px 0 16px; flex-wrap: wrap;
+}
+.inline-filters { display: flex; gap: 12px; flex: 1; align-items: flex-start; }
+.search-field { flex: 1; }
+
+/* Table */
+.table-card { overflow-x: auto; }
+.lead-table { width: 100%; border-collapse: collapse; }
+.lead-table th {
+  text-align: left; padding: 12px 16px; font-size: 12px; font-weight: 600;
+  color: #888; text-transform: uppercase; letter-spacing: 0.5px;
+  border-bottom: 2px solid #eee; background: #fafafa;
+}
+.lead-table td {
+  padding: 12px 16px; font-size: 13px;
+  border-bottom: 1px solid #f5f5f5; color: #444;
+}
+.clickable-row { cursor: pointer; transition: background 0.15s; }
+.clickable-row:hover { background: #f8f9fa; }
+.company-cell .company-name { font-weight: 600; color: #1a1a2e; }
+.amount-cell { font-weight: 600; color: #2e7d32; }
+
+.persona-badge {
+  display: inline-flex; align-items: center; gap: 4px;
+  padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600;
+}
+.badge-icon { font-size: 14px; width: 14px; height: 14px; }
+.badge {
+  display: inline-block; padding: 2px 8px; border-radius: 10px;
+  font-size: 11px; font-weight: 600;
+}
+.grade-s { background: #ffebee; color: #c62828; }
+.grade-a { background: #fff3e0; color: #e65100; }
+.grade-b { background: #fffde7; color: #f9a825; }
+.grade-c { background: #fafafa; color: #757575; }
+
+.stage-badge {
+  font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500;
+}
+.stage-screened { background: #e3f2fd; color: #1565c0; }
+.stage-contacted { background: #e8f5e9; color: #2e7d32; }
+.stage-replied { background: #fff3e0; color: #e65100; }
+.stage-sample { background: #f3e5f5; color: #6a1b9a; }
+.stage-quote { background: #e1f5fe; color: #0277bd; }
+.stage-order { background: #e8f5e9; color: #1b5e20; }
+.overdue { color: #c62828; font-weight: 600; }
+
+.verify-icon { font-size: 18px; width: 18px; height: 18px; color: #ccc; }
+.verify-icon.verified { color: #2e7d32; }
+.verify-icon.sm { font-size: 16px; width: 16px; height: 16px; }
+
+.table-footer {
+  padding: 12px 16px; font-size: 12px; color: #888; border-top: 1px solid #eee;
+}
+
+/* Persona Group View */
+.persona-groups { display: flex; flex-direction: column; gap: 12px; }
+.persona-group {
+  background: #fff; border-radius: 12px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
+  overflow: hidden;
+}
+.persona-group.low-quality { opacity: 0.7; }
+.group-header {
+  display: flex; align-items: center; gap: 12px;
+  padding: 14px 20px; cursor: pointer;
+  border-left: 4px solid #ccc;
+  transition: background 0.15s;
+}
+.group-header:hover { background: #f8f9fa; }
+.expand-icon { color: #999; font-size: 20px; width: 20px; height: 20px; }
+.group-avatar {
+  width: 36px; height: 36px; border-radius: 10px;
+  display: flex; align-items: center; justify-content: center;
+  flex-shrink: 0;
+}
+.group-avatar mat-icon { font-size: 18px; width: 18px; height: 18px; color: #fff; }
+.group-info { flex: 1; }
+.group-name { display: block; font-size: 14px; font-weight: 600; color: #1a1a2e; }
+.group-strategy { font-size: 12px; color: #888; }
+.group-stats { display: flex; gap: 16px; align-items: center; }
+.group-count {
+  font-size: 12px; padding: 2px 10px; background: #f0f0f0;
+  border-radius: 10px; font-weight: 600; color: #555;
+}
+.group-value { font-size: 14px; font-weight: 700; color: #2e7d32; }
+
+.group-body { padding: 0 20px 12px 20px; }
+.group-lead {
+  display: flex; align-items: center; gap: 12px;
+  padding: 10px 14px; border-bottom: 1px solid #f5f5f5;
+  cursor: pointer; transition: background 0.15s;
+}
+.group-lead:hover { background: #f8f9fa; }
+.group-lead:last-child { border-bottom: none; }
+.gl-company { flex: 1; font-size: 13px; font-weight: 600; color: #333; }
+.gl-value { font-size: 13px; font-weight: 600; color: #2e7d32; min-width: 70px; text-align: right; }
+.gl-date { font-size: 12px; color: #888; min-width: 40px; }
+.gl-flag { font-size: 14px; }
+
+/* Stats View */
+.stats-grid {
+  display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
+}
+.stats-card { padding: 20px; }
+.stats-card h3 {
+  display: flex; align-items: center; gap: 8px;
+  font-size: 16px; margin: 0 0 16px;
+}
+.stats-card h3 mat-icon { color: #1976d2; }
+.chart-bars { display: flex; flex-direction: column; gap: 10px; }
+.chart-row { display: flex; align-items: center; gap: 10px; }
+.chart-label { font-size: 13px; min-width: 90px; color: #555; }
+.chart-bar-wrap {
+  flex: 1; height: 10px; background: #f0f0f0; border-radius: 5px; overflow: hidden;
+}
+.chart-bar { height: 100%; border-radius: 5px; transition: width 0.5s; }
+.chart-num { font-size: 12px; color: #888; min-width: 80px; text-align: right; }
+
+.summary-items { display: flex; flex-direction: column; gap: 12px; }
+.summary-row {
+  display: flex; justify-content: space-between; align-items: center;
+  padding: 8px 0; border-bottom: 1px solid #f5f5f5;
+  font-size: 14px; color: #555;
+}
+.summary-row strong { color: #1a1a2e; font-size: 16px; }
+
+@media (max-width: 900px) {
+  .stats-grid { grid-template-columns: 1fr; }
+}
+

+ 2 - 334
lead-discovery/src/app/pages/lead-list/lead-list.component.ts

@@ -25,340 +25,8 @@ import {
         MatChipsModule, MatFormFieldModule, MatInputModule, MatSelectModule,
         MatChipsModule, MatFormFieldModule, MatInputModule, MatSelectModule,
         MatButtonToggleModule,
         MatButtonToggleModule,
     ],
     ],
-    template: `
-    <div class="page-container">
-      <div class="page-header">
-        <h1><mat-icon>people</mat-icon> 线索管理</h1>
-        <p class="subtitle">所有线索的多维度视图,支持按画像分组管理</p>
-      </div>
-
-      <!-- View Toggle -->
-      <div class="view-toggle card">
-        <mat-button-toggle-group [(ngModel)]="viewMode" (change)="onViewChange()">
-          <mat-button-toggle value="list">
-            <mat-icon>list</mat-icon> 列表
-          </mat-button-toggle>
-          <mat-button-toggle value="persona">
-            <mat-icon>category</mat-icon> 按画像分组
-          </mat-button-toggle>
-          <mat-button-toggle value="stats">
-            <mat-icon>bar_chart</mat-icon> 统计
-          </mat-button-toggle>
-        </mat-button-toggle-group>
-
-        <!-- Filters (for list view) -->
-        <div class="inline-filters" *ngIf="viewMode === 'list'">
-          <mat-form-field appearance="outline" class="search-field">
-            <mat-label>搜索</mat-label>
-            <mat-icon matPrefix>search</mat-icon>
-            <input matInput [(ngModel)]="searchText" (ngModelChange)="applyFilters()">
-          </mat-form-field>
-          <mat-form-field appearance="outline">
-            <mat-label>画像</mat-label>
-            <mat-select [(ngModel)]="filterPersona" (selectionChange)="applyFilters()">
-              <mat-option value="">全部</mat-option>
-              <mat-option *ngFor="let p of personaList" [value]="p.id">{{ p.id }}-{{ p.name }}</mat-option>
-            </mat-select>
-          </mat-form-field>
-          <mat-form-field appearance="outline">
-            <mat-label>等级</mat-label>
-            <mat-select [(ngModel)]="filterGrade" (selectionChange)="applyFilters()">
-              <mat-option value="">全部</mat-option>
-              <mat-option value="S">S级</mat-option>
-              <mat-option value="A">A级</mat-option>
-              <mat-option value="B">B级</mat-option>
-              <mat-option value="C">C级</mat-option>
-            </mat-select>
-          </mat-form-field>
-        </div>
-      </div>
-
-      <!-- ===== LIST VIEW ===== -->
-      <div class="table-card card" *ngIf="viewMode === 'list'">
-        <table class="lead-table">
-          <thead>
-            <tr>
-              <th>公司名称</th>
-              <th>画像</th>
-              <th>等级</th>
-              <th>联系人</th>
-              <th>国家</th>
-              <th>跟进阶段</th>
-              <th>下次跟进</th>
-              <th>验证</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr *ngFor="let lead of filteredLeads" (click)="openLead(lead)" class="clickable-row">
-              <td class="company-cell">
-                <span class="company-name">{{ lead.companyName }}</span>
-              </td>
-              <td>
-                <span class="persona-badge" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
-                  <mat-icon class="badge-icon">{{ getPersonaIcon(lead.persona) }}</mat-icon>
-                  {{ lead.persona }}
-                </span>
-              </td>
-              <td>
-                <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">
-                  {{ lead.valueGrade }}级
-                </span>
-              </td>
-              <td>{{ lead.contactName }}</td>
-              <td>{{ getFlag(lead.country) }} {{ lead.country }}</td>
-              <td>
-                <span class="stage-badge" [class]="'stage-' + lead.followUpStage">
-                  {{ getStageLabel(lead.followUpStage) }}
-                </span>
-              </td>
-              <td [class.overdue]="isOverdue(lead.nextFollowUpDate)">
-                {{ lead.nextFollowUpDate | date:'MM/dd' }}
-              </td>
-              <td>
-                <mat-icon class="verify-icon" [class.verified]="lead.personaVerified">
-                  {{ lead.personaVerified ? 'verified' : 'pending' }}
-                </mat-icon>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-        <div class="table-footer">
-          共 {{ filteredLeads.length }} 条线索
-        </div>
-      </div>
-
-      <!-- ===== PERSONA GROUP VIEW ===== -->
-      <div class="persona-groups" *ngIf="viewMode === 'persona'">
-        <div class="persona-group" *ngFor="let group of personaGroups"
-             [class.collapsed]="group.collapsed"
-             [class.low-quality]="group.persona === 'P6'">
-          <div class="group-header" (click)="group.collapsed = !group.collapsed"
-               [style.border-left-color]="getPersonaColor(group.persona)">
-            <mat-icon class="expand-icon">{{ group.collapsed ? 'chevron_right' : 'expand_more' }}</mat-icon>
-            <div class="group-avatar" [style.background]="getPersonaColor(group.persona)">
-              <mat-icon>{{ getPersonaIcon(group.persona) }}</mat-icon>
-            </div>
-            <div class="group-info">
-              <span class="group-name">{{ group.persona }}-{{ group.name }}</span>
-              <span class="group-strategy">{{ group.strategy }}</span>
-            </div>
-            <div class="group-stats">
-              <span class="group-count">{{ group.leads.length }} 条</span>
-            </div>
-          </div>
-
-          <div class="group-body" *ngIf="!group.collapsed">
-            <div class="group-lead" *ngFor="let lead of group.leads" (click)="openLead(lead)">
-              <span class="gl-company">{{ lead.companyName }}</span>
-              <span class="badge" [class]="'grade-' + lead.valueGrade.toLowerCase()">{{ lead.valueGrade }}级</span>
-              <span class="stage-badge" [class]="'stage-' + lead.followUpStage">{{ getStageLabel(lead.followUpStage) }}</span>
-              <span class="gl-date" [class.overdue]="isOverdue(lead.nextFollowUpDate)">{{ lead.nextFollowUpDate | date:'MM/dd' }}</span>
-              <span class="gl-flag">{{ getFlag(lead.country) }}</span>
-              <mat-icon class="verify-icon sm" [class.verified]="lead.personaVerified">
-                {{ lead.personaVerified ? 'verified' : 'pending' }}
-              </mat-icon>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- ===== STATS VIEW ===== -->
-      <div class="stats-view" *ngIf="viewMode === 'stats'">
-        <div class="stats-grid">
-          <!-- By Persona -->
-          <div class="stats-card card">
-            <h3><mat-icon>category</mat-icon> 按画像分布</h3>
-            <div class="chart-bars">
-              <div class="chart-row" *ngFor="let s of personaStats">
-                <span class="chart-label">{{ s.id }}-{{ s.name }}</span>
-                <div class="chart-bar-wrap">
-                  <div class="chart-bar" [style.width.%]="s.percent" [style.background]="s.color"></div>
-                </div>
-                <span class="chart-num">{{ s.count }} ({{ s.percent }}%)</span>
-              </div>
-            </div>
-          </div>
-          <!-- By Grade -->
-          <div class="stats-card card">
-            <h3><mat-icon>grade</mat-icon> 按等级分布</h3>
-            <div class="chart-bars">
-              <div class="chart-row" *ngFor="let g of gradeStats">
-                <span class="chart-label">{{ g.grade }}级</span>
-                <div class="chart-bar-wrap">
-                  <div class="chart-bar" [style.width.%]="g.percent" [style.background]="g.color"></div>
-                </div>
-                <span class="chart-num">{{ g.count }} (\${{ g.value | number:'1.0-0' }})</span>
-              </div>
-            </div>
-          </div>
-          <!-- By Stage -->
-          <div class="stats-card card">
-            <h3><mat-icon>linear_scale</mat-icon> 按跟进阶段</h3>
-            <div class="chart-bars">
-              <div class="chart-row" *ngFor="let st of stageStats">
-                <span class="chart-label">{{ st.label }}</span>
-                <div class="chart-bar-wrap">
-                  <div class="chart-bar" [style.width.%]="st.percent" [style.background]="st.color"></div>
-                </div>
-                <span class="chart-num">{{ st.count }}</span>
-              </div>
-            </div>
-          </div>
-          <!-- Summary -->
-          <div class="stats-card card summary-card">
-            <h3><mat-icon>insights</mat-icon> 汇总</h3>
-            <div class="summary-items">
-              <div class="summary-row"><span>总线索数</span><strong>{{ leads.length }}</strong></div>
-              <div class="summary-row"><span>已验证</span><strong>{{ leads.filter(verifiedFilter).length }}</strong></div>
-              <div class="summary-row"><span>待验证</span><strong>{{ leads.filter(pendingFilter).length }}</strong></div>
-              <div class="summary-row"><span>S级线索</span><strong>{{ leads.filter(sGradeFilter).length }}</strong></div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  `,
-    styles: [`
-    .page-header h1 {
-      display: flex; align-items: center; gap: 8px;
-      font-size: 24px; font-weight: 600; margin: 0;
-    }
-    .page-header h1 mat-icon { color: #1976d2; }
-    .subtitle { font-size: 14px; color: #666; margin: 4px 0 0; }
-
-    .view-toggle {
-      display: flex; align-items: center; gap: 16px;
-      padding: 12px 16px; margin: 20px 0 16px; flex-wrap: wrap;
-    }
-    .inline-filters { display: flex; gap: 12px; flex: 1; align-items: flex-start; }
-    .search-field { flex: 1; }
-
-    /* Table */
-    .table-card { overflow-x: auto; }
-    .lead-table { width: 100%; border-collapse: collapse; }
-    .lead-table th {
-      text-align: left; padding: 12px 16px; font-size: 12px; font-weight: 600;
-      color: #888; text-transform: uppercase; letter-spacing: 0.5px;
-      border-bottom: 2px solid #eee; background: #fafafa;
-    }
-    .lead-table td {
-      padding: 12px 16px; font-size: 13px;
-      border-bottom: 1px solid #f5f5f5; color: #444;
-    }
-    .clickable-row { cursor: pointer; transition: background 0.15s; }
-    .clickable-row:hover { background: #f8f9fa; }
-    .company-cell .company-name { font-weight: 600; color: #1a1a2e; }
-    .amount-cell { font-weight: 600; color: #2e7d32; }
-
-    .persona-badge {
-      display: inline-flex; align-items: center; gap: 4px;
-      padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 600;
-    }
-    .badge-icon { font-size: 14px; width: 14px; height: 14px; }
-    .badge {
-      display: inline-block; padding: 2px 8px; border-radius: 10px;
-      font-size: 11px; font-weight: 600;
-    }
-    .grade-s { background: #ffebee; color: #c62828; }
-    .grade-a { background: #fff3e0; color: #e65100; }
-    .grade-b { background: #fffde7; color: #f9a825; }
-    .grade-c { background: #fafafa; color: #757575; }
-
-    .stage-badge {
-      font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500;
-    }
-    .stage-screened { background: #e3f2fd; color: #1565c0; }
-    .stage-contacted { background: #e8f5e9; color: #2e7d32; }
-    .stage-replied { background: #fff3e0; color: #e65100; }
-    .stage-sample { background: #f3e5f5; color: #6a1b9a; }
-    .stage-quote { background: #e1f5fe; color: #0277bd; }
-    .stage-order { background: #e8f5e9; color: #1b5e20; }
-    .overdue { color: #c62828; font-weight: 600; }
-
-    .verify-icon { font-size: 18px; width: 18px; height: 18px; color: #ccc; }
-    .verify-icon.verified { color: #2e7d32; }
-    .verify-icon.sm { font-size: 16px; width: 16px; height: 16px; }
-
-    .table-footer {
-      padding: 12px 16px; font-size: 12px; color: #888; border-top: 1px solid #eee;
-    }
-
-    /* Persona Group View */
-    .persona-groups { display: flex; flex-direction: column; gap: 12px; }
-    .persona-group {
-      background: #fff; border-radius: 12px;
-      box-shadow: 0 1px 4px rgba(0,0,0,0.06);
-      overflow: hidden;
-    }
-    .persona-group.low-quality { opacity: 0.7; }
-    .group-header {
-      display: flex; align-items: center; gap: 12px;
-      padding: 14px 20px; cursor: pointer;
-      border-left: 4px solid #ccc;
-      transition: background 0.15s;
-    }
-    .group-header:hover { background: #f8f9fa; }
-    .expand-icon { color: #999; font-size: 20px; width: 20px; height: 20px; }
-    .group-avatar {
-      width: 36px; height: 36px; border-radius: 10px;
-      display: flex; align-items: center; justify-content: center;
-      flex-shrink: 0;
-    }
-    .group-avatar mat-icon { font-size: 18px; width: 18px; height: 18px; color: #fff; }
-    .group-info { flex: 1; }
-    .group-name { display: block; font-size: 14px; font-weight: 600; color: #1a1a2e; }
-    .group-strategy { font-size: 12px; color: #888; }
-    .group-stats { display: flex; gap: 16px; align-items: center; }
-    .group-count {
-      font-size: 12px; padding: 2px 10px; background: #f0f0f0;
-      border-radius: 10px; font-weight: 600; color: #555;
-    }
-    .group-value { font-size: 14px; font-weight: 700; color: #2e7d32; }
-
-    .group-body { padding: 0 20px 12px 20px; }
-    .group-lead {
-      display: flex; align-items: center; gap: 12px;
-      padding: 10px 14px; border-bottom: 1px solid #f5f5f5;
-      cursor: pointer; transition: background 0.15s;
-    }
-    .group-lead:hover { background: #f8f9fa; }
-    .group-lead:last-child { border-bottom: none; }
-    .gl-company { flex: 1; font-size: 13px; font-weight: 600; color: #333; }
-    .gl-value { font-size: 13px; font-weight: 600; color: #2e7d32; min-width: 70px; text-align: right; }
-    .gl-date { font-size: 12px; color: #888; min-width: 40px; }
-    .gl-flag { font-size: 14px; }
-
-    /* Stats View */
-    .stats-grid {
-      display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
-    }
-    .stats-card { padding: 20px; }
-    .stats-card h3 {
-      display: flex; align-items: center; gap: 8px;
-      font-size: 16px; margin: 0 0 16px;
-    }
-    .stats-card h3 mat-icon { color: #1976d2; }
-    .chart-bars { display: flex; flex-direction: column; gap: 10px; }
-    .chart-row { display: flex; align-items: center; gap: 10px; }
-    .chart-label { font-size: 13px; min-width: 90px; color: #555; }
-    .chart-bar-wrap {
-      flex: 1; height: 10px; background: #f0f0f0; border-radius: 5px; overflow: hidden;
-    }
-    .chart-bar { height: 100%; border-radius: 5px; transition: width 0.5s; }
-    .chart-num { font-size: 12px; color: #888; min-width: 80px; text-align: right; }
-
-    .summary-items { display: flex; flex-direction: column; gap: 12px; }
-    .summary-row {
-      display: flex; justify-content: space-between; align-items: center;
-      padding: 8px 0; border-bottom: 1px solid #f5f5f5;
-      font-size: 14px; color: #555;
-    }
-    .summary-row strong { color: #1a1a2e; font-size: 16px; }
-
-    @media (max-width: 900px) {
-      .stats-grid { grid-template-columns: 1fr; }
-    }
-  `]
+    templateUrl: './lead-list.component.html',
+    styleUrls: ['./lead-list.component.scss'],
 })
 })
 export class LeadListComponent implements OnInit {
 export class LeadListComponent implements OnInit {
   leads: Lead[] = [];
   leads: Lead[] = [];

+ 7 - 0
lead-discovery/src/app/pages/list/list.component.html

@@ -190,6 +190,7 @@
           <th>跟进阶段</th>
           <th>跟进阶段</th>
           <th>下次跟进</th>
           <th>下次跟进</th>
           <th>验证</th>
           <th>验证</th>
+          <th>操作</th>
         </tr>
         </tr>
       </thead>
       </thead>
       <tbody>
       <tbody>
@@ -223,6 +224,11 @@
               {{ lead.personaVerified ? 'verified' : 'pending' }}
               {{ lead.personaVerified ? 'verified' : 'pending' }}
             </mat-icon>
             </mat-icon>
           </td>
           </td>
+          <td>
+            <button class="table-detail-btn" (click)="openLead(lead); $event.stopPropagation()" matTooltip="查看详情">
+              <mat-icon>visibility</mat-icon> 详情
+            </button>
+          </td>
         </tr>
         </tr>
       </tbody>
       </tbody>
     </table>
     </table>
@@ -286,4 +292,5 @@
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
+
 </div>
 </div>

+ 19 - 0
lead-discovery/src/app/pages/list/list.component.scss

@@ -262,6 +262,25 @@
 }
 }
 .summary-row strong { color: #1a1a2e; font-size: 16px; }
 .summary-row strong { color: #1a1a2e; font-size: 16px; }
 
 
+/* ===== Table Detail Button ===== */
+.table-detail-btn {
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  padding: 4px 12px;
+  border: 1px solid #1976d2;
+  border-radius: 6px;
+  background: #fff;
+  color: #1976d2;
+  font-size: 12px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.2s;
+  white-space: nowrap;
+}
+.table-detail-btn mat-icon { font-size: 16px; width: 16px; height: 16px; }
+.table-detail-btn:hover { background: #1976d2; color: #fff; }
+
 /* ===== Responsive ===== */
 /* ===== Responsive ===== */
 @media (max-width: 1100px) {
 @media (max-width: 1100px) {
   .kanban-board > .kanban-column { min-width: 240px; }
   .kanban-board > .kanban-column { min-width: 240px; }

+ 15 - 14
lead-discovery/src/app/pages/list/list.component.ts

@@ -54,12 +54,12 @@ export class ListComponent implements OnInit {
   personaList = PERSONA_LIST;
   personaList = PERSONA_LIST;
 
 
   stageColumns = [
   stageColumns = [
-    { key: 'screened', label: '已筛选', color: '#1976d2' },
-    { key: 'contacted', label: '已触达', color: '#43a047' },
-    { key: 'replied', label: '已回复', color: '#ef6c00' },
-    { key: 'sample', label: '寄样中', color: '#8e24aa' },
-    { key: 'quote', label: '报价中', color: '#0277bd' },
-    { key: 'order', label: '已下单', color: '#1b5e20' },
+    { key: 'screened', label: '待验证', color: '#1976d2' },
+    { key: 'contacted', label: '已背调', color: '#43a047' },
+    { key: 'replied', label: '已联系', color: '#ef6c00' },
+    { key: 'sample', label: '已报价', color: '#8e24aa' },
+    { key: 'quote', label: '谈判中', color: '#0277bd' },
+    { key: 'order', label: '已成交', color: '#1b5e20' },
   ];
   ];
 
 
   // ===== Stats View =====
   // ===== Stats View =====
@@ -155,12 +155,12 @@ export class ListComponent implements OnInit {
     });
     });
 
 
     const stageConfig = [
     const stageConfig = [
-      { key: 'screened', label: '已筛选', color: '#1976d2' },
-      { key: 'contacted', label: '已触达', color: '#43a047' },
-      { key: 'replied', label: '已回复', color: '#ef6c00' },
-      { key: 'sample', label: '寄样中', color: '#8e24aa' },
-      { key: 'quote', label: '报价中', color: '#0277bd' },
-      { key: 'order', label: '已下单', color: '#1b5e20' },
+      { key: 'screened', label: '待验证', color: '#1976d2' },
+      { key: 'contacted', label: '已背调', color: '#43a047' },
+      { key: 'replied', label: '已联系', color: '#ef6c00' },
+      { key: 'sample', label: '已报价', color: '#8e24aa' },
+      { key: 'quote', label: '谈判中', color: '#0277bd' },
+      { key: 'order', label: '已成交', color: '#1b5e20' },
     ];
     ];
     this.stageStats = stageConfig.map(s => {
     this.stageStats = stageConfig.map(s => {
       const count = this.leads.filter(l => l.followUpStage === s.key).length;
       const count = this.leads.filter(l => l.followUpStage === s.key).length;
@@ -176,8 +176,8 @@ export class ListComponent implements OnInit {
 
 
   getStageLabel(stage: string): string {
   getStageLabel(stage: string): string {
     const labels: Record<string, string> = {
     const labels: Record<string, string> = {
-      screened: '已筛选', contacted: '已触达', replied: '已回复',
-      sample: '寄样中', quote: '报价中', order: '已下单',
+      screened: '待验证', contacted: '已背调', replied: '已联系',
+      sample: '已报价', quote: '谈判中', order: '已成交',
     };
     };
     return labels[stage] || stage;
     return labels[stage] || stage;
   }
   }
@@ -195,4 +195,5 @@ export class ListComponent implements OnInit {
   getPersonaIcon(p: CustomerPersona): string { return PERSONA_DEFINITIONS[p]?.icon || 'business'; }
   getPersonaIcon(p: CustomerPersona): string { return PERSONA_DEFINITIONS[p]?.icon || 'business'; }
 
 
   isOverdue(date: Date): boolean { return new Date(date) < new Date(); }
   isOverdue(date: Date): boolean { return new Date(date) < new Date(); }
+
 }
 }

+ 557 - 0
lead-discovery/src/app/services/ai-screen.service.ts

@@ -0,0 +1,557 @@
+/**
+ * AI 驱动的快速筛查服务(参考 nanchi AgentValueAnalysisService)
+ *
+ * 流程:
+ * 1. 从邮箱/官网提取域名
+ * 2. Google Search + Firecrawl 抓取公司真实数据(快速背调)
+ * 3. 将线索信息 + 背调数据喂给 AI 模型 → 画像分类 + 五维评分 + 决策建议
+ *
+ * 五维度:需求匹配度 | 商机规模 | 采购紧迫性 | 可信度 | 风险项
+ */
+import { Injectable } from '@angular/core';
+import { completionJSON } from 'fmode-ng/core';
+import { GenerateApiService, FirecrawlScrapeData } from './generate-api.service';
+import {
+  QuickScreenResult, CustomerCategory, CustomerPersona, ValueGrade,
+  ProductRecommendation, PERSONA_DEFINITIONS,
+} from '../models/lead.model';
+import { MockDataService } from './mock-data.service';
+
+export interface AiScreenProgress {
+  progress: number;
+  message: string;
+}
+
+export interface QuickBgCheckSummary {
+  companyVerified: boolean;
+  companyProfile: string;
+  riskLevel: 'low' | 'medium' | 'high';
+  country: string;
+  industry: string;
+  employeeScale: string;
+  foundedYear: string;
+  certifications: string[];
+  dataSources: Array<{ url: string; title: string }>;
+}
+
+interface AiClassifyResult {
+  persona: string;
+  personaLabel: string;
+  personaConfidence: number;
+  valueGrade: string;
+  customerCategory: string;
+  mainBusiness: string;
+  country: string;
+  estimatedScale: string;
+  productRelevance: string;
+  matchedKeywords: string[];
+  certRequirements: string[];
+  recommendedAction: string;
+  matchedProductSKUs: string[];
+  matchedProductReasons: Record<string, string>;
+  scores: {
+    demandClarity: number;
+    credibility: number;
+    businessScale: number;
+    productFit: number;
+    urgency: number;
+  };
+  demandSnapshot: {
+    products: Array<{ name: string; quantity: number; unit: string }>;
+    estimatedAnnualValue: number;
+    certifications: string[];
+  };
+  credibilityDetails: {
+    level: string;
+    signals: string[];
+  };
+  recommendedActions: {
+    timeframe: string;
+    filesToSend: string[];
+    keyPoints: string[];
+  };
+  socialLinks: {
+    linkedin?: string;
+    website?: string;
+  };
+  reportSummary: string;
+}
+
+const FREE_EMAIL_PROVIDERS = [
+  'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
+  'qq.com', '163.com', '126.com', 'foxmail.com', 'mail.com',
+];
+
+@Injectable({ providedIn: 'root' })
+export class AiScreenService {
+
+  private readonly MODEL_NAME = 'fmode-1.6-cn';
+
+  constructor(
+    private generateApi: GenerateApiService,
+    private mockData: MockDataService,
+  ) {}
+
+  /**
+   * AI 快速筛查(完整流程)
+   */
+  async screenWithAI(
+    companyName: string,
+    emailAddress: string,
+    emailContent = '',
+    emailSubject = '',
+    onProgress?: (p: AiScreenProgress) => void,
+  ): Promise<QuickScreenResult> {
+    const start = Date.now();
+    const domain = this.extractDomain(emailAddress, '');
+
+    // Phase 1: 快速背调
+    let bgSummary: QuickBgCheckSummary | undefined;
+    let bgContext = '';
+
+    try {
+      onProgress?.({ progress: 5, message: '🔍 联网搜索公司信息...' });
+      const bg = await this.quickBackgroundCheck(domain, companyName, onProgress);
+      bgSummary = bg.summary;
+      bgContext = bg.contextForAI;
+    } catch (err) {
+      console.warn('[AiScreen] 快速背调失败,仅用线索信息:', err);
+    }
+
+    // Phase 2: AI 分类(使用 fmode-ng completionJSON)
+    onProgress?.({ progress: 55, message: '🤖 AI 正在分析线索价值...' });
+
+    const prompt = this.buildClassifyPrompt(companyName, emailAddress, domain, emailContent, emailSubject, bgContext);
+    const schema = this.buildSchema();
+
+    let aiResult: AiClassifyResult;
+    try {
+      const raw = await completionJSON(
+        prompt,
+        schema,
+        (_partialContent) => {
+          onProgress?.({ progress: 70, message: '🤖 AI 正在生成分析报告...' });
+        }
+      );
+      aiResult = raw as AiClassifyResult;
+    } catch (err) {
+      console.warn('[AiScreen] AI 模型调用失败,降级为规则引擎:', err);
+      return this.fallbackClassify(domain, companyName, emailContent, emailSubject, Date.now() - start);
+    }
+
+    onProgress?.({ progress: 90, message: '📊 整理分析结果...' });
+
+    const result = this.normalizeAiResult(aiResult, domain, companyName, bgSummary, Date.now() - start);
+
+    onProgress?.({ progress: 100, message: '✅ 分析完成' });
+    return result;
+  }
+
+  // ==================== 快速背调 ====================
+
+  private extractDomain(email: string, website: string): string {
+    if (website) {
+      const cleaned = website.replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/.*$/, '').trim();
+      if (cleaned) return cleaned;
+    }
+    if (email && email.includes('@')) {
+      const d = email.split('@')[1]?.trim().toLowerCase();
+      if (d && !FREE_EMAIL_PROVIDERS.includes(d)) return d;
+    }
+    return '';
+  }
+
+  private async quickBackgroundCheck(
+    domain: string,
+    companyName: string,
+    onProgress?: (p: AiScreenProgress) => void,
+  ): Promise<{ summary: QuickBgCheckSummary; contextForAI: string }> {
+
+    let searchSnippets = '';
+    let scrapedData: FirecrawlScrapeData[] = [];
+
+    // Google Search
+    if (domain) {
+      onProgress?.({ progress: 10, message: `🔍 搜索 ${domain} 的公司信息...` });
+      try {
+        const searchResp = await this.generateApi.googleSearch(`${domain} company profile about`, 5);
+        if (searchResp.rawData?.length) {
+          searchSnippets = searchResp.rawData
+            .slice(0, 5)
+            .map((r, i) => `${i + 1}. [${r.title}](${r.link})\n   ${r.snippet}`)
+            .join('\n');
+        }
+      } catch {}
+    }
+
+    // Firecrawl
+    if (domain) {
+      onProgress?.({ progress: 25, message: `🕷️ 抓取 ${domain} 官网...` });
+      try {
+        scrapedData = await this.generateApi.firecrawlBatchScrape([
+          `https://${domain}`, `https://www.${domain}`,
+        ]);
+      } catch {}
+    }
+
+    onProgress?.({ progress: 45, message: '📊 提炼公司背景信息...' });
+
+    const extracted = this.extractFromScrapedData(scrapedData);
+
+    const summary: QuickBgCheckSummary = {
+      companyVerified: scrapedData.length > 0,
+      companyProfile: extracted.businessScope || (scrapedData[0]?.markdown?.substring(0, 300) || ''),
+      riskLevel: scrapedData.length > 0 ? 'low' : 'high',
+      country: extracted.country || '',
+      industry: extracted.industry || '',
+      employeeScale: extracted.employeeCount || '',
+      foundedYear: extracted.foundedYear || '',
+      certifications: extracted.certifications || [],
+      dataSources: scrapedData.map(d => ({ url: d.metadata.url || d.metadata.sourceURL, title: d.metadata.title || '' })),
+    };
+
+    let contextForAI = '';
+    if (searchSnippets) contextForAI += `## Google 搜索结果\n${searchSnippets}\n\n`;
+    if (scrapedData.length > 0) {
+      contextForAI += '## 官网抓取内容\n';
+      for (const page of scrapedData.slice(0, 2)) {
+        const json: any = (page as any).extract?.json || page.json || {};
+        contextForAI += `### ${page.metadata.title || page.metadata.url}\n`;
+        contextForAI += `结构化数据: ${JSON.stringify(json)}\n`;
+        contextForAI += `页面内容摘要: ${(page.markdown || '').substring(0, 1200)}\n\n`;
+      }
+    }
+    if (summary.companyVerified) {
+      contextForAI += `## 快速背调摘要\n`;
+      contextForAI += `- 公司验证: ✅ 官网可访问\n`;
+      contextForAI += `- 国家: ${summary.country || '未知'}\n`;
+      contextForAI += `- 行业: ${summary.industry || '未知'}\n`;
+      contextForAI += `- 员工规模: ${summary.employeeScale || '未知'}\n`;
+      contextForAI += `- 成立年份: ${summary.foundedYear || '未知'}\n`;
+      contextForAI += `- 认证资质: ${summary.certifications.length > 0 ? summary.certifications.join(', ') : '未知'}\n`;
+      contextForAI += `- 风险等级: ${summary.riskLevel}\n\n`;
+    }
+
+    return { summary, contextForAI };
+  }
+
+  private extractFromScrapedData(data: FirecrawlScrapeData[]): {
+    country: string; industry: string; foundedYear: string;
+    employeeCount: string; businessScope: string; certifications: string[];
+  } {
+    const r = { country: '', industry: '', foundedYear: '', employeeCount: '', businessScope: '', certifications: [] as string[] };
+    for (const page of data) {
+      const json: any = (page as any).extract?.json || page.json || {};
+      if (json.country && !r.country) r.country = json.country;
+      if (json.industry && !r.industry) r.industry = json.industry;
+      if (json.founded_year && !r.foundedYear) r.foundedYear = json.founded_year;
+      if (json.employee_count && !r.employeeCount) r.employeeCount = json.employee_count;
+      if (json.business_scope && !r.businessScope) r.businessScope = json.business_scope;
+      if (json.certifications?.length > 0 && r.certifications.length === 0) r.certifications = json.certifications;
+    }
+    return r;
+  }
+
+  // ==================== Prompt 构建 ====================
+
+  private buildProductCatalog(): string {
+    return this.mockData.products.map(p =>
+      `- ${p.sku}: ${p.name}(${p.nameEn}), 批发$${p.wholesalePrice}, 零售$${p.retailPrice}, ${p.category}, ${p.description}`
+    ).join('\n');
+  }
+
+  private buildClassifyPrompt(
+    companyName: string, email: string, domain: string,
+    emailContent: string, emailSubject: string, bgContext: string,
+  ): string {
+    const bgSection = bgContext
+      ? `\n## 联网背调数据(真实抓取)\n${bgContext}`
+      : '\n## 联网背调数据\n未能获取到联网数据。请基于公司名称、邮箱域名、邮件内容等线索信息,结合你的行业知识进行合理推断和分析。\n';
+
+    const productCatalog = this.buildProductCatalog();
+
+    return `你是犀牛急救(Rhino Rescue)公司的外贸线索分类与价值评估专家。
+犀牛急救主营急救医疗器械(急救包、止血带、夹板、IFAK、壁挂急救箱、EVA户外急救包等),客户覆盖医药分销商、工业安全供应商、政府/NGO采购、品牌OEM、电商零售商。
+
+请根据以下信息,判断该线索属于哪种客户画像,并给出**详细**的价值评估。
+即使线索信息有限,也请基于公司名称含义、邮箱域名、行业常识等进行合理推断,给出你的专业判断。
+
+## 线索信息
+- 公司名称: ${companyName || '未知'}
+- 邮箱: ${email || '未知'}
+- 域名: ${domain || '未知'}
+- 邮件主题: ${emailSubject || '无'}
+- 邮件内容: ${(emailContent || '无').substring(0, 1500)}
+${bgSection}
+## 我方产品目录(犀牛急救真实产品,请从中选择匹配的推荐)
+${productCatalog}
+
+## 客户画像定义
+- P1 医药分销商: 拥有销售渠道的医疗用品经销商,关键词: distributor, wholesale, FDA, bulk order, medical supply, pharmacy
+- P2 工业安全供应商: 面向企业客户的安全设备供应商,关键词: OSHA, workplace safety, PPE, ANSI, industrial, cabinet
+- P3 政府/机构采购: 政府、军队、NGO、医院等,关键词: tender, procurement, government, RFP, military, emergency
+- P4 品牌OEM客户: 户外/运动品牌贴牌定制,关键词: OEM, private label, custom branding, outdoor, adventure, brand
+- P5 电商/零售商: 线上零售商,关键词: amazon, shopify, FBA, dropship, e-commerce, retail
+- P6 低质量/无效线索: 无明确需求、套价格、骗样品,关键词: everything, lowest price, free sample, all kinds
+
+## 价值等级定义
+- S: 最高优先级,需求明确+规模大+认证匹配,24h内回复
+- A: 高优先级,有明确需求或大渠道,3天内跟进
+- B: 中优先级,有一定需求但不够明确,7天内跟进
+- C: 低优先级,需求不明或低质量,归档观察
+
+请严格按照以下 JSON 格式返回(只返回 JSON,不要其他文字):
+{
+  "persona": "P1/P2/P3/P4/P5/P6",
+  "personaLabel": "画像中文名称",
+  "personaConfidence": 0到100,
+  "valueGrade": "S/A/B/C",
+  "customerCategory": "A/B/C/D(A=医疗渠道 B=工业安全 C=应急政府 D=品牌OEM)",
+  "mainBusiness": "公司主营业务描述(基于公司名和域名推断,50-100字)",
+  "country": "国家代码(从域名后缀或公司名推断,如US/GB/DE/CN)",
+  "estimatedScale": "员工规模推断",
+  "productRelevance": "high/medium/low",
+  "matchedKeywords": ["从线索中识别到的关键词"],
+  "certRequirements": ["该客户可能需要的认证,如FDA/CE/OSHA/ISO13485"],
+  "recommendedAction": "具体推荐的跟进行动(50字以内)",
+  "matchedProductSKUs": ["从产品目录中选择最匹配的3-5个SKU编号"],
+  "matchedProductReasons": {"SKU编号": "推荐理由"},
+  "scores": {
+    "demandClarity": 0到100,
+    "credibility": 0到100,
+    "businessScale": 0到100,
+    "productFit": 0到100,
+    "urgency": 0到100
+  },
+  "demandSnapshot": {
+    "products": [{"name": "可能需要的产品名", "quantity": 预估年需求量, "unit": "件/年"}],
+    "estimatedAnnualValue": 预估年采购额美元数字,
+    "certifications": ["认证要求"]
+  },
+  "credibilityDetails": {
+    "level": "high/medium/low",
+    "signals": ["判断可信度的具体依据,至少2条,如:企业邮箱、有官网、行业匹配等"]
+  },
+  "recommendedActions": {
+    "timeframe": "24h内/3天内/7天内",
+    "filesToSend": ["建议发送的文件名称"],
+    "keyPoints": ["跟进时重点强调的内容"]
+  },
+  "socialLinks": {
+    "linkedin": "推断的LinkedIn URL或空字符串",
+    "website": "推断的官网URL"
+  },
+  "reportSummary": "100-200字的完整线索评估报告,包含公司背景推断、需求分析、产品匹配、跟进建议"
+}
+
+重要规则:
+- 即使信息有限,也必须给出合理推断,不要留空
+- demandSnapshot.products 至少列出2个可能需要的产品
+- credibilityDetails.signals 至少列出2条可信度判断依据
+- matchedProductSKUs 必须从产品目录中选择真实SKU
+- scores 每项都必须给出非零评分(即使是推断)
+- reportSummary 必须写满100字以上
+- 如果域名为.com推断US,.co.uk推断GB,.de推断DE,.org可能是NGO`;
+  }
+
+  /**
+   * completionJSON 所需的 schema 字符串
+   */
+  private buildSchema(): string {
+    return `{
+  "persona": "string",
+  "personaLabel": "string",
+  "personaConfidence": "number",
+  "valueGrade": "string",
+  "customerCategory": "string",
+  "mainBusiness": "string",
+  "country": "string",
+  "estimatedScale": "string",
+  "productRelevance": "string",
+  "matchedKeywords": ["string"],
+  "certRequirements": ["string"],
+  "recommendedAction": "string",
+  "matchedProductSKUs": ["string"],
+  "matchedProductReasons": {"string": "string"},
+  "scores": {
+    "demandClarity": "number",
+    "credibility": "number",
+    "businessScale": "number",
+    "productFit": "number",
+    "urgency": "number"
+  },
+  "demandSnapshot": {
+    "products": [{"name": "string", "quantity": "number", "unit": "string"}],
+    "estimatedAnnualValue": "number",
+    "certifications": ["string"]
+  },
+  "credibilityDetails": {
+    "level": "string",
+    "signals": ["string"]
+  },
+  "recommendedActions": {
+    "timeframe": "string",
+    "filesToSend": ["string"],
+    "keyPoints": ["string"]
+  },
+  "socialLinks": {
+    "linkedin": "string",
+    "website": "string"
+  },
+  "reportSummary": "string"
+}`;
+  }
+
+  // ==================== 结果规范化 ====================
+
+  private normalizeAiResult(
+    ai: AiClassifyResult, domain: string, companyName: string,
+    bgSummary: QuickBgCheckSummary | undefined, processingTime: number,
+  ): QuickScreenResult {
+    const persona = this.normalizePersona(ai.persona);
+    const grade = this.normalizeGrade(ai.valueGrade);
+    const category = this.personaToCategory(persona);
+    const personaDef = PERSONA_DEFINITIONS[persona];
+
+    // 用 AI 返回的 SKU 从真实产品目录匹配
+    const aiSKUs = ai.matchedProductSKUs || [];
+    const reasons = ai.matchedProductReasons || {};
+    let products = this.mockData.getProductsBySKUs(aiSKUs).map(p => ({
+      sku: p.sku,
+      name: p.name,
+      nameEn: p.nameEn,
+      wholesalePrice: p.wholesalePrice,
+      retailPrice: p.retailPrice,
+      reason: reasons[p.sku] || p.description,
+    }));
+
+    // AI 没有返回有效 SKU 时,按画像类别兜底
+    if (products.length === 0) {
+      products = this.mockData.getRecommendedProducts(category);
+    }
+
+    const totalScore = ai.scores
+      ? Math.round(
+          (ai.scores.demandClarity * 0.25 + ai.scores.credibility * 0.2 +
+           ai.scores.businessScale * 0.2 + ai.scores.productFit * 0.2 +
+           ai.scores.urgency * 0.15)
+        )
+      : 60;
+
+    return {
+      companyName: companyName || ai.mainBusiness?.split(' ')[0] || '',
+      domain,
+      country: ai.country || (bgSummary?.country) || 'Unknown',
+      customerCategory: category,
+      categoryLabel: this.categoryLabel(category),
+      valueGrade: grade,
+      productRelevance: this.normalizeRelevance(ai.productRelevance),
+      mainBusiness: ai.mainBusiness || personaDef.description,
+      estimatedScale: ai.estimatedScale || (bgSummary?.employeeScale) || this.scaleByGrade(grade),
+      certRequirements: ai.certRequirements || (bgSummary?.certifications) || [],
+      recommendedAction: ai.recommendedAction || personaDef.firstAction,
+      suggestedProducts: products,
+      socialLinks: ai.socialLinks || {},
+      confidence: ai.personaConfidence || 60,
+      processingTime,
+      persona,
+      personaLabel: ai.personaLabel || personaDef.name,
+      personaConfidence: ai.personaConfidence || 60,
+      matchedKeywords: ai.matchedKeywords || [],
+      scores: ai.scores ? { ...ai.scores, total: totalScore } : undefined,
+      demandSnapshot: ai.demandSnapshot || undefined,
+      credibilityDetails: ai.credibilityDetails
+        ? { level: this.normalizeCredLevel(ai.credibilityDetails.level), signals: ai.credibilityDetails.signals || [] }
+        : undefined,
+      recommendedActions: ai.recommendedActions || undefined,
+    };
+  }
+
+  // ==================== 降级:规则引擎 ====================
+
+  private fallbackClassify(
+    domain: string, title: string, content: string, subject: string, processingTime: number,
+  ): QuickScreenResult {
+    const text = `${domain} ${title} ${content} ${subject}`.toLowerCase();
+    let persona: CustomerPersona = 'P4';
+    let category: CustomerCategory = 'D';
+    let grade: ValueGrade = 'B';
+    let mainBusiness = '待分析';
+    let confidence = 60;
+    const matchedKeywords: string[] = [];
+
+    if (text.includes('everything') || text.includes('all kinds') || text.includes('lowest price')) {
+      persona = 'P6'; category = 'D'; mainBusiness = '综合贸易(低质量)'; confidence = 55;
+    } else if (text.includes('tender') || text.includes('government') || text.includes('red cross')) {
+      persona = 'P3'; category = 'C'; mainBusiness = '应急物资政府采购'; confidence = 85;
+    } else if (text.includes('medical') || text.includes('distributor') || text.includes('fda')) {
+      persona = 'P1'; category = 'A'; mainBusiness = '医疗耗材分销'; confidence = 85;
+    } else if (text.includes('safety') || text.includes('osha') || text.includes('workplace')) {
+      persona = 'P2'; category = 'B'; mainBusiness = '工业安全设备'; confidence = 82;
+    } else if (text.includes('amazon') || text.includes('shopify') || text.includes('fba')) {
+      persona = 'P5'; category = 'D'; mainBusiness = '电商零售'; confidence = 80;
+    } else if (text.includes('oem') || text.includes('custom') || text.includes('outdoor')) {
+      persona = 'P4'; category = 'D'; mainBusiness = '品牌定制'; confidence = 78;
+    }
+
+    if (text.includes('bulk') || text.includes('tender') || text.includes('500')) grade = 'S';
+    else if (text.includes('quote') || text.includes('distributor')) grade = 'A';
+
+    const personaDef = PERSONA_DEFINITIONS[persona];
+    const products = this.mockData.getRecommendedProducts(category);
+
+    return {
+      companyName: domain ? domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1) : title.split(' ')[0],
+      domain, country: this.guessCountry(domain),
+      customerCategory: category, categoryLabel: this.categoryLabel(category),
+      valueGrade: grade,
+      productRelevance: grade === 'S' || grade === 'A' ? 'high' : grade === 'B' ? 'medium' : 'low',
+      mainBusiness, estimatedScale: this.scaleByGrade(grade),
+      certRequirements: [], recommendedAction: personaDef.firstAction,
+      suggestedProducts: products, socialLinks: {},
+      confidence, processingTime,
+      persona, personaLabel: personaDef.name, personaConfidence: confidence,
+      matchedKeywords,
+    };
+  }
+
+  // ==================== 辅助方法 ====================
+
+  private normalizePersona(p: string): CustomerPersona {
+    if (['P1', 'P2', 'P3', 'P4', 'P5', 'P6'].includes(p)) return p as CustomerPersona;
+    return 'P4';
+  }
+  private normalizeGrade(g: string): ValueGrade {
+    if (['S', 'A', 'B', 'C'].includes(g)) return g as ValueGrade;
+    return 'B';
+  }
+  private normalizeRelevance(r: string): 'high' | 'medium' | 'low' {
+    if (['high', 'medium', 'low'].includes(r)) return r as any;
+    return 'medium';
+  }
+  private normalizeCredLevel(l: string): 'high' | 'medium' | 'low' {
+    if (['high', 'medium', 'low'].includes(l)) return l as any;
+    return 'medium';
+  }
+  private personaToCategory(p: CustomerPersona): CustomerCategory {
+    const map: Record<CustomerPersona, CustomerCategory> = { P1: 'A', P2: 'B', P3: 'C', P4: 'D', P5: 'D', P6: 'D' };
+    return map[p];
+  }
+  private categoryLabel(c: CustomerCategory): string {
+    return { A: '医疗渠道', B: '工业安全', C: '应急政府', D: '品牌OEM' }[c];
+  }
+  private scaleByGrade(g: ValueGrade): string {
+    return { S: '200-500人', A: '50-200人', B: '10-50人', C: '<10人' }[g];
+  }
+  private guessCountry(domain: string): string {
+    if (!domain) return 'Unknown';
+    if (domain.endsWith('.com')) return 'US';
+    if (domain.endsWith('.co.uk')) return 'GB';
+    if (domain.endsWith('.de')) return 'DE';
+    if (domain.endsWith('.org')) return 'PL';
+    if (domain.endsWith('.ca')) return 'CA';
+    if (domain.endsWith('.au')) return 'AU';
+    if (domain.endsWith('.se')) return 'SE';
+    return 'Unknown';
+  }
+}

+ 244 - 0
lead-discovery/src/app/services/batch-input.service.ts

@@ -0,0 +1,244 @@
+import { Injectable } from '@angular/core';
+import { BatchInputItem, BatchProcessResult, EntityGroup } from '../models/lead.model';
+import { EntityResolverService } from './entity-resolver.service';
+
+@Injectable({ providedIn: 'root' })
+export class BatchInputService {
+
+  constructor(private entityResolver: EntityResolverService) {}
+
+  /**
+   * 解析多行文本输入(每行一条:公司名 + 邮箱 或 纯邮箱)
+   */
+  parseTextInput(text: string): BatchInputItem[] {
+    if (!text || !text.trim()) return [];
+
+    const lines = text.split('\n')
+      .map(l => l.trim())
+      .filter(l => l.length > 0);
+
+    return lines.map(line => this.parseSingleLine(line));
+  }
+
+  /**
+   * 解析逗号/分号分隔的多个邮箱
+   */
+  parseMultipleEmails(text: string): BatchInputItem[] {
+    if (!text || !text.trim()) return [];
+
+    const emails = text.split(/[,;,;\n]/)
+      .map(e => e.trim())
+      .filter(e => e.length > 0);
+
+    return emails.map(email => {
+      const isEmail = email.includes('@');
+      if (isEmail) {
+        const domain = this.entityResolver.extractDomain(email);
+        return {
+          rawText: email,
+          parsedEmail: email,
+          parsedDomain: domain,
+          parsedCompanyName: domain ? this.domainToCompanyName(domain) : undefined,
+          parseConfidence: domain ? 75 : 40,
+          parseSource: 'email' as const,
+        };
+      }
+      return {
+        rawText: email,
+        parsedCompanyName: email,
+        parseConfidence: 50,
+        parseSource: 'text' as const,
+      };
+    });
+  }
+
+  /**
+   * 解析 CSV 文本内容
+   * 自动检测列头:company/公司/email/邮箱/contact/联系人/phone/电话/country/国家
+   */
+  parseCSVText(csvText: string): BatchInputItem[] {
+    if (!csvText || !csvText.trim()) return [];
+
+    const lines = csvText.split('\n').map(l => l.trim()).filter(l => l.length > 0);
+    if (lines.length < 2) return [];
+
+    const header = lines[0].toLowerCase();
+    const cols = this.splitCSVLine(lines[0]);
+    const colMap = this.detectColumns(cols);
+
+    const items: BatchInputItem[] = [];
+    for (let i = 1; i < lines.length; i++) {
+      const values = this.splitCSVLine(lines[i]);
+      if (values.length === 0) continue;
+
+      const email = colMap.email >= 0 ? values[colMap.email]?.trim() : undefined;
+      const company = colMap.company >= 0 ? values[colMap.company]?.trim() : undefined;
+      const contact = colMap.contact >= 0 ? values[colMap.contact]?.trim() : undefined;
+      const phone = colMap.phone >= 0 ? values[colMap.phone]?.trim() : undefined;
+      const country = colMap.country >= 0 ? values[colMap.country]?.trim() : undefined;
+      const domain = email ? this.entityResolver.extractDomain(email) : undefined;
+
+      items.push({
+        rawText: lines[i],
+        parsedCompanyName: company || (domain ? this.domainToCompanyName(domain) : undefined),
+        parsedEmail: email,
+        parsedDomain: domain,
+        parsedContactName: contact,
+        parsedPhone: phone,
+        parsedCountry: country,
+        parseConfidence: this.calcConfidence(company, email, domain),
+        parseSource: 'csv',
+      });
+    }
+
+    return items;
+  }
+
+  /**
+   * 模拟 OCR 解析结果(Demo 阶段用预设数据)
+   */
+  parseOCRResult(imageFiles: File[]): BatchInputItem[] {
+    const mockOCRResults: BatchInputItem[] = [
+      {
+        rawText: 'James Wilson\nGlobal Safety Solutions\njames@globalsafety.com\n+1-312-555-0198',
+        parsedCompanyName: 'Global Safety Solutions',
+        parsedEmail: 'james@globalsafety.com',
+        parsedDomain: 'globalsafety.com',
+        parsedContactName: 'James Wilson',
+        parsedPhone: '+1-312-555-0198',
+        parsedCountry: 'US',
+        parseConfidence: 85,
+        parseSource: 'ocr',
+      },
+      {
+        rawText: 'Dr. Maria Fernandez\nMedSupply Latina S.A.\nmaria.f@medsupply-latina.com\nBogota, Colombia',
+        parsedCompanyName: 'MedSupply Latina S.A.',
+        parsedEmail: 'maria.f@medsupply-latina.com',
+        parsedDomain: 'medsupply-latina.com',
+        parsedContactName: 'Dr. Maria Fernandez',
+        parsedCountry: 'CO',
+        parseConfidence: 82,
+        parseSource: 'ocr',
+      },
+      {
+        rawText: 'Tom Baker\nOutdoor Essentials Ltd\ntom@outdoor-essentials.co.uk\nManchester, UK',
+        parsedCompanyName: 'Outdoor Essentials Ltd',
+        parsedEmail: 'tom@outdoor-essentials.co.uk',
+        parsedDomain: 'outdoor-essentials.co.uk',
+        parsedContactName: 'Tom Baker',
+        parsedCountry: 'GB',
+        parseConfidence: 88,
+        parseSource: 'ocr',
+      },
+    ];
+
+    return mockOCRResults.slice(0, Math.max(imageFiles.length, mockOCRResults.length));
+  }
+
+  /**
+   * 执行完整的批量处理流程:解析 → 去重归并 → 返回结果
+   */
+  processBatch(items: BatchInputItem[]): BatchProcessResult {
+    const validItems = items.filter(i =>
+      i.parsedEmail || i.parsedCompanyName || i.parsedDomain
+    );
+    const failedCount = items.length - validItems.length;
+
+    const resolveInputs = validItems.map(item => ({
+      domain: item.parsedDomain || (item.parsedEmail ? this.entityResolver.extractDomain(item.parsedEmail) : ''),
+      companyName: item.parsedCompanyName || '',
+      email: item.parsedEmail || '',
+      contactName: item.parsedContactName || '',
+      source: item.parseSource,
+    }));
+
+    const entities = this.entityResolver.resolveBatch(resolveInputs);
+    const duplicatesFound = entities.filter(e => !e.isNew).length;
+
+    return {
+      totalInput: items.length,
+      successParsed: validItems.length,
+      failedParsed: failedCount,
+      duplicatesFound,
+      items: validItems,
+      entities,
+    };
+  }
+
+  // ===== 内部方法 =====
+
+  private parseSingleLine(line: string): BatchInputItem {
+    const emailMatch = line.match(/[\w.+-]+@[\w.-]+\.\w+/);
+    const email = emailMatch ? emailMatch[0] : undefined;
+    const domain = email ? this.entityResolver.extractDomain(email) : undefined;
+
+    let companyName: string | undefined;
+    if (email) {
+      const remaining = line.replace(email, '').trim().replace(/[,;,;\t|]+/g, '').trim();
+      companyName = remaining || (domain ? this.domainToCompanyName(domain) : undefined);
+    } else {
+      companyName = line;
+    }
+
+    const phoneMatch = line.match(/[+]?\d[\d\s\-().]{7,}/);
+
+    return {
+      rawText: line,
+      parsedCompanyName: companyName,
+      parsedEmail: email,
+      parsedDomain: domain,
+      parsedPhone: phoneMatch ? phoneMatch[0].trim() : undefined,
+      parseConfidence: this.calcConfidence(companyName, email, domain),
+      parseSource: 'text',
+    };
+  }
+
+  private splitCSVLine(line: string): string[] {
+    const result: string[] = [];
+    let current = '';
+    let inQuotes = false;
+
+    for (const ch of line) {
+      if (ch === '"') {
+        inQuotes = !inQuotes;
+      } else if ((ch === ',' || ch === '\t') && !inQuotes) {
+        result.push(current);
+        current = '';
+      } else {
+        current += ch;
+      }
+    }
+    result.push(current);
+    return result;
+  }
+
+  private detectColumns(headers: string[]): { company: number; email: number; contact: number; phone: number; country: number } {
+    const map = { company: -1, email: -1, contact: -1, phone: -1, country: -1 };
+
+    headers.forEach((h, i) => {
+      const lower = h.toLowerCase().trim();
+      if (['company', '公司', '公司名称', 'company name', 'organization'].includes(lower)) map.company = i;
+      else if (['email', '邮箱', '邮件', 'e-mail', 'mail'].includes(lower)) map.email = i;
+      else if (['contact', '联系人', 'name', '姓名', 'contact name'].includes(lower)) map.contact = i;
+      else if (['phone', '电话', 'tel', 'telephone', 'mobile'].includes(lower)) map.phone = i;
+      else if (['country', '国家', 'region', '地区'].includes(lower)) map.country = i;
+    });
+
+    return map;
+  }
+
+  private domainToCompanyName(domain: string): string {
+    if (!domain) return '';
+    const parts = domain.split('.');
+    const name = parts[0].replace(/[-_]/g, ' ');
+    return name.charAt(0).toUpperCase() + name.slice(1);
+  }
+
+  private calcConfidence(company?: string, email?: string, domain?: string): number {
+    let score = 30;
+    if (company) score += 25;
+    if (email) score += 25;
+    if (domain) score += 20;
+    return Math.min(score, 95);
+  }
+}

+ 320 - 0
lead-discovery/src/app/services/entity-resolver.service.ts

@@ -0,0 +1,320 @@
+import { Injectable } from '@angular/core';
+import { Lead, EntityGroup, ContactInfo, MergeRecord, EntityProfile, CustomerPersona, ValueGrade } from '../models/lead.model';
+
+const FREE_EMAIL_PROVIDERS = [
+  'gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com',
+  'qq.com', '163.com', 'mail.com', 'aol.com', 'icloud.com',
+  'protonmail.com', 'yandex.com', 'zoho.com',
+];
+
+const COMPANY_SUFFIXES = [
+  'ltd', 'inc', 'corp', 'co', 'llc', 'gmbh', 'ag', 'sa', 'srl',
+  'pty', 'plc', 'bv', 'ab', 'oy', 'as', 'limited', 'incorporated',
+  'corporation', 'company', 'group', 'holding', 'holdings',
+  'international', 'intl', 'enterprise', 'enterprises',
+];
+
+@Injectable({ providedIn: 'root' })
+export class EntityResolverService {
+
+  private entityIndex = new Map<string, EntityGroup>();
+  private domainIndex = new Map<string, string>();
+  private companyIndex = new Map<string, string>();
+
+  /**
+   * 从邮箱提取域名,免费邮箱返回空字符串
+   */
+  extractDomain(email: string): string {
+    if (!email) return '';
+    const parts = email.split('@');
+    if (parts.length !== 2) return '';
+    const domain = parts[1].toLowerCase().trim();
+    return FREE_EMAIL_PROVIDERS.includes(domain) ? '' : domain;
+  }
+
+  /**
+   * 判断是否免费邮箱
+   */
+  isFreeEmail(email: string): boolean {
+    if (!email) return false;
+    const parts = email.split('@');
+    if (parts.length !== 2) return false;
+    return FREE_EMAIL_PROVIDERS.includes(parts[1].toLowerCase().trim());
+  }
+
+  /**
+   * 域名标准化:去除 www 前缀,统一小写
+   */
+  normalizeDomain(domain: string): string {
+    if (!domain) return '';
+    let d = domain.toLowerCase().trim();
+    if (d.startsWith('www.')) d = d.substring(4);
+    if (d.startsWith('http://')) d = d.substring(7);
+    if (d.startsWith('https://')) d = d.substring(8);
+    const slashIdx = d.indexOf('/');
+    if (slashIdx > 0) d = d.substring(0, slashIdx);
+    return d;
+  }
+
+  /**
+   * 公司名标准化:去除后缀、标点、空格、大小写
+   */
+  normalizeCompanyName(name: string): string {
+    if (!name) return '';
+    let n = name.toLowerCase().trim();
+    n = n.replace(/[.,\-_()()]/g, ' ');
+    const words = n.split(/\s+/).filter(w => !COMPANY_SUFFIXES.includes(w));
+    return words.join('');
+  }
+
+  /**
+   * 字符串相似度(Dice coefficient)
+   */
+  similarity(a: string, b: string): number {
+    if (!a || !b) return 0;
+    if (a === b) return 1;
+    const bigramsA = new Set<string>();
+    const bigramsB = new Set<string>();
+    for (let i = 0; i < a.length - 1; i++) bigramsA.add(a.substring(i, i + 2));
+    for (let i = 0; i < b.length - 1; i++) bigramsB.add(b.substring(i, i + 2));
+    let intersection = 0;
+    bigramsA.forEach(bg => { if (bigramsB.has(bg)) intersection++; });
+    return (2 * intersection) / (bigramsA.size + bigramsB.size);
+  }
+
+  /**
+   * 用已有线索列表初始化索引
+   */
+  initFromLeads(leads: Lead[]) {
+    this.entityIndex.clear();
+    this.domainIndex.clear();
+    this.companyIndex.clear();
+
+    for (const lead of leads) {
+      const entityId = lead.entityId || `entity-${lead.id}`;
+      const domain = lead.domainKey || this.extractDomain(lead.contactEmail);
+      const normDomain = this.normalizeDomain(domain);
+      const normCompany = this.normalizeCompanyName(lead.companyName);
+
+      let group = this.entityIndex.get(entityId);
+      if (!group) {
+        group = {
+          entityId,
+          primaryDomain: domain,
+          primaryCompanyName: lead.companyName,
+          normalizedDomain: normDomain,
+          normalizedCompanyName: normCompany,
+          confidence: 95,
+          leads: [],
+          contacts: [],
+          duplicateWarnings: [],
+          existingLeadIds: [],
+          isNew: false,
+          mergeHistory: [],
+        };
+        this.entityIndex.set(entityId, group);
+      }
+
+      group.leads.push(lead);
+      group.existingLeadIds.push(lead.id);
+
+      if (lead.contactEmail) {
+        const existing = group.contacts.find(c => c.email === lead.contactEmail);
+        if (!existing) {
+          group.contacts.push({
+            name: lead.contactName,
+            email: lead.contactEmail,
+            source: lead.sourceLabel,
+            firstSeenAt: lead.createdAt,
+          });
+        }
+      }
+
+      if (normDomain) this.domainIndex.set(normDomain, entityId);
+      if (normCompany) this.companyIndex.set(normCompany, entityId);
+    }
+  }
+
+  /**
+   * 核心:查找匹配的主体
+   * 返回 { entityId, confidence, matchType } 或 null
+   */
+  findMatchingEntity(
+    domain: string,
+    companyName: string
+  ): { entityId: string; confidence: number; matchType: string } | null {
+    const normDomain = this.normalizeDomain(domain);
+    const normCompany = this.normalizeCompanyName(companyName);
+
+    // 优先级1:域名完全匹配
+    if (normDomain && this.domainIndex.has(normDomain)) {
+      const entityId = this.domainIndex.get(normDomain)!;
+      const entity = this.entityIndex.get(entityId);
+      if (entity) {
+        const companyMatch = normCompany && this.similarity(normCompany, entity.normalizedCompanyName) > 0.6;
+        return {
+          entityId,
+          confidence: companyMatch ? 99 : 95,
+          matchType: companyMatch ? '域名+公司名完全匹配' : '域名完全匹配',
+        };
+      }
+    }
+
+    // 优先级2:公司名完全匹配
+    if (normCompany && this.companyIndex.has(normCompany)) {
+      const entityId = this.companyIndex.get(normCompany)!;
+      return { entityId, confidence: 90, matchType: '公司名完全匹配' };
+    }
+
+    // 优先级3:域名相似度匹配
+    if (normDomain) {
+      for (const [existingDomain, entityId] of this.domainIndex.entries()) {
+        const sim = this.similarity(normDomain, existingDomain);
+        if (sim > 0.8) {
+          return { entityId, confidence: Math.round(sim * 85), matchType: `域名相似(${Math.round(sim * 100)}%)` };
+        }
+      }
+    }
+
+    // 优先级4:公司名相似度匹配
+    if (normCompany) {
+      for (const [existingCompany, entityId] of this.companyIndex.entries()) {
+        const sim = this.similarity(normCompany, existingCompany);
+        if (sim > 0.85) {
+          return { entityId, confidence: Math.round(sim * 80), matchType: `公司名相似(${Math.round(sim * 100)}%)` };
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * 将一条线索解析并归入主体,返回主体信息
+   */
+  resolveEntity(
+    domain: string,
+    companyName: string,
+    contactEmail: string,
+    contactName: string,
+    source: string,
+  ): { entity: EntityGroup; isDuplicate: boolean; matchInfo?: string } {
+    const match = this.findMatchingEntity(domain, companyName);
+
+    if (match) {
+      const entity = this.entityIndex.get(match.entityId)!;
+      const isNewContact = !entity.contacts.find(c => c.email === contactEmail);
+      if (isNewContact && contactEmail) {
+        entity.contacts.push({
+          name: contactName,
+          email: contactEmail,
+          source,
+          firstSeenAt: new Date(),
+        });
+      }
+      return {
+        entity,
+        isDuplicate: true,
+        matchInfo: `${match.matchType} (置信度 ${match.confidence}%)`,
+      };
+    }
+
+    // 新主体
+    const normDomain = this.normalizeDomain(domain);
+    const normCompany = this.normalizeCompanyName(companyName);
+    const entityId = `entity-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
+
+    const newEntity: EntityGroup = {
+      entityId,
+      primaryDomain: domain,
+      primaryCompanyName: companyName,
+      normalizedDomain: normDomain,
+      normalizedCompanyName: normCompany,
+      confidence: 100,
+      leads: [],
+      contacts: contactEmail ? [{
+        name: contactName,
+        email: contactEmail,
+        source,
+        firstSeenAt: new Date(),
+      }] : [],
+      duplicateWarnings: [],
+      existingLeadIds: [],
+      isNew: true,
+      mergeHistory: [],
+    };
+
+    this.entityIndex.set(entityId, newEntity);
+    if (normDomain) this.domainIndex.set(normDomain, entityId);
+    if (normCompany) this.companyIndex.set(normCompany, entityId);
+
+    return { entity: newEntity, isDuplicate: false };
+  }
+
+  /**
+   * 批量解析:输入多条线索信息,自动归并到主体
+   */
+  resolveBatch(
+    items: Array<{ domain: string; companyName: string; email: string; contactName: string; source: string }>
+  ): EntityGroup[] {
+    const touchedEntityIds = new Set<string>();
+
+    for (const item of items) {
+      const result = this.resolveEntity(item.domain, item.companyName, item.email, item.contactName, item.source);
+      touchedEntityIds.add(result.entity.entityId);
+      if (result.isDuplicate) {
+        result.entity.duplicateWarnings.push(
+          `发现重复: ${item.companyName || item.email} — ${result.matchInfo}`
+        );
+      }
+    }
+
+    return Array.from(touchedEntityIds)
+      .map(id => this.entityIndex.get(id)!)
+      .filter(Boolean);
+  }
+
+  getEntity(entityId: string): EntityGroup | undefined {
+    return this.entityIndex.get(entityId);
+  }
+
+  getAllEntities(): EntityGroup[] {
+    return Array.from(this.entityIndex.values());
+  }
+
+  getEntityCount(): number {
+    return this.entityIndex.size;
+  }
+
+  /**
+   * 构建主体画像汇总
+   */
+  buildEntityProfile(entityId: string): EntityProfile | null {
+    const entity = this.entityIndex.get(entityId);
+    if (!entity || entity.leads.length === 0) return null;
+
+    const latestLead = entity.leads.reduce((a, b) =>
+      a.createdAt > b.createdAt ? a : b
+    );
+
+    return {
+      entityId,
+      primaryCompanyName: entity.primaryCompanyName,
+      primaryDomain: entity.primaryDomain,
+      allDomains: [...new Set(entity.leads.map(l => l.domainKey).filter(Boolean))],
+      allEmails: [...new Set(entity.leads.map(l => l.contactEmail).filter(Boolean))],
+      contacts: entity.contacts,
+      leadIds: entity.leads.map(l => l.id),
+      latestPersona: latestLead.persona,
+      latestGrade: latestLead.valueGrade,
+      totalEstimatedValue: entity.leads.reduce((sum, l) => sum + l.estimatedOrderValue, 0),
+      interactionTimeline: entity.leads.map(l => ({
+        date: l.createdAt,
+        event: `${l.sourceLabel} - ${l.companyName}`,
+        leadId: l.id,
+      })).sort((a, b) => b.date.getTime() - a.date.getTime()),
+      createdAt: entity.leads.reduce((a, b) => a.createdAt < b.createdAt ? a : b).createdAt,
+      updatedAt: latestLead.createdAt,
+    };
+  }
+}

+ 208 - 0
lead-discovery/src/app/services/generate-api.service.ts

@@ -0,0 +1,208 @@
+/**
+ * Generate API 服务(参考 nanchi GenerateApiService)
+ *
+ * 封装三个核心 API:
+ * 1. Google 搜索 — 公司验证、市场情报
+ * 2. Firecrawl 网页抓取 — 官网内容提取
+ * 3. AI 模型调用 — completionJSON 替代
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { firstValueFrom } from 'rxjs';
+
+export interface GoogleSearchResult {
+  title: string;
+  link: string;
+  snippet: string;
+}
+
+export interface GoogleSearchResponse {
+  tool: string;
+  provider: string;
+  rawData: GoogleSearchResult[];
+  timestamp: string;
+}
+
+export interface FirecrawlMetadata {
+  title: string;
+  description: string;
+  url: string;
+  sourceURL: string;
+  statusCode: number;
+}
+
+export interface FirecrawlScrapeData {
+  markdown?: string;
+  json?: any;
+  metadata: FirecrawlMetadata;
+}
+
+export interface FirecrawlBatchScrapeResponse {
+  completed: boolean;
+  total: number;
+  data: FirecrawlScrapeData[];
+  timestamp: string;
+}
+
+@Injectable({ providedIn: 'root' })
+export class GenerateApiService {
+
+  private readonly BASE_URL = 'https://server.fmode.cn';
+
+  constructor(private http: HttpClient) {}
+
+  hasToken(): boolean {
+    return !!this.getToken();
+  }
+
+  setToken(token: string) {
+    localStorage.setItem('fmode_auth_token', token);
+  }
+
+  private getToken(): string {
+    return localStorage.getItem('fmode_auth_token') || '';
+  }
+
+  private getHeaders(): HttpHeaders {
+    return new HttpHeaders({
+      'Authorization': `Bearer ${this.getToken()}`,
+      'Content-Type': 'application/json',
+    });
+  }
+
+  private ensureToken(): void {
+    if (!this.getToken()) {
+      throw new Error('未配置 AI Token,请在设置中输入 fmode session token');
+    }
+  }
+
+  /**
+   * Google 搜索(需要 Token)
+   */
+  async googleSearch(query: string, num = 5): Promise<GoogleSearchResponse> {
+    this.ensureToken();
+    const url = `${this.BASE_URL}/api/apig/generate/plugin/google-search`;
+    console.log(`🔍 [Google Search] 查询: "${query}"`);
+    const resp = await firstValueFrom(
+      this.http.post<GoogleSearchResponse>(url, { query, num }, { headers: this.getHeaders() })
+    );
+    console.log(`✅ [Google Search] 找到 ${resp.rawData?.length || 0} 个结果`);
+    return resp;
+  }
+
+  /**
+   * Firecrawl 批量抓取官网(需要 Token)
+   */
+  async firecrawlBatchScrape(urls: string[]): Promise<FirecrawlScrapeData[]> {
+    this.ensureToken();
+    const apiUrl = `${this.BASE_URL}/api/apig/firecrawl/batch/scrape`;
+    console.log(`🕷️ [Firecrawl] 抓取 ${urls.length} 个URL...`);
+    const resp = await firstValueFrom(
+      this.http.post<FirecrawlBatchScrapeResponse>(apiUrl, {
+        urls,
+        formats: [
+          'markdown',
+          {
+            type: 'json',
+            prompt: '提取公司关键信息:公司名称、行业、国家、成立年份、员工规模、主营业务、认证资质',
+            schema: {
+              type: 'object',
+              properties: {
+                company_name: { type: 'string' },
+                industry: { type: 'string' },
+                country: { type: 'string' },
+                founded_year: { type: 'string' },
+                employee_count: { type: 'string' },
+                business_scope: { type: 'string' },
+                certifications: { type: 'array', items: { type: 'string' } },
+              },
+              required: ['company_name'],
+            },
+          },
+        ],
+        pollInterval: 2,
+        timeout: 60,
+      }, { headers: this.getHeaders() })
+    );
+    return (resp.data || []).filter(d => d.metadata.statusCode === 200);
+  }
+
+  /**
+   * 调用 AI 模型(替代 completionJSON,需要 Token)
+   */
+  async callAIModelJSON<T = any>(
+    model: string,
+    prompt: string,
+    onProgress?: (msg: string) => void,
+  ): Promise<T> {
+    this.ensureToken();
+    const url = `${this.BASE_URL}/api/apig/generate/minor/${model}`;
+    onProgress?.('正在调用 AI 模型...');
+
+    const response = await fetch(url, {
+      method: 'POST',
+      headers: {
+        'Authorization': `Bearer ${this.getToken()}`,
+        'Content-Type': 'application/json',
+      },
+      body: JSON.stringify({ content: prompt }),
+    });
+
+    if (!response.ok) {
+      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+    }
+
+    const contentType = response.headers.get('content-type');
+
+    if (contentType?.includes('text/event-stream')) {
+      const reader = response.body!.getReader();
+      const decoder = new TextDecoder();
+      let fullContent = '';
+      let buffer = '';
+
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) break;
+
+        buffer += decoder.decode(value, { stream: true });
+        const lines = buffer.split('\n');
+        buffer = lines.pop() || '';
+
+        for (const line of lines) {
+          if (line.startsWith('data: ')) {
+            const data = line.substring(6).trim();
+            if (data === '[DONE]') continue;
+            try {
+              const evt = JSON.parse(data);
+              const content = evt.choices?.[0]?.delta?.content;
+              if (content) fullContent += content;
+            } catch {}
+          }
+        }
+      }
+
+      return this.extractJSON<T>(fullContent);
+    } else {
+      const data = await response.json();
+      const resultText = data.result || JSON.stringify(data);
+      return this.extractJSON<T>(resultText);
+    }
+  }
+
+  /**
+   * 从 AI 输出文本中提取 JSON
+   */
+  private extractJSON<T>(text: string): T {
+    let jsonStr = text.trim();
+    const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
+    if (fenceMatch) jsonStr = fenceMatch[1].trim();
+
+    const braceStart = jsonStr.indexOf('{');
+    const braceEnd = jsonStr.lastIndexOf('}');
+    if (braceStart >= 0 && braceEnd > braceStart) {
+      jsonStr = jsonStr.substring(braceStart, braceEnd + 1);
+    }
+
+    return JSON.parse(jsonStr) as T;
+  }
+}

+ 305 - 4
lead-discovery/src/app/services/mock-data.service.ts

@@ -2,7 +2,8 @@ import { Injectable } from '@angular/core';
 import {
 import {
   Lead, Email, Product, CustomerCategory, ValueGrade, CustomerPersona,
   Lead, Email, Product, CustomerCategory, ValueGrade, CustomerPersona,
   QuickScreenResult, ProductRecommendation, AiDiscoveryBatch,
   QuickScreenResult, ProductRecommendation, AiDiscoveryBatch,
-  PERSONA_DEFINITIONS, categoryToPersona, ProductMatch
+  PERSONA_DEFINITIONS, categoryToPersona, ProductMatch,
+  BackgroundCheckReport
 } from '../models/lead.model';
 } from '../models/lead.model';
 
 
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
@@ -333,19 +334,21 @@ Web: www.meditechsupply.ca`,
     this.createLead('l8', 'Bundeswehr Procurement', 'beschaffung@bwb.de', 'Hans Weber', 'DE', 'C', 'A', 'P3', 'exhibition', 'quote', 350000, new Date('2026-02-16')),
     this.createLead('l8', 'Bundeswehr Procurement', 'beschaffung@bwb.de', 'Hans Weber', 'DE', 'C', 'A', 'P3', 'exhibition', 'quote', 350000, new Date('2026-02-16')),
     this.createLead('l9', 'TrailBlazer Gear', 'buying@trailblazer.com', 'Emma Davis', 'US', 'D', 'B', 'P5', 'platform', 'contacted', 15000, new Date('2026-02-20')),
     this.createLead('l9', 'TrailBlazer Gear', 'buying@trailblazer.com', 'Emma Davis', 'US', 'D', 'B', 'P5', 'platform', 'contacted', 15000, new Date('2026-02-20')),
     this.createLead('l10', 'NordicMed AB', 'supply@nordicmed.se', 'Erik Lindgren', 'SE', 'A', 'S', 'P1', 'email', 'contacted', 95000, new Date('2026-02-12')),
     this.createLead('l10', 'NordicMed AB', 'supply@nordicmed.se', 'Erik Lindgren', 'SE', 'A', 'S', 'P1', 'email', 'contacted', 95000, new Date('2026-02-12')),
+    this.createLead('l11', 'Acme Medical', 'purchasing@acmemedical.com', 'Robert Chen', 'US', 'A', 'A', 'P1', 'email', 'screened', 80000, new Date('2026-02-14')),
+    this.createLead('l12', 'SafetyFirst', 'sales@safetyfirst-uk.co.uk', 'David Brown', 'GB', 'B', 'B', 'P2', 'platform', 'screened', 25000, new Date('2026-02-15')),
   ];
   ];
 
 
   // ===== AI 挖掘批次(模拟) =====
   // ===== AI 挖掘批次(模拟) =====
   readonly discoveryBatches: AiDiscoveryBatch[] = [
   readonly discoveryBatches: AiDiscoveryBatch[] = [
     {
     {
-      id: 'batch-1', runAt: new Date('2026-02-11T08:00:00'), triggerType: 'daily',
+      id: 'batch-1', runAt: new Date('2026-02-11T00:00:00'), triggerType: 'daily',
       totalProcessed: 47, newLeadsFound: 12,
       totalProcessed: 47, newLeadsFound: 12,
       personaBreakdown: { P1: 5, P2: 2, P3: 1, P4: 2, P5: 1, P6: 1 },
       personaBreakdown: { P1: 5, P2: 2, P3: 1, P4: 2, P5: 1, P6: 1 },
       gradeBreakdown: { S: 2, A: 4, B: 4, C: 2 },
       gradeBreakdown: { S: 2, A: 4, B: 4, C: 2 },
       pendingVerification: 5, verified: 7, status: 'completed',
       pendingVerification: 5, verified: 7, status: 'completed',
     },
     },
     {
     {
-      id: 'batch-2', runAt: new Date('2026-02-10T08:00:00'), triggerType: 'daily',
+      id: 'batch-2', runAt: new Date('2026-02-10T00:00:00'), triggerType: 'daily',
       totalProcessed: 35, newLeadsFound: 8,
       totalProcessed: 35, newLeadsFound: 8,
       personaBreakdown: { P1: 3, P2: 1, P3: 2, P4: 1, P5: 0, P6: 1 },
       personaBreakdown: { P1: 3, P2: 1, P3: 2, P4: 1, P5: 0, P6: 1 },
       gradeBreakdown: { S: 1, A: 3, B: 3, C: 1 },
       gradeBreakdown: { S: 1, A: 3, B: 3, C: 1 },
@@ -397,6 +400,8 @@ Web: www.meditechsupply.ca`,
       generatedAt: new Date(Date.now() - Math.random() * 3 * 86400000),
       generatedAt: new Date(Date.now() - Math.random() * 3 * 86400000),
     } : undefined;
     } : undefined;
 
 
+    const domain = email.split('@')[1] || '';
+
     return {
     return {
       id, leadNumber: `LD-${Date.now()}-${id}`,
       id, leadNumber: `LD-${Date.now()}-${id}`,
       contactName: contact, contactEmail: email, companyName: company,
       contactName: contact, contactEmail: email, companyName: company,
@@ -405,7 +410,7 @@ Web: www.meditechsupply.ca`,
       persona, personaLabel: personaDef.name, personaConfidence: personaConf,
       persona, personaLabel: personaDef.name, personaConfidence: personaConf,
       personaVerified: stage !== 'screened',
       personaVerified: stage !== 'screened',
       quickScreenResult: {
       quickScreenResult: {
-        companyName: company, domain: email.split('@')[1], country,
+        companyName: company, domain, country,
         customerCategory: cat, categoryLabel: catLabels[cat],
         customerCategory: cat, categoryLabel: catLabels[cat],
         valueGrade: grade, productRelevance: grade === 'S' || grade === 'A' ? 'high' : 'medium',
         valueGrade: grade, productRelevance: grade === 'S' || grade === 'A' ? 'high' : 'medium',
         mainBusiness: this.getMainBusiness(cat),
         mainBusiness: this.getMainBusiness(cat),
@@ -427,6 +432,12 @@ Web: www.meditechsupply.ca`,
       followUpNotes: '',
       followUpNotes: '',
       matchedProductSKUs: products.map(p => p.sku),
       matchedProductSKUs: products.map(p => p.sku),
       estimatedOrderValue: orderValue,
       estimatedOrderValue: orderValue,
+      hasBackgroundCheck: stage === 'contacted' || stage === 'replied' || stage === 'sample' || stage === 'quote' || stage === 'order',
+      entityId: `entity-${id}`,
+      domainKey: domain,
+      emailKey: email,
+      relatedLeadIds: [],
+      isEntityPrimary: true,
       createdAt: new Date(Date.now() - Math.random() * 7 * 86400000),
       createdAt: new Date(Date.now() - Math.random() * 7 * 86400000),
     };
     };
   }
   }
@@ -462,4 +473,294 @@ Web: www.meditechsupply.ca`,
     };
     };
     return m[grade];
     return m[grade];
   }
   }
+
+  /**
+   * 生成背调报告(Mock数据)
+   * 根据线索信息生成符合三维报告结构的背调数据
+   */
+  generateBackgroundCheckReport(lead: Lead): BackgroundCheckReport {
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+    const score = lead.valueGrade === 'S' ? 85 : lead.valueGrade === 'A' ? 72 : lead.valueGrade === 'B' ? 58 : 35;
+    const riskLevel = lead.valueGrade === 'S' ? 'low' : lead.valueGrade === 'A' ? 'medium' : 'high' as 'low' | 'medium' | 'high';
+
+    // 根据画像类型生成不同的报告内容
+    const report: BackgroundCheckReport = {
+      meta: {
+        date: new Date().toISOString().split('T')[0],
+        investigator: 'AI助手',
+        method: '三维背调法 + AI智能分析',
+        level: lead.valueGrade,
+      },
+      overview: {
+        companyName: lead.companyName,
+        website: lead.quickScreenResult.domain ? `https://${lead.quickScreenResult.domain}` : '',
+        country: lead.country,
+        channel: lead.sourceLabel,
+        completion: {
+          company: 95,
+          decisionMaker: lead.persona === 'P3' ? 80 : 90,
+          project: 85,
+        },
+        conclusion: this.getConclusion(lead),
+      },
+      dimension1: {
+        basic: {
+          regYear: this.getRegYear(lead.valueGrade),
+          employees: this.getEmployeeCount(lead.valueGrade),
+          address: `${lead.country}(基于域名推断)`,
+        },
+        financial: {
+          revenue: this.getRevenue(lead.valueGrade),
+          currency: lead.country === 'US' ? 'USD' : lead.country === 'GB' ? 'GBP' : lead.country === 'DE' ? 'EUR' : 'USD',
+        },
+        social: {
+          linkedin: {
+            url: `https://linkedin.com/company/${lead.companyName.toLowerCase().replace(/\s+/g, '-')}`,
+            followers: this.getSocialFollowers(lead.valueGrade, 'linkedin'),
+            verified: true,
+          },
+          twitter: lead.valueGrade !== 'C' ? {
+            url: `https://twitter.com/${lead.companyName.toLowerCase().replace(/\s+/g, '')}`,
+            followers: this.getSocialFollowers(lead.valueGrade, 'twitter'),
+            verified: false,
+          } : undefined,
+          instagram: lead.persona === 'P4' || lead.persona === 'P5' ? {
+            url: `https://instagram.com/${lead.companyName.toLowerCase().replace(/\s+/g, '')}`,
+            followers: this.getSocialFollowers(lead.valueGrade, 'instagram'),
+            verified: false,
+          } : undefined,
+        },
+        product: {
+          mainCategories: this.getMainCategories(lead.persona),
+          businessType: personaDef.description,
+        },
+      },
+      dimension2: {
+        contact: {
+          name: lead.contactName,
+          title: this.getContactTitle(lead.persona),
+          email: lead.contactEmail,
+          authority: this.getAuthority(lead.persona, lead.valueGrade),
+        },
+        background: this.getContactBackground(lead),
+        communication: this.getCommunicationStyle(lead.persona),
+      },
+      dimension3: {
+        needs: {
+          details: this.getNeedsDetails(lead),
+          orderType: this.getOrderType(lead.persona),
+          usage: this.getUsage(lead.persona),
+        },
+        budget: {
+          amount: `$${lead.estimatedOrderValue.toLocaleString()}`,
+          sensitivity: lead.valueGrade === 'S' ? '低' : lead.valueGrade === 'A' ? '中' : '高',
+        },
+        time: {
+          delivery: this.getDeliveryTime(lead.persona),
+          urgency: lead.valueGrade === 'S' ? '高' : lead.valueGrade === 'A' ? '中' : '低',
+        },
+        competition: {
+          currentSupplier: '行业头部品牌(推测)',
+        },
+      },
+      rating: {
+        overallScore: score,
+        level: lead.valueGrade,
+      },
+      risk: {
+        level: riskLevel,
+        factors: this.getRiskFactors(lead),
+      },
+      strategy: {
+        approach: personaDef.salesStrategy,
+        actions: this.getStrategyActions(lead),
+        timeline: this.getTimeline(lead.valueGrade),
+      },
+    };
+
+    return report;
+  }
+
+  private getConclusion(lead: Lead): string {
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+    if (lead.valueGrade === 'S') {
+      return `${lead.companyName} 是一家${personaDef.name},需求明确、规模可观,建议立即跟进,重点强调${personaDef.salesStrategy}。`;
+    } else if (lead.valueGrade === 'A') {
+      return `${lead.companyName} 是潜在的${personaDef.name}客户,有稳定渠道,建议3天内跟进,发送${personaDef.firstAction}。`;
+    } else if (lead.valueGrade === 'B') {
+      return `${lead.companyName} 有潜力但需求尚不明确,建议7天内跟进,先发产品目录探需求。`;
+    } else {
+      return `${lead.companyName} 线索质量较低,建议归档观察,定期群发营销邮件。`;
+    }
+  }
+
+  private getRegYear(grade: ValueGrade): string {
+    const years: Record<ValueGrade, string> = {
+      S: '2010-2015',
+      A: '2015-2020',
+      B: '2020-2023',
+      C: '未知',
+    };
+    return years[grade];
+  }
+
+  private getEmployeeCount(grade: ValueGrade): number {
+    const counts: Record<ValueGrade, number> = {
+      S: 250,
+      A: 120,
+      B: 30,
+      C: 5,
+    };
+    return counts[grade];
+  }
+
+  private getRevenue(grade: ValueGrade): string {
+    const revenues: Record<ValueGrade, string> = {
+      S: '50M-100M',
+      A: '10M-50M',
+      B: '1M-10M',
+      C: '<1M',
+    };
+    return revenues[grade];
+  }
+
+  private getSocialFollowers(grade: ValueGrade, platform: string): number {
+    const multipliers: Record<string, number> = {
+      linkedin: 1,
+      twitter: 0.3,
+      instagram: 0.5,
+    };
+    const base = grade === 'S' ? 50000 : grade === 'A' ? 20000 : grade === 'B' ? 5000 : 500;
+    return Math.floor(base * (multipliers[platform] || 1));
+  }
+
+  private getMainCategories(persona: CustomerPersona): string[] {
+    const categories: Record<CustomerPersona, string[]> = {
+      P1: ['医疗耗材', '急救用品', '医疗器械'],
+      P2: ['工业安全', '劳保用品', 'PPE'],
+      P3: ['应急物资', '救灾装备', '医疗设备'],
+      P4: ['户外装备', '运动用品', '定制产品'],
+      P5: ['零售商品', '电商产品', '消费品'],
+      P6: ['杂货', '通用产品'],
+    };
+    return categories[persona];
+  }
+
+  private getContactTitle(persona: CustomerPersona): string {
+    const titles: Record<CustomerPersona, string> = {
+      P1: '采购经理',
+      P2: '采购负责人',
+      P3: '采购官员',
+      P4: '产品开发经理',
+      P5: '采购专员',
+      P6: '联系人',
+    };
+    return titles[persona];
+  }
+
+  private getAuthority(persona: CustomerPersona, grade: ValueGrade): string {
+    if (grade === 'S') return '高(有决策权)';
+    if (grade === 'A') return '中(可影响决策)';
+    return '低(需向上级汇报)';
+  }
+
+  private getContactBackground(lead: Lead): string {
+    return `在${lead.companyName}担任${this.getContactTitle(lead.persona)},负责${PERSONA_DEFINITIONS[lead.persona].description}相关采购业务。`;
+  }
+
+  private getCommunicationStyle(persona: CustomerPersona): string {
+    const styles: Record<CustomerPersona, string> = {
+      P1: '专业、注重认证和资质',
+      P2: '务实、关注合规和成本',
+      P3: '正式、遵循采购流程',
+      P4: '注重设计和品牌调性',
+      P5: '快速决策、关注利润率',
+      P6: '信息有限',
+    };
+    return styles[persona];
+  }
+
+  private getNeedsDetails(lead: Lead): string {
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+    return `需要${personaDef.description}相关的${lead.quickScreenResult.suggestedProducts.map(p => p.name).join('、')}等产品。`;
+  }
+
+  private getOrderType(persona: CustomerPersona): string {
+    const types: Record<CustomerPersona, string> = {
+      P1: '年度采购合同',
+      P2: '批量订单+复购',
+      P3: '招标采购',
+      P4: 'OEM定制订单',
+      P5: '小批量试单',
+      P6: '不确定',
+    };
+    return types[persona];
+  }
+
+  private getUsage(persona: CustomerPersona): string {
+    const usages: Record<CustomerPersona, string> = {
+      P1: '分销给医院和诊所',
+      P2: '供应给企业客户',
+      P3: '应急储备和救灾使用',
+      P4: '零售销售',
+      P5: '线上销售',
+      P6: '未知',
+    };
+    return usages[persona];
+  }
+
+  private getDeliveryTime(persona: CustomerPersona): string {
+    const times: Record<CustomerPersona, string> = {
+      P1: '30-45天',
+      P2: '30-60天',
+      P3: '60-90天',
+      P4: '45-60天(含打样)',
+      P5: '15-30天',
+      P6: '不确定',
+    };
+    return times[persona];
+  }
+
+  private getRiskFactors(lead: Lead): string[] {
+    const factors: string[] = [];
+    if (lead.valueGrade === 'C') {
+      factors.push('线索质量低,需求不明确');
+      factors.push('公司规模小,采购能力有限');
+    } else if (lead.persona === 'P3') {
+      factors.push('政府采购流程复杂,周期长');
+      factors.push('需要准备大量资质文件');
+    } else if (lead.persona === 'P4') {
+      factors.push('定制化需求高,打样成本需考虑');
+      factors.push('品牌调性要求严格');
+    } else {
+      factors.push('无明显风险因素');
+    }
+    return factors;
+  }
+
+  private getStrategyActions(lead: Lead): string[] {
+    const personaDef = PERSONA_DEFINITIONS[lead.persona];
+    const actions: string[] = [
+      personaDef.firstAction,
+      `重点强调:${personaDef.salesStrategy}`,
+    ];
+    if (lead.valueGrade === 'S') {
+      actions.push('24小时内发送开发信和产品目录');
+      actions.push('提议安排视频会议深入沟通');
+    } else if (lead.valueGrade === 'A') {
+      actions.push('3天内发送开发信');
+      actions.push('附上相关认证文件');
+    }
+    return actions;
+  }
+
+  private getTimeline(grade: ValueGrade): string {
+    const timelines: Record<ValueGrade, string> = {
+      S: '24小时内首次接触 → 1周内报价 → 2-3周内样品确认 → 1-2个月内下单',
+      A: '3天内首次接触 → 2周内报价 → 1个月内样品确认 → 2-3个月内下单',
+      B: '7天内首次接触 → 1个月内报价 → 2-3个月内样品确认 → 3-6个月内下单',
+      C: '归档观察,定期群发营销邮件',
+    };
+    return timelines[grade];
+  }
 }
 }

+ 141 - 9
lead-discovery/src/app/services/quick-screen.service.ts

@@ -1,23 +1,66 @@
 import { Injectable } from '@angular/core';
 import { Injectable } from '@angular/core';
 import { MockDataService } from './mock-data.service';
 import { MockDataService } from './mock-data.service';
+import { EntityResolverService } from './entity-resolver.service';
+import { AiScreenService } from './ai-screen.service';
 import {
 import {
   QuickScreenResult, CustomerCategory, ValueGrade, CustomerPersona,
   QuickScreenResult, CustomerCategory, ValueGrade, CustomerPersona,
-  Lead, Email, PERSONA_DEFINITIONS
+  Lead, Email, PERSONA_DEFINITIONS, BatchInputItem, EntityGroup
 } from '../models/lead.model';
 } from '../models/lead.model';
 
 
+export interface CreateLeadResult {
+  lead: Lead;
+  isDuplicate: boolean;
+  existingEntity?: EntityGroup;
+  matchInfo?: string;
+}
+
+export interface BatchScreenResult {
+  entities: Array<{
+    entity: EntityGroup;
+    screenResult: QuickScreenResult;
+    isDuplicate: boolean;
+    matchInfo?: string;
+  }>;
+  totalProcessed: number;
+  newEntities: number;
+  duplicateEntities: number;
+}
+
 /**
 /**
  * 第一阶段:快速筛选服务
  * 第一阶段:快速筛选服务
- * 模拟 域名提取 → 首页快照 → AI分类 的流程
+ *
+ * 支持两种模式:
+ * - AI 模式(默认): 调用 AI 模型进行画像分类和价值评估
+ * - 规则模式(降级): AI 不可用时使用关键词匹配规则
  */
  */
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
 export class QuickScreenService {
 export class QuickScreenService {
 
 
-  constructor(private mockData: MockDataService) {}
+  /** true = 调用 fmode-ng completionJSON,false = 规则引擎降级 */
+  useAI = true;
+
+  constructor(
+    private mockData: MockDataService,
+    private entityResolver: EntityResolverService,
+    private aiScreenService: AiScreenService,
+  ) {}
 
 
   /**
   /**
-   * 执行快速筛选(模拟)
+   * 执行快速筛选
+   * AI 模式:调用 AI 模型分析邮件内容 + 联网背调
+   * 规则模式:关键词匹配(降级)
    */
    */
   async screenEmail(email: Email): Promise<QuickScreenResult> {
   async screenEmail(email: Email): Promise<QuickScreenResult> {
+    if (this.useAI) {
+      try {
+        return await this.aiScreenService.screenWithAI(
+          '', email.from, email.body, email.subject,
+        );
+      } catch (err) {
+        console.warn('[QuickScreen] AI 筛查失败,降级为规则引擎:', err);
+      }
+    }
+
     const start = Date.now();
     const start = Date.now();
     const domain = this.extractDomain(email.from);
     const domain = this.extractDomain(email.from);
     await this.simulateDelay(1500);
     await this.simulateDelay(1500);
@@ -28,8 +71,24 @@ export class QuickScreenService {
 
 
   /**
   /**
    * 手动录入快速筛选
    * 手动录入快速筛选
+   * AI 模式:调用 AI 模型分析公司名+邮箱 + 联网背调
+   * 规则模式:关键词匹配(降级)
    */
    */
-  async screenManual(companyName: string, emailAddress: string): Promise<QuickScreenResult> {
+  async screenManual(
+    companyName: string,
+    emailAddress: string,
+    onProgress?: (p: { progress: number; message: string }) => void,
+  ): Promise<QuickScreenResult> {
+    if (this.useAI) {
+      try {
+        return await this.aiScreenService.screenWithAI(
+          companyName, emailAddress, '', '', onProgress,
+        );
+      } catch (err) {
+        console.warn('[QuickScreen] AI 筛查失败,降级为规则引擎:', err);
+      }
+    }
+
     const start = Date.now();
     const start = Date.now();
     const domain = emailAddress ? this.extractDomain(emailAddress) : '';
     const domain = emailAddress ? this.extractDomain(emailAddress) : '';
     await this.simulateDelay(1200);
     await this.simulateDelay(1200);
@@ -40,9 +99,9 @@ export class QuickScreenService {
   }
   }
 
 
   /**
   /**
-   * 从筛选结果创建Lead
+   * 从筛选结果创建Lead(带查重)
    */
    */
-  createLeadFromScreen(screen: QuickScreenResult, email?: Email): Lead {
+  createLeadFromScreen(screen: QuickScreenResult, email?: Email): CreateLeadResult {
     const now = new Date();
     const now = new Date();
     const nextDate = new Date();
     const nextDate = new Date();
 
 
@@ -54,12 +113,19 @@ export class QuickScreenService {
     }
     }
 
 
     const personaDef = PERSONA_DEFINITIONS[screen.persona];
     const personaDef = PERSONA_DEFINITIONS[screen.persona];
+    const contactEmail = email?.from || '';
+    const domain = screen.domain || this.extractDomain(contactEmail);
 
 
-    return {
+    const entityResult = this.entityResolver.resolveEntity(
+      domain, screen.companyName, contactEmail,
+      email?.fromName || '', email ? '邮件' : '手动录入',
+    );
+
+    const lead: Lead = {
       id: 'l-' + Date.now(),
       id: 'l-' + Date.now(),
       leadNumber: 'LD-' + Date.now(),
       leadNumber: 'LD-' + Date.now(),
       contactName: email?.fromName || '',
       contactName: email?.fromName || '',
-      contactEmail: email?.from || '',
+      contactEmail,
       companyName: screen.companyName,
       companyName: screen.companyName,
       country: screen.country,
       country: screen.country,
       source: email ? 'email' : 'manual',
       source: email ? 'email' : 'manual',
@@ -79,8 +145,74 @@ export class QuickScreenService {
       followUpNotes: '',
       followUpNotes: '',
       matchedProductSKUs: screen.suggestedProducts.map(p => p.sku),
       matchedProductSKUs: screen.suggestedProducts.map(p => p.sku),
       estimatedOrderValue: this.estimateOrderValue(screen.valueGrade, screen.customerCategory),
       estimatedOrderValue: this.estimateOrderValue(screen.valueGrade, screen.customerCategory),
+      hasBackgroundCheck: false,
+      entityId: entityResult.entity.entityId,
+      domainKey: domain,
+      emailKey: contactEmail,
+      relatedLeadIds: entityResult.entity.existingLeadIds.filter(id => id !== 'l-' + Date.now()),
+      isEntityPrimary: entityResult.entity.isNew,
       createdAt: now,
       createdAt: now,
     };
     };
+
+    return {
+      lead,
+      isDuplicate: entityResult.isDuplicate,
+      existingEntity: entityResult.isDuplicate ? entityResult.entity : undefined,
+      matchInfo: entityResult.matchInfo,
+    };
+  }
+
+  /**
+   * 批量筛查:接收解析后的 BatchInputItem[],并行执行筛查并自动归并到主体
+   * AI 模式下逐个调用 AI 模型,降级模式下使用规则引擎
+   */
+  async screenBatch(items: BatchInputItem[]): Promise<BatchScreenResult> {
+    const entityMap = new Map<string, { entity: EntityGroup; screenResult: QuickScreenResult; isDuplicate: boolean; matchInfo?: string }>();
+
+    for (const item of items) {
+      const domain = item.parsedDomain || (item.parsedEmail ? this.extractDomain(item.parsedEmail) : '');
+      const companyName = item.parsedCompanyName || '';
+
+      const entityResult = this.entityResolver.resolveEntity(
+        domain, companyName, item.parsedEmail || '',
+        item.parsedContactName || '', item.parseSource,
+      );
+
+      if (!entityMap.has(entityResult.entity.entityId)) {
+        let screenResult: QuickScreenResult;
+
+        if (this.useAI) {
+          try {
+            screenResult = await this.aiScreenService.screenWithAI(
+              companyName, item.parsedEmail || '', '', '',
+            );
+          } catch {
+            screenResult = this.classifyByDomain(domain, companyName, '');
+            screenResult.companyName = companyName || screenResult.companyName;
+          }
+        } else {
+          await this.simulateDelay(200);
+          screenResult = this.classifyByDomain(domain, companyName, '');
+          screenResult.companyName = companyName || screenResult.companyName;
+        }
+
+        entityMap.set(entityResult.entity.entityId, {
+          entity: entityResult.entity,
+          screenResult,
+          isDuplicate: entityResult.isDuplicate,
+          matchInfo: entityResult.matchInfo,
+        });
+      }
+    }
+
+    const entities = Array.from(entityMap.values());
+
+    return {
+      entities,
+      totalProcessed: items.length,
+      newEntities: entities.filter(e => !e.isDuplicate).length,
+      duplicateEntities: entities.filter(e => e.isDuplicate).length,
+    };
   }
   }
 
 
   // ===== 内部方法 =====
   // ===== 内部方法 =====

+ 2 - 2
lead-discovery/src/styles.scss

@@ -80,9 +80,9 @@ html, body {
 
 
 /* ===== Utility classes ===== */
 /* ===== Utility classes ===== */
 .page-container {
 .page-container {
-  max-width: 1400px;
+  max-width: 1800px;
   margin: 0 auto;
   margin: 0 auto;
-  padding: 24px;
+  padding: 24px 32px;
 }
 }
 
 
 .card {
 .card {

Некоторые файлы не были показаны из-за большого количества измененных файлов