| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050 |
- <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@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="示例: Acme Medical john@acmemedical.com info@safetyfirst-uk.co.uk WildGear GmbH alex@wildgear.de 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 }} <{{ drawerEmail.from }}></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"><{{ selectedInboxEmail.from }}></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>
|