dashboard.component.html 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. <div class="page-container">
  2. <!-- Header -->
  3. <div class="page-header">
  4. <div>
  5. <h1><mat-icon>auto_awesome</mat-icon> 线索获取工作台</h1>
  6. <p class="subtitle">AI 每日自动处理新线索,您只需验证结果</p>
  7. </div>
  8. <div class="header-actions">
  9. <button mat-stroked-button class="inbox-btn" (click)="openInbox()">
  10. <mat-icon>inbox</mat-icon>
  11. 收件箱
  12. </button>
  13. <div class="header-date">
  14. <mat-icon>today</mat-icon>
  15. {{ today | date:'yyyy-MM-dd EEEE' }}
  16. </div>
  17. </div>
  18. </div>
  19. <!-- Stats Cards -->
  20. <div class="stats-row">
  21. <div class="stat-card new">
  22. <div class="stat-icon-wrap"><mat-icon>fiber_new</mat-icon></div>
  23. <div class="stat-info">
  24. <span class="stat-value">{{ latestBatch?.newLeadsFound || 0 }}</span>
  25. <span class="stat-label">今日新线索</span>
  26. </div>
  27. </div>
  28. <div class="stat-card pending">
  29. <div class="stat-icon-wrap"><mat-icon>pending_actions</mat-icon></div>
  30. <div class="stat-info">
  31. <span class="stat-value">{{ pendingCount }}</span>
  32. <span class="stat-label">待验证</span>
  33. </div>
  34. </div>
  35. <div class="stat-card verified">
  36. <div class="stat-icon-wrap"><mat-icon>verified</mat-icon></div>
  37. <div class="stat-info">
  38. <span class="stat-value">{{ verifiedCount }}</span>
  39. <span class="stat-label">已确认</span>
  40. </div>
  41. </div>
  42. </div>
  43. <!-- AI Batch Status -->
  44. <div class="batch-status card" *ngIf="latestBatch">
  45. <div class="batch-header">
  46. <mat-icon>smart_toy</mat-icon>
  47. <span>AI 今日批次</span>
  48. <span class="batch-time">{{ latestBatch.runAt | date:'HH:mm' }} 运行</span>
  49. <span class="batch-badge" [class]="'status-' + latestBatch.status">
  50. {{ latestBatch.status === 'completed' ? '✅ 已完成' : latestBatch.status === 'running' ? '⏳ 运行中' : '❌ 失败' }}
  51. </span>
  52. </div>
  53. <div class="batch-stats">
  54. <span>处理 <strong>{{ latestBatch.totalProcessed }}</strong> 条</span>
  55. <span>发现 <strong>{{ latestBatch.newLeadsFound }}</strong> 条新线索</span>
  56. <span>待验证 <strong>{{ latestBatch.pendingVerification }}</strong></span>
  57. </div>
  58. </div>
  59. <!-- ===== Main Tab Area ===== -->
  60. <mat-tab-group class="main-tabs" animationDuration="200ms" [(selectedIndex)]="activeTab">
  61. <!-- Tab 1: 待验证线索 -->
  62. <mat-tab>
  63. <ng-template mat-tab-label>
  64. <mat-icon>assignment</mat-icon>
  65. <span class="tab-label-text">待验证线索</span>
  66. <span class="tab-count" *ngIf="pendingLeads.length > 0">{{ pendingLeads.length }}</span>
  67. </ng-template>
  68. <div class="tab-content">
  69. <div class="content-grid">
  70. <!-- Left: Pending Verification List -->
  71. <div class="left-col">
  72. <div class="section-card card">
  73. <div class="section-header">
  74. <mat-icon>assignment</mat-icon>
  75. <h3>AI 发现 · 需要您验证</h3>
  76. <span class="count-badge">{{ pendingLeads.length }}</span>
  77. </div>
  78. <div class="lead-verify-list">
  79. <div class="verify-card" *ngFor="let lead of pendingLeads"
  80. [class]="'persona-border-' + lead.persona.toLowerCase()">
  81. <div class="verify-top">
  82. <div class="verify-avatar" [style.background]="getPersonaColor(lead.persona)">
  83. <mat-icon>{{ getPersonaIcon(lead.persona) }}</mat-icon>
  84. </div>
  85. <div class="verify-info">
  86. <div class="verify-company">{{ lead.companyName }}</div>
  87. <div class="verify-tags">
  88. <span class="persona-tag" [style.background]="getPersonaBg(lead.persona)" [style.color]="getPersonaColor(lead.persona)">
  89. {{ lead.persona }}-{{ lead.personaLabel }}
  90. </span>
  91. <span class="grade-tag" [class]="'grade-' + lead.valueGrade.toLowerCase()">
  92. {{ lead.valueGrade }}级
  93. </span>
  94. <span class="flag">{{ getFlag(lead.country) }}</span>
  95. </div>
  96. </div>
  97. </div>
  98. <div class="verify-ai-reason">
  99. <mat-icon>psychology</mat-icon>
  100. <span>AI判断:{{ lead.personaLabel }} · {{ getPersonaStrategy(lead.persona) }}</span>
  101. </div>
  102. <div class="verify-keywords" *ngIf="lead.quickScreenResult.matchedKeywords?.length">
  103. <span class="keyword-chip" *ngFor="let kw of lead.quickScreenResult.matchedKeywords">{{ kw }}</span>
  104. </div>
  105. <div class="verify-actions">
  106. <button mat-raised-button color="primary" class="action-btn" (click)="confirmLead(lead)">
  107. <mat-icon>check</mat-icon> 确认
  108. </button>
  109. <button mat-stroked-button class="action-btn" (click)="editLead(lead)">
  110. <mat-icon>edit</mat-icon> 修改
  111. </button>
  112. <button mat-stroked-button color="warn" class="action-btn" (click)="dismissLead(lead)">
  113. <mat-icon>close</mat-icon> 无效
  114. </button>
  115. <button mat-icon-button [matTooltip]="'查看详情'" (click)="openLead(lead)">
  116. <mat-icon>open_in_new</mat-icon>
  117. </button>
  118. <button mat-icon-button [matTooltip]="'查看原始邮件'" (click)="showEmailDrawer(lead)"
  119. *ngIf="getEmailForLead(lead)">
  120. <mat-icon>email</mat-icon>
  121. </button>
  122. </div>
  123. </div>
  124. <div class="empty-verify" *ngIf="pendingLeads.length === 0">
  125. <mat-icon>celebration</mat-icon>
  126. <p>所有线索已验证完毕 🎉</p>
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. <!-- Right: Persona Distribution + Activity + Quick Links -->
  132. <div class="right-col">
  133. <div class="section-card card">
  134. <div class="section-header">
  135. <mat-icon>donut_large</mat-icon>
  136. <h3>本周画像分布</h3>
  137. </div>
  138. <div class="persona-dist-list">
  139. <div class="dist-row" *ngFor="let p of personaDistribution">
  140. <div class="dist-label">
  141. <mat-icon [style.color]="p.color">{{ p.icon }}</mat-icon>
  142. <span>{{ p.id }}-{{ p.name }}</span>
  143. </div>
  144. <div class="dist-bar-wrap">
  145. <div class="dist-bar" [style.width.%]="p.percent" [style.background]="p.color"></div>
  146. </div>
  147. <span class="dist-count">{{ p.count }} ({{ p.percent }}%)</span>
  148. </div>
  149. </div>
  150. </div>
  151. <div class="section-card card">
  152. <div class="section-header">
  153. <mat-icon>history</mat-icon>
  154. <h3>最近活动</h3>
  155. </div>
  156. <div class="activity-list">
  157. <div class="activity-item" *ngFor="let a of recentActivities">
  158. <div class="activity-dot" [style.background]="a.color"></div>
  159. <div class="activity-content">
  160. <span class="activity-text">{{ a.text }}</span>
  161. <span class="activity-time">{{ a.time }}</span>
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. </div> <!-- /right-col -->
  167. </div> <!-- /content-grid -->
  168. </div> <!-- /tab-content -->
  169. </mat-tab>
  170. <!-- Tab 2: 导入线索 -->
  171. <mat-tab>
  172. <ng-template mat-tab-label>
  173. <mat-icon>cloud_upload</mat-icon>
  174. <span class="tab-label-text">导入线索</span>
  175. <span class="tab-count new" *ngIf="batchScreenResult">{{ batchScreenResult.entities.length }}</span>
  176. </ng-template>
  177. <div class="tab-content">
  178. <!-- ===== Source Selector ===== -->
  179. <div class="import-source-tabs">
  180. <button class="source-tab" [class.active]="importSource === 'email'" (click)="importSource = 'email'">
  181. <mat-icon>email</mat-icon>
  182. <span>邮件导入</span>
  183. </button>
  184. <button class="source-tab" [class.active]="importSource === 'exhibition'" (click)="importSource = 'exhibition'">
  185. <mat-icon>dynamic_feed</mat-icon>
  186. <span>批量筛查</span>
  187. <span class="source-tab-badge new" *ngIf="batchScreenResult">{{ batchScreenResult.entities.length }}</span>
  188. </button>
  189. <button class="source-tab" [class.active]="importSource === 'platform'" (click)="importSource = 'platform'">
  190. <mat-icon>cloud_sync</mat-icon>
  191. <span>平台询盘同步</span>
  192. <span class="source-tab-badge new" *ngIf="!platformSynced">NEW</span>
  193. </button>
  194. </div>
  195. <!-- ===== Source 1: 邮件导入(原有) ===== -->
  196. <div class="import-section card" *ngIf="importSource === 'email'">
  197. <div class="section-title-row">
  198. <mat-icon class="section-icon">email</mat-icon>
  199. <span>导入邮件 / 手动录入</span>
  200. </div>
  201. <!-- EML Import Banner -->
  202. <div class="import-banner" (click)="importFromEmail()" [class.disabled]="isImporting">
  203. <div class="import-drop-zone">
  204. <mat-icon class="upload-icon">cloud_upload</mat-icon>
  205. <div class="drop-text">
  206. <span class="drop-main">点击选择 .eml 邮件文件</span>
  207. <span class="drop-hint">自动提取邮件中的公司信息和产品线索</span>
  208. </div>
  209. <button mat-stroked-button color="accent" class="import-btn" [disabled]="isImporting">
  210. <mat-icon>email</mat-icon>
  211. 邮件导入(推荐)
  212. </button>
  213. </div>
  214. </div>
  215. <!-- Import Progress -->
  216. <div *ngIf="isImporting" class="import-progress-section">
  217. <mat-progress-bar mode="determinate" [value]="importProgress"></mat-progress-bar>
  218. <div class="import-progress-info">
  219. <mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
  220. <span>{{ importStepMessage }}</span>
  221. </div>
  222. </div>
  223. <!-- Linked Email Status -->
  224. <div *ngIf="importedEmailId" class="linked-status">
  225. <mat-icon>link</mat-icon>
  226. <span>已关联邮件数据(ID: {{ importedEmailId }})</span>
  227. <button mat-icon-button class="clear-import-btn" (click)="clearImport()" matTooltip="清除导入">
  228. <mat-icon>close</mat-icon>
  229. </button>
  230. </div>
  231. <!-- Auto-filled Form -->
  232. <div class="import-form-grid">
  233. <div class="form-item">
  234. <div class="label">公司名称<span class="required">*</span></div>
  235. <input class="form-input" [(ngModel)]="importData.companyName" placeholder="请输入公司名称">
  236. </div>
  237. <div class="form-item">
  238. <div class="label">关键联系人</div>
  239. <input class="form-input" [(ngModel)]="importData.contactName" placeholder="请输入客户名称,如:John Smith">
  240. <div *ngIf="importData.roleDescription" class="hint">
  241. <mat-icon class="hint-icon">work</mat-icon>
  242. <span>参考职位:{{ importData.roleDescription }}</span>
  243. </div>
  244. </div>
  245. <div class="form-item">
  246. <div class="label">邮箱地址</div>
  247. <input class="form-input" [(ngModel)]="importData.email" placeholder="name&#64;company.com">
  248. </div>
  249. <div class="form-item">
  250. <div class="label">公司官网(选填)</div>
  251. <input class="form-input" [(ngModel)]="importData.website" placeholder="支持从邮箱域名自动填充">
  252. <div class="hint">
  253. <mat-icon class="hint-icon">info</mat-icon>
  254. <span>支持自动从邮箱提取域名,可手动修改</span>
  255. </div>
  256. </div>
  257. </div>
  258. <!-- Product Demands Preview -->
  259. <div *ngIf="importedProducts.length > 0" class="product-demands-section">
  260. <div class="demands-header">
  261. <mat-icon>inventory_2</mat-icon>
  262. <span>提取的产品需求 ({{ importedProducts.length }})</span>
  263. <span class="demands-source-tag" *ngIf="importedAttachmentStats">
  264. 正文 {{ importedAttachmentStats.bodyAnalyzed ? '✓' : '✗' }} ·
  265. 附件 {{ importedAttachmentStats.analyzed }}/{{ importedAttachmentStats.total }}
  266. </span>
  267. </div>
  268. <div class="demands-grid">
  269. <div class="demand-card" *ngFor="let p of importedProducts; let i = index">
  270. <div class="demand-index">{{ i + 1 }}</div>
  271. <div class="demand-info">
  272. <div class="demand-name">{{ p.productName }}</div>
  273. <div class="demand-meta">
  274. <span *ngIf="p.quantity" class="meta-tag">📦 {{ p.quantity }}</span>
  275. <span *ngIf="p.moq" class="meta-tag">MOQ: {{ p.moq }}</span>
  276. <span *ngIf="p.specifications" class="meta-tag">{{ p.specifications }}</span>
  277. </div>
  278. <div class="demand-source">来源: {{ p.source }}</div>
  279. </div>
  280. </div>
  281. </div>
  282. </div>
  283. <!-- Info Box -->
  284. <div class="info-box">
  285. <div class="info-title">
  286. <mat-icon class="info-icon">info</mat-icon>
  287. <span>产品清单来源说明</span>
  288. </div>
  289. <div class="info-content">
  290. <div class="info-item">📧 邮件正文及附件中的产品描述</div>
  291. <div class="info-item">🌐 公司官网的产品页面</div>
  292. </div>
  293. </div>
  294. <!-- Action Buttons -->
  295. <div class="import-actions">
  296. <button mat-raised-button color="primary"
  297. (click)="importAndScreen()"
  298. [disabled]="!importData.companyName || isImporting || isScreening">
  299. <mat-icon>bolt</mat-icon>
  300. 快速筛选并创建线索
  301. </button>
  302. </div>
  303. <!-- Screening Progress -->
  304. <div *ngIf="isScreening" class="screening-progress card">
  305. <mat-progress-spinner mode="indeterminate" diameter="36"></mat-progress-spinner>
  306. <div class="progress-text">
  307. <h4>AI 快速筛选中...</h4>
  308. <p>{{ screeningStep }}</p>
  309. </div>
  310. </div>
  311. <!-- Screen Result -->
  312. <div *ngIf="importScreenResult" class="import-result">
  313. <app-screen-result-card
  314. [result]="importScreenResult"
  315. (createLead)="onCreateImportLead()"
  316. (dismiss)="importScreenResult = null">
  317. </app-screen-result-card>
  318. </div>
  319. </div>
  320. <!-- ===== Source 2: 展会批量导入 & 批量筛查(合并) ===== -->
  321. <div class="import-section card" *ngIf="importSource === 'exhibition'">
  322. <div class="section-title-row">
  323. <mat-icon class="section-icon">groups</mat-icon>
  324. <span>批量线索导入 & 筛查</span>
  325. </div>
  326. <div class="source-description">
  327. <div class="desc-icon-wrap exhibition">
  328. <mat-icon>event</mat-icon>
  329. </div>
  330. <div class="desc-text">
  331. <strong>适用场景</strong>:展会名片、截图、聊天记录、CSV 文件或直接粘贴多条线索信息。
  332. AI 自动解析 → 主体识别去重 → 画像分类 → 价值评级 → 产品匹配 → 生成跟进建议。
  333. </div>
  334. </div>
  335. <!-- ===== 输入区(未筛查时显示) ===== -->
  336. <ng-container *ngIf="!batchScreenResult && !isExhibitionImporting && !exhibitionImported">
  337. <!-- 上传按钮组 -->
  338. <div class="batch-input-methods">
  339. <div class="batch-method-card" (click)="importBatchCSV()">
  340. <mat-icon>upload_file</mat-icon>
  341. <span>CSV / Excel 文件</span>
  342. <small>批量导入表格数据</small>
  343. </div>
  344. <div class="batch-method-card" (click)="importBatchOCR()">
  345. <mat-icon>photo_camera</mat-icon>
  346. <span>截图 / 名片 OCR</span>
  347. <small>上传图片自动识别</small>
  348. </div>
  349. <div class="batch-method-card" (click)="importExhibitionCSV()">
  350. <mat-icon>auto_awesome</mat-icon>
  351. <span>展会模拟导入</span>
  352. <small>模拟大批量 AI 处理</small>
  353. </div>
  354. </div>
  355. <!-- 文本粘贴区 -->
  356. <div class="batch-text-area">
  357. <div class="batch-text-label">
  358. <mat-icon>edit_note</mat-icon>
  359. <span>或直接粘贴线索信息(每行一条,支持 "公司名 邮箱" 或纯邮箱列表)</span>
  360. </div>
  361. <textarea class="batch-textarea"
  362. [(ngModel)]="batchTextInput"
  363. placeholder="示例:&#10;Acme Medical john@acmemedical.com&#10;info@safetyfirst-uk.co.uk&#10;WildGear GmbH alex@wildgear.de&#10;buyer@newcompany.com"
  364. rows="5"></textarea>
  365. <div class="batch-text-actions">
  366. <button mat-raised-button color="primary" (click)="parseBatchText()" [disabled]="!batchTextInput.trim()">
  367. <mat-icon>manage_search</mat-icon> 解析文本
  368. </button>
  369. <button mat-stroked-button (click)="parseBatchEmails()" [disabled]="!batchTextInput.trim()">
  370. <mat-icon>alternate_email</mat-icon> 按邮箱解析
  371. </button>
  372. </div>
  373. </div>
  374. <!-- 解析结果预览表格 -->
  375. <div class="batch-preview" *ngIf="batchParsedItems.length > 0">
  376. <div class="batch-preview-header">
  377. <mat-icon>checklist</mat-icon>
  378. <h4>解析结果预览</h4>
  379. <span class="batch-count">{{ batchParsedItems.length }} 条</span>
  380. <span class="batch-count duplicate">已选 {{ batchSelectedItems.size }} 条</span>
  381. <div class="batch-preview-actions">
  382. <button mat-icon-button matTooltip="全选/取消" (click)="toggleBatchSelectAll()">
  383. <mat-icon>{{ batchSelectedItems.size === batchParsedItems.length ? 'deselect' : 'select_all' }}</mat-icon>
  384. </button>
  385. <button mat-icon-button matTooltip="清空" (click)="clearBatch()">
  386. <mat-icon>delete_sweep</mat-icon>
  387. </button>
  388. </div>
  389. </div>
  390. <div class="batch-table-wrap">
  391. <table class="batch-table">
  392. <thead>
  393. <tr>
  394. <th class="col-check"></th>
  395. <th>公司名</th>
  396. <th>邮箱</th>
  397. <th>域名</th>
  398. <th>联系人</th>
  399. <th>置信度</th>
  400. <th>来源</th>
  401. <th></th>
  402. </tr>
  403. </thead>
  404. <tbody>
  405. <tr *ngFor="let item of batchParsedItems; let i = index"
  406. [class.selected]="batchSelectedItems.has(i)"
  407. [class.low-confidence]="item.parseConfidence < 50">
  408. <td class="col-check">
  409. <input type="checkbox" [checked]="batchSelectedItems.has(i)" (change)="toggleBatchItem(i)">
  410. </td>
  411. <td>{{ item.parsedCompanyName || '—' }}</td>
  412. <td>{{ item.parsedEmail || '—' }}</td>
  413. <td>{{ item.parsedDomain || '—' }}</td>
  414. <td>{{ item.parsedContactName || '—' }}</td>
  415. <td>
  416. <span class="confidence-badge" [class]="getConfidenceClass(item.parseConfidence)">
  417. {{ item.parseConfidence }}%
  418. </span>
  419. </td>
  420. <td><span class="source-tag">{{ item.parseSource }}</span></td>
  421. <td>
  422. <button mat-icon-button matTooltip="移除" (click)="removeBatchItem(i)">
  423. <mat-icon>close</mat-icon>
  424. </button>
  425. </td>
  426. </tr>
  427. </tbody>
  428. </table>
  429. </div>
  430. <div class="batch-execute-bar">
  431. <button mat-raised-button color="primary" class="batch-execute-btn"
  432. (click)="executeBatchScreen()"
  433. [disabled]="batchSelectedItems.size === 0 || batchProcessing">
  434. <mat-icon>bolt</mat-icon>
  435. 批量执行初步筛查({{ batchSelectedItems.size }} 条)
  436. </button>
  437. </div>
  438. </div>
  439. <!-- 说明 -->
  440. <div class="info-box" *ngIf="batchParsedItems.length === 0">
  441. <div class="info-title">
  442. <mat-icon class="info-icon">lightbulb</mat-icon>
  443. <span>AI 批量处理流程</span>
  444. </div>
  445. <div class="info-content">
  446. <div class="info-item">📋 <strong>Step 1</strong>:解析输入(CSV / 名片 OCR / 粘贴文本),提取公司、联系人、邮箱</div>
  447. <div class="info-item">🔄 <strong>Step 2</strong>:主体识别 & 去重 — 域名+公司名归并到同一主体</div>
  448. <div class="info-item">🤖 <strong>Step 3</strong>:AI 画像分类(P1-P6)+ 价值评级(S/A/B/C)</div>
  449. <div class="info-item">📦 <strong>Step 4</strong>:产品匹配 + 差异化信息获取</div>
  450. <div class="info-item">📌 <strong>Step 5</strong>:输出待确认列表,逐条确认后进入深度背调</div>
  451. </div>
  452. </div>
  453. </ng-container>
  454. <!-- ===== 批量筛查进度 ===== -->
  455. <div *ngIf="batchProcessing" class="batch-import-progress">
  456. <div class="batch-progress-header">
  457. <mat-icon class="spin-icon">autorenew</mat-icon>
  458. <strong>AI 批量筛查中...</strong>
  459. <span class="batch-progress-pct">{{ batchProgress }}%</span>
  460. </div>
  461. <mat-progress-bar mode="determinate" [value]="batchProgress" color="primary"></mat-progress-bar>
  462. <div class="batch-progress-steps">
  463. <div class="batch-step" [class.done]="batchProgress >= 10" [class.active]="batchProgress > 0 && batchProgress < 10">
  464. <mat-icon>{{ batchProgress >= 10 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  465. <span>解析输入</span>
  466. </div>
  467. <div class="batch-step" [class.done]="batchProgress >= 30" [class.active]="batchProgress >= 10 && batchProgress < 30">
  468. <mat-icon>{{ batchProgress >= 30 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  469. <span>主体去重</span>
  470. </div>
  471. <div class="batch-step" [class.done]="batchProgress >= 50" [class.active]="batchProgress >= 30 && batchProgress < 50">
  472. <mat-icon>{{ batchProgress >= 50 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  473. <span>AI 画像分类</span>
  474. </div>
  475. <div class="batch-step" [class.done]="batchProgress >= 90" [class.active]="batchProgress >= 50 && batchProgress < 90">
  476. <mat-icon>{{ batchProgress >= 90 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  477. <span>价值评估</span>
  478. </div>
  479. <div class="batch-step" [class.done]="batchProgress >= 100" [class.active]="batchProgress >= 90 && batchProgress < 100">
  480. <mat-icon>{{ batchProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  481. <span>生成结果</span>
  482. </div>
  483. </div>
  484. <div class="batch-progress-detail">{{ batchStepMessage }}</div>
  485. </div>
  486. <!-- ===== 展会模拟导入进度 ===== -->
  487. <div *ngIf="isExhibitionImporting" class="batch-import-progress">
  488. <div class="batch-progress-header">
  489. <mat-icon class="spin-icon">autorenew</mat-icon>
  490. <strong>AI 批量分析中...</strong>
  491. <span class="batch-progress-pct">{{ exhibitionProgress }}%</span>
  492. </div>
  493. <mat-progress-bar mode="determinate" [value]="exhibitionProgress" color="primary"></mat-progress-bar>
  494. <div class="batch-progress-steps">
  495. <div class="batch-step" [class.done]="exhibitionProgress >= 15" [class.active]="exhibitionProgress > 0 && exhibitionProgress < 15">
  496. <mat-icon>{{ exhibitionProgress >= 15 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  497. <span>解析文件</span>
  498. </div>
  499. <div class="batch-step" [class.done]="exhibitionProgress >= 40" [class.active]="exhibitionProgress >= 15 && exhibitionProgress < 40">
  500. <mat-icon>{{ exhibitionProgress >= 40 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  501. <span>AI 画像分类</span>
  502. </div>
  503. <div class="batch-step" [class.done]="exhibitionProgress >= 65" [class.active]="exhibitionProgress >= 40 && exhibitionProgress < 65">
  504. <mat-icon>{{ exhibitionProgress >= 65 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  505. <span>价值评级</span>
  506. </div>
  507. <div class="batch-step" [class.done]="exhibitionProgress >= 85" [class.active]="exhibitionProgress >= 65 && exhibitionProgress < 85">
  508. <mat-icon>{{ exhibitionProgress >= 85 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  509. <span>产品匹配</span>
  510. </div>
  511. <div class="batch-step" [class.done]="exhibitionProgress >= 100" [class.active]="exhibitionProgress >= 85 && exhibitionProgress < 100">
  512. <mat-icon>{{ exhibitionProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  513. <span>生成跟进建议</span>
  514. </div>
  515. </div>
  516. <div class="batch-progress-detail">{{ exhibitionStepMessage }}</div>
  517. </div>
  518. <!-- ===== 展会模拟导入结果 ===== -->
  519. <div *ngIf="exhibitionImported && !isExhibitionImporting" class="batch-import-result">
  520. <div class="batch-result-header">
  521. <mat-icon>celebration</mat-icon>
  522. <strong>批量线索导入完成</strong>
  523. </div>
  524. <div class="batch-result-stats">
  525. <div class="batch-stat-item">
  526. <span class="batch-stat-num">{{ exhibitionResult.total }}</span>
  527. <span class="batch-stat-label">导入总数</span>
  528. </div>
  529. <div class="batch-stat-item highlight-s">
  530. <span class="batch-stat-num">{{ exhibitionResult.sCount }}</span>
  531. <span class="batch-stat-label">S 级线索</span>
  532. </div>
  533. <div class="batch-stat-item highlight-a">
  534. <span class="batch-stat-num">{{ exhibitionResult.aCount }}</span>
  535. <span class="batch-stat-label">A 级线索</span>
  536. </div>
  537. <div class="batch-stat-item">
  538. <span class="batch-stat-num">{{ exhibitionResult.bCount }}</span>
  539. <span class="batch-stat-label">B 级线索</span>
  540. </div>
  541. <div class="batch-stat-item dim">
  542. <span class="batch-stat-num">{{ exhibitionResult.cCount }}</span>
  543. <span class="batch-stat-label">C 级/无效</span>
  544. </div>
  545. </div>
  546. <div class="batch-result-personas">
  547. <span class="persona-chip" *ngFor="let p of exhibitionResult.personas">
  548. {{ p.label }} × {{ p.count }}
  549. </span>
  550. </div>
  551. <div class="batch-result-actions">
  552. <button mat-raised-button color="primary" (click)="router.navigate(['/list'])">
  553. <mat-icon>visibility</mat-icon> 查看线索看板
  554. </button>
  555. <button mat-stroked-button (click)="activeTab = 0">
  556. <mat-icon>assignment</mat-icon> 去验证 ({{ exhibitionResult.sCount + exhibitionResult.aCount }})
  557. </button>
  558. </div>
  559. </div>
  560. <!-- ===== 第一轮筛查结果 — 待确认列表 ===== -->
  561. <div *ngIf="batchScreenResult && !batchProcessing" class="batch-screen-result-section">
  562. <div class="section-header">
  563. <mat-icon>fact_check</mat-icon>
  564. <h3>第一轮筛查结果 — 待确认</h3>
  565. <span class="section-hint">请确认 AI 的判断结果,确认后可进入第二轮深度背调</span>
  566. </div>
  567. <div class="batch-summary-bar">
  568. <div class="summary-item">
  569. <strong>{{ batchScreenResult.entities.length }}</strong>
  570. <span>个主体</span>
  571. </div>
  572. <div class="summary-item new-entities">
  573. <strong>{{ batchScreenResult.newEntities }}</strong>
  574. <span>新主体</span>
  575. </div>
  576. <div class="summary-item duplicate-entities">
  577. <strong>{{ batchScreenResult.duplicateEntities }}</strong>
  578. <span>已有主体</span>
  579. </div>
  580. <div class="summary-actions">
  581. <button mat-stroked-button (click)="clearBatch()">
  582. <mat-icon>refresh</mat-icon> 重新筛查
  583. </button>
  584. </div>
  585. </div>
  586. <div class="batch-entity-list">
  587. <div class="batch-entity-card" *ngFor="let item of batchScreenResult.entities"
  588. [class.is-duplicate]="item.isDuplicate">
  589. <div class="entity-card-top">
  590. <div class="entity-avatar" [style.background]="getPersonaColor(item.screenResult.persona)">
  591. <mat-icon>{{ getPersonaIcon(item.screenResult.persona) }}</mat-icon>
  592. </div>
  593. <div class="entity-info">
  594. <div class="entity-company">{{ item.entity.primaryCompanyName || item.screenResult.companyName }}</div>
  595. <div class="entity-tags">
  596. <span class="persona-tag" [style.background]="getPersonaBg(item.screenResult.persona)" [style.color]="getPersonaColor(item.screenResult.persona)">
  597. {{ item.screenResult.persona }}-{{ item.screenResult.personaLabel }}
  598. </span>
  599. <span class="grade-tag" [class]="getGradeClass(item.screenResult.valueGrade)">
  600. {{ item.screenResult.valueGrade }}级
  601. </span>
  602. <span class="flag" *ngIf="item.screenResult.country !== 'Unknown'">{{ getFlag(item.screenResult.country) }}</span>
  603. </div>
  604. </div>
  605. <div class="entity-status">
  606. <span class="entity-badge new" *ngIf="!item.isDuplicate">新主体</span>
  607. <span class="entity-badge duplicate" *ngIf="item.isDuplicate">
  608. 已有记录
  609. <span class="dup-count" *ngIf="item.entity.existingLeadIds.length">({{ item.entity.existingLeadIds.length }}条)</span>
  610. </span>
  611. </div>
  612. </div>
  613. <div class="entity-duplicate-warn" *ngIf="item.isDuplicate">
  614. <mat-icon>info</mat-icon>
  615. <span>{{ item.matchInfo }}</span>
  616. </div>
  617. <div class="entity-details">
  618. <div class="entity-detail-row" *ngIf="item.entity.primaryDomain">
  619. <mat-icon>language</mat-icon>
  620. <span>{{ item.entity.primaryDomain }}</span>
  621. </div>
  622. <div class="entity-detail-row" *ngIf="item.entity.contacts.length > 0">
  623. <mat-icon>people</mat-icon>
  624. <span>联系人: {{ item.entity.contacts.length }}
  625. <span class="contact-list">({{ getContactSummary(item.entity.contacts) }})</span>
  626. </span>
  627. </div>
  628. </div>
  629. <div class="entity-keywords" *ngIf="item.screenResult.matchedKeywords?.length">
  630. <span class="keyword-chip" *ngFor="let kw of item.screenResult.matchedKeywords">{{ kw }}</span>
  631. </div>
  632. <div class="entity-actions">
  633. <button mat-raised-button color="primary" class="action-btn">
  634. <mat-icon>check</mat-icon> 确认
  635. </button>
  636. <button mat-stroked-button class="action-btn">
  637. <mat-icon>edit</mat-icon> 修改画像
  638. </button>
  639. <button mat-stroked-button color="warn" class="action-btn">
  640. <mat-icon>close</mat-icon> 归档
  641. </button>
  642. </div>
  643. </div>
  644. </div>
  645. </div>
  646. </div>
  647. <!-- ===== Source 3: 平台询盘同步 ===== -->
  648. <div class="import-section card" *ngIf="importSource === 'platform'">
  649. <div class="section-title-row">
  650. <mat-icon class="section-icon">cloud_sync</mat-icon>
  651. <span>B2B 平台询盘自动同步</span>
  652. </div>
  653. <div class="source-description">
  654. <div class="desc-icon-wrap platform">
  655. <mat-icon>language</mat-icon>
  656. </div>
  657. <div class="desc-text">
  658. <strong>典型场景</strong>:系统通过 API 对接阿里巴巴国际站、环球资源、Made-in-China 等 B2B 平台,
  659. 每日定时拉取新询盘,纳入凌晨 AI 批量处理任务。销售上班后直接看到 AI 已分析好的平台线索。
  660. </div>
  661. </div>
  662. <!-- Platform Cards -->
  663. <div class="platform-grid">
  664. <div class="platform-card" *ngFor="let plat of platformList" [class.connected]="plat.connected">
  665. <div class="platform-card-header">
  666. <div class="platform-logo" [style.background]="plat.color">
  667. <mat-icon>{{ plat.icon }}</mat-icon>
  668. </div>
  669. <div class="platform-info">
  670. <div class="platform-name">{{ plat.name }}</div>
  671. <div class="platform-desc">{{ plat.description }}</div>
  672. </div>
  673. <div class="platform-status">
  674. <span class="platform-status-tag" [class.connected]="plat.connected" [class.disconnected]="!plat.connected">
  675. {{ plat.connected ? '✅ 已对接' : '⏳ 待对接' }}
  676. </span>
  677. </div>
  678. </div>
  679. <div class="platform-card-body" *ngIf="plat.connected">
  680. <div class="platform-meta-row">
  681. <mat-icon>schedule</mat-icon>
  682. <span>同步频率:<strong>{{ plat.syncFrequency }}</strong></span>
  683. </div>
  684. <div class="platform-meta-row">
  685. <mat-icon>update</mat-icon>
  686. <span>上次同步:<strong>{{ plat.lastSync }}</strong></span>
  687. </div>
  688. <div class="platform-meta-row">
  689. <mat-icon>leaderboard</mat-icon>
  690. <span>本月拉取:<strong>{{ plat.monthlyCount }} 条询盘</strong></span>
  691. </div>
  692. </div>
  693. <div class="platform-card-body" *ngIf="!plat.connected">
  694. <div class="platform-pending-msg">
  695. <mat-icon>info</mat-icon>
  696. <span>需要在平台开放平台获取 API Key 后对接</span>
  697. </div>
  698. </div>
  699. <div class="platform-card-actions">
  700. <button mat-raised-button [color]="plat.connected ? 'primary' : 'basic'"
  701. (click)="syncPlatform(plat)"
  702. [disabled]="!plat.connected || isPlatformSyncing">
  703. <mat-icon>{{ plat.connected ? 'sync' : 'link' }}</mat-icon>
  704. {{ plat.connected ? '立即同步' : '配置对接' }}
  705. </button>
  706. </div>
  707. </div>
  708. </div>
  709. <!-- Platform Sync Progress -->
  710. <div *ngIf="isPlatformSyncing" class="batch-import-progress platform-sync-progress">
  711. <div class="batch-progress-header">
  712. <mat-icon class="spin-icon">sync</mat-icon>
  713. <strong>正在从 {{ syncingPlatformName }} 拉取询盘...</strong>
  714. <span class="batch-progress-pct">{{ platformSyncProgress }}%</span>
  715. </div>
  716. <mat-progress-bar mode="determinate" [value]="platformSyncProgress" color="accent"></mat-progress-bar>
  717. <div class="batch-progress-steps">
  718. <div class="batch-step" [class.done]="platformSyncProgress >= 20" [class.active]="platformSyncProgress > 0 && platformSyncProgress < 20">
  719. <mat-icon>{{ platformSyncProgress >= 20 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  720. <span>连接平台 API</span>
  721. </div>
  722. <div class="batch-step" [class.done]="platformSyncProgress >= 45" [class.active]="platformSyncProgress >= 20 && platformSyncProgress < 45">
  723. <mat-icon>{{ platformSyncProgress >= 45 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  724. <span>拉取新询盘</span>
  725. </div>
  726. <div class="batch-step" [class.done]="platformSyncProgress >= 70" [class.active]="platformSyncProgress >= 45 && platformSyncProgress < 70">
  727. <mat-icon>{{ platformSyncProgress >= 70 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  728. <span>AI 分析 & 分类</span>
  729. </div>
  730. <div class="batch-step" [class.done]="platformSyncProgress >= 100" [class.active]="platformSyncProgress >= 70 && platformSyncProgress < 100">
  731. <mat-icon>{{ platformSyncProgress >= 100 ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>
  732. <span>入库 & 生成建议</span>
  733. </div>
  734. </div>
  735. <div class="batch-progress-detail">
  736. {{ platformStepMessage }}
  737. </div>
  738. </div>
  739. <!-- Platform Sync Result -->
  740. <div *ngIf="platformSynced && !isPlatformSyncing" class="batch-import-result platform-result">
  741. <div class="batch-result-header">
  742. <mat-icon>cloud_done</mat-icon>
  743. <strong>平台询盘同步完成</strong>
  744. </div>
  745. <div class="batch-result-stats">
  746. <div class="batch-stat-item">
  747. <span class="batch-stat-num">{{ platformResult.total }}</span>
  748. <span class="batch-stat-label">拉取询盘</span>
  749. </div>
  750. <div class="batch-stat-item highlight-s">
  751. <span class="batch-stat-num">{{ platformResult.sCount }}</span>
  752. <span class="batch-stat-label">S 级线索</span>
  753. </div>
  754. <div class="batch-stat-item highlight-a">
  755. <span class="batch-stat-num">{{ platformResult.aCount }}</span>
  756. <span class="batch-stat-label">A 级线索</span>
  757. </div>
  758. <div class="batch-stat-item">
  759. <span class="batch-stat-num">{{ platformResult.bCount }}</span>
  760. <span class="batch-stat-label">B 级线索</span>
  761. </div>
  762. <div class="batch-stat-item dim">
  763. <span class="batch-stat-num">{{ platformResult.duplicates }}</span>
  764. <span class="batch-stat-label">重复/已有</span>
  765. </div>
  766. </div>
  767. <div class="batch-result-actions">
  768. <button mat-raised-button color="primary" (click)="router.navigate(['/list'])">
  769. <mat-icon>visibility</mat-icon> 查看线索看板
  770. </button>
  771. <button mat-stroked-button (click)="activeTab = 0">
  772. <mat-icon>assignment</mat-icon> 去验证
  773. </button>
  774. </div>
  775. </div>
  776. <!-- Platform Schedule Info -->
  777. <div class="platform-schedule-card" *ngIf="!isPlatformSyncing && !platformSynced">
  778. <div class="schedule-header">
  779. <mat-icon>event_repeat</mat-icon>
  780. <strong>自动同步计划</strong>
  781. </div>
  782. <div class="schedule-timeline">
  783. <div class="schedule-item">
  784. <div class="schedule-time">🕛 每日 00:00</div>
  785. <div class="schedule-desc">自动拉取已对接平台的所有新询盘,纳入 AI 凌晨批量处理</div>
  786. </div>
  787. <div class="schedule-item">
  788. <div class="schedule-time">🌅 每日 08:00</div>
  789. <div class="schedule-desc">AI 分析完毕,结果推送到"待验证线索"列表,等待销售确认</div>
  790. </div>
  791. <div class="schedule-item">
  792. <div class="schedule-time">📊 每周一</div>
  793. <div class="schedule-desc">生成本周平台询盘汇总分析报告(画像分布、价值评级、转化建议)</div>
  794. </div>
  795. </div>
  796. </div>
  797. <!-- Platform Info Box -->
  798. <div class="info-box" *ngIf="!isPlatformSyncing && !platformSynced">
  799. <div class="info-title">
  800. <mat-icon class="info-icon">lightbulb</mat-icon>
  801. <span>平台询盘 AI 处理流程</span>
  802. </div>
  803. <div class="info-content">
  804. <div class="info-item">🔗 <strong>Step 1</strong>:通过平台 API 自动拉取过去 24 小时的新询盘</div>
  805. <div class="info-item">📧 <strong>Step 2</strong>:解析询盘内容(产品需求、数量、规格、买家信息)</div>
  806. <div class="info-item">🤖 <strong>Step 3</strong>:AI 画像分类 + 五维评分 + 价值评级</div>
  807. <div class="info-item">🔄 <strong>Step 4</strong>:去重(与现有线索比对邮箱/公司名)</div>
  808. <div class="info-item">📌 <strong>Step 5</strong>:新线索入库,标记"待验证",推送到工作台</div>
  809. </div>
  810. </div>
  811. </div>
  812. </div>
  813. </mat-tab>
  814. </mat-tab-group>
  815. <!-- ===== Email Drawer Overlay ===== -->
  816. <div class="drawer-overlay" *ngIf="drawerEmail" (click)="closeEmailDrawer()"></div>
  817. <div class="email-drawer" [class.open]="drawerEmail">
  818. <div class="drawer-header" *ngIf="drawerEmail">
  819. <h3>📧 原始邮件</h3>
  820. <button mat-icon-button (click)="closeEmailDrawer()">
  821. <mat-icon>close</mat-icon>
  822. </button>
  823. </div>
  824. <div class="drawer-body" *ngIf="drawerEmail">
  825. <div class="drawer-meta">
  826. <div class="drawer-meta-row">
  827. <span class="drawer-label">发件人</span>
  828. <span>{{ drawerEmail.fromName }} &lt;{{ drawerEmail.from }}&gt;</span>
  829. </div>
  830. <div class="drawer-meta-row">
  831. <span class="drawer-label">收件人</span>
  832. <span>{{ drawerEmail.to }}</span>
  833. </div>
  834. <div class="drawer-meta-row" *ngIf="drawerEmail.cc">
  835. <span class="drawer-label">抄送</span>
  836. <span>{{ drawerEmail.cc }}</span>
  837. </div>
  838. <div class="drawer-meta-row">
  839. <span class="drawer-label">时间</span>
  840. <span>{{ drawerEmail.receivedAt | date:'yyyy-MM-dd HH:mm:ss' }}</span>
  841. </div>
  842. <div class="drawer-meta-row">
  843. <span class="drawer-label">主题</span>
  844. <span class="drawer-subject">{{ drawerEmail.subject }}</span>
  845. </div>
  846. </div>
  847. <!-- Attachments -->
  848. <div class="drawer-attachments" *ngIf="drawerEmail.attachments.length > 0">
  849. <div class="drawer-att-header">
  850. <mat-icon>attach_file</mat-icon>
  851. 附件 ({{ drawerEmail.attachments.length }})
  852. <span class="att-total-size">共 {{ getTotalSize(drawerEmail.attachments) }}</span>
  853. </div>
  854. <div class="drawer-att-grid">
  855. <div class="drawer-att-card" *ngFor="let att of drawerEmail.attachments" (click)="downloadAttachment(att)">
  856. <div class="att-icon-wrap" [class]="getAttColorClass(att.mimeType)">
  857. <mat-icon>{{ att.icon }}</mat-icon>
  858. </div>
  859. <div class="att-info">
  860. <span class="att-name" [title]="att.fileName">{{ att.fileName }}</span>
  861. <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
  862. </div>
  863. <mat-icon class="att-download-icon">download</mat-icon>
  864. </div>
  865. </div>
  866. </div>
  867. <mat-divider></mat-divider>
  868. <div class="drawer-email-body">
  869. <pre class="email-body-text">{{ drawerEmail.body }}</pre>
  870. </div>
  871. </div>
  872. </div>
  873. </div>
  874. <!-- ===== Inbox Full-Screen Overlay (outside page-container to avoid sidenav clipping) ===== -->
  875. <div class="inbox-overlay" *ngIf="inboxOpen" (click)="closeInbox()"></div>
  876. <div class="inbox-modal" *ngIf="inboxOpen">
  877. <!-- Inbox Header -->
  878. <div class="inbox-header">
  879. <div class="inbox-title">
  880. <mat-icon>inbox</mat-icon>
  881. <h2>收件箱</h2>
  882. <span class="inbox-count">{{ emails.length }} 封邮件</span>
  883. </div>
  884. <div class="inbox-header-actions">
  885. <button mat-icon-button (click)="closeInbox()" matTooltip="关闭">
  886. <mat-icon>close</mat-icon>
  887. </button>
  888. </div>
  889. </div>
  890. <!-- Inbox Body: List + Detail -->
  891. <div class="inbox-body">
  892. <!-- Left: Email List -->
  893. <div class="inbox-list">
  894. <div class="inbox-list-item" *ngFor="let email of emails"
  895. [class.active]="selectedInboxEmail?.id === email.id"
  896. (click)="selectInboxEmail(email)">
  897. <div class="inbox-item-avatar">
  898. <mat-icon>mail</mat-icon>
  899. </div>
  900. <div class="inbox-item-content">
  901. <div class="inbox-item-top">
  902. <span class="inbox-item-sender">{{ email.fromName }}</span>
  903. <span class="inbox-item-time">{{ email.receivedAt | date:'MM/dd HH:mm' }}</span>
  904. </div>
  905. <div class="inbox-item-subject">{{ email.subject }}</div>
  906. <div class="inbox-item-preview">{{ email.preview }}</div>
  907. <div class="inbox-item-tags">
  908. <span class="inbox-att-tag" *ngIf="email.attachments.length > 0">
  909. <mat-icon>attach_file</mat-icon> {{ email.attachments.length }}
  910. </span>
  911. <span class="inbox-lead-tag" *ngIf="email.leadId">
  912. <mat-icon>person</mat-icon> 已关联线索
  913. </span>
  914. </div>
  915. </div>
  916. </div>
  917. </div>
  918. <!-- Right: Email Detail -->
  919. <div class="inbox-detail" *ngIf="selectedInboxEmail">
  920. <div class="inbox-detail-subject">
  921. <mat-icon class="subject-icon">email</mat-icon>
  922. {{ selectedInboxEmail.subject }}
  923. </div>
  924. <div class="inbox-detail-meta">
  925. <div class="inbox-detail-sender-row">
  926. <div class="sender-avatar">
  927. <mat-icon>person</mat-icon>
  928. </div>
  929. <div class="sender-info">
  930. <span class="sender-name">{{ selectedInboxEmail.fromName }}</span>
  931. <span class="sender-email">&lt;{{ selectedInboxEmail.from }}&gt;</span>
  932. </div>
  933. <span class="detail-time">{{ selectedInboxEmail.receivedAt | date:'yyyy-MM-dd HH:mm:ss' }}</span>
  934. </div>
  935. <div class="inbox-detail-recipients">
  936. <span class="recipient-label">收件人:</span>
  937. <span>{{ selectedInboxEmail.to }}</span>
  938. </div>
  939. <div class="inbox-detail-recipients" *ngIf="selectedInboxEmail.cc">
  940. <span class="recipient-label">抄送:</span>
  941. <span>{{ selectedInboxEmail.cc }}</span>
  942. </div>
  943. </div>
  944. <!-- Attachments -->
  945. <div class="inbox-detail-attachments" *ngIf="selectedInboxEmail.attachments.length > 0">
  946. <div class="inbox-att-header">
  947. <mat-icon>attach_file</mat-icon>
  948. <span>附件 ({{ selectedInboxEmail.attachments.length }})</span>
  949. <span class="att-total-size">共 {{ getTotalSize(selectedInboxEmail.attachments) }}</span>
  950. </div>
  951. <div class="inbox-att-list">
  952. <div class="inbox-att-chip" *ngFor="let att of selectedInboxEmail.attachments" (click)="downloadAttachment(att)">
  953. <div class="att-icon-wrap" [class]="getAttColorClass(att.mimeType)">
  954. <mat-icon>{{ att.icon }}</mat-icon>
  955. </div>
  956. <div class="att-info">
  957. <span class="att-name" [title]="att.fileName">{{ att.fileName }}</span>
  958. <span class="att-size">{{ formatFileSize(att.fileSize) }}</span>
  959. </div>
  960. <mat-icon class="att-download-icon">download</mat-icon>
  961. </div>
  962. </div>
  963. </div>
  964. <mat-divider></mat-divider>
  965. <!-- Email Body -->
  966. <div class="inbox-detail-body">
  967. <pre class="email-body-text">{{ selectedInboxEmail.body }}</pre>
  968. </div>
  969. <!-- Actions -->
  970. <div class="inbox-detail-actions" *ngIf="getLeadForEmail(selectedInboxEmail) as lead">
  971. <button mat-raised-button color="primary" (click)="openLead(lead); closeInbox()">
  972. <mat-icon>open_in_new</mat-icon> 查看关联线索:{{ lead.companyName }}
  973. </button>
  974. </div>
  975. </div>
  976. <!-- Empty State -->
  977. <div class="inbox-detail inbox-empty-detail" *ngIf="!selectedInboxEmail">
  978. <mat-icon>markunread_mailbox</mat-icon>
  979. <p>选择一封邮件查看详情</p>
  980. </div>
  981. </div>
  982. </div>