|
@@ -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>
|
|
|
|
|
- <{{ email.from }}>
|
|
|
|
|
- </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%,支持深度定制',
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|