Add contact extraction for discovery results: click to scrape email/phone/WhatsApp/WeChat from company website

This commit is contained in:
TradeMate Dev
2026-05-27 11:24:03 +08:00
parent ab06990e73
commit 6f0d8b0fb4
2 changed files with 56 additions and 7 deletions
+4 -1
View File
@@ -19,7 +19,9 @@ ANALYZE_MATCH_PROMPT = """你是外贸客户分析专家。分析目标公司的
"contact_info": { "contact_info": {
"emails": ["找到的邮箱"], "emails": ["找到的邮箱"],
"phones": ["找到的电话"], "phones": ["找到的电话"],
"social": ["LinkedIn等社媒链接"] "social": ["LinkedIn等社媒链接"],
"wechat": "找到的微信号",
"whatsapp": "找到的 WhatsApp 号码"
} }
} }
@@ -292,6 +294,7 @@ URL: {company_url}
"match_reason": "无法获取网页内容进行分析,建议手动查看", "match_reason": "无法获取网页内容进行分析,建议手动查看",
"url": url, "url": url,
"provider": "template", "provider": "template",
"contact_info": {"emails": [], "phones": [], "social": [], "wechat": "", "whatsapp": ""},
} }
def _template_outreach(self, company: Dict[str, Any], product: Dict[str, Any]) -> Dict[str, Any]: def _template_outreach(self, company: Dict[str, Any], product: Dict[str, Any]) -> Dict[str, Any]:
+52 -6
View File
@@ -12,23 +12,46 @@
</el-form> </el-form>
<div v-if="results.length"> <div v-if="results.length">
<el-alert v-if="provider === 'template' || provider === '建议'" title="以下为搜索策略建议,可用于手动开发客户" type="warning" show-icon :closable="false" style="margin-bottom:12px" /> <el-alert v-if="provider === 'template' || provider === '建议'" title="以下为搜索策略建议,可用于手动开发客户" type="warning" show-icon :closable="false" style="margin-bottom:12px" />
<el-alert v-if="provider === 'bing'" title="以下为搜索到的相关公司,点击'访问网站'了解更多,也可添加为客户" type="info" show-icon :closable="false" style="margin-bottom:12px" /> <el-alert v-if="provider === 'bing'" title="以下为搜索到的相关公司,点击公司网址可查看详情,'提取联系方式'可获取邮箱/电话" type="info" show-icon :closable="false" style="margin-bottom:12px" />
<el-card v-for="(r, idx) in results" :key="r.id || r.name || idx" shadow="hover" style="margin-top:12px"> <el-card v-for="(r, idx) in results" :key="r.id || r.name || idx" shadow="hover" style="margin-top:12px">
<div style="display:flex;justify-content:space-between;align-items:center"> <div style="display:flex;justify-content:space-between;align-items:center">
<h4 style="margin:0">{{ r.name }} <el-tag v-if="r.match_score" size="small" :type="scoreType(r.match_score)">{{ r.match_score }}%</el-tag></h4> <h4 style="margin:0">{{ r.name }} <el-tag v-if="r.match_score" size="small" :type="scoreType(r.match_score)">{{ r.match_score }}%</el-tag></h4>
<el-tag v-if="r.source && provider !== 'template'" size="small" type="info">{{ r.source }}</el-tag> <el-tag v-if="r.source && provider !== 'template'" size="small" type="info">{{ r.source }}</el-tag>
</div> </div>
<p v-if="r.description" style="color:#666;font-size:13px;margin:8px 0">{{ r.description }}</p> <p v-if="r.description" style="color:#666;font-size:13px;margin:8px 0">{{ r.description }}</p>
<div v-if="r._contactDetail" style="background:#f0f9ff;border-radius:4px;padding:8px;margin:8px 0;font-size:12px">
<div v-if="r._contactDetail.emails?.length" style="margin:2px 0">
📧 <template v-for="e in r._contactDetail.emails">{{ e }} </template>
</div>
<div v-if="r._contactDetail.phones?.length" style="margin:2px 0">
📞 <template v-for="p in r._contactDetail.phones">{{ p }} </template>
</div>
<div v-if="r._contactDetail.social?.length" style="margin:2px 0">
🔗 <template v-for="s in r._contactDetail.social"><a :href="s" target="_blank" rel="noopener" style="color:#1890ff">{{ s }}</a> </template>
</div>
<div v-if="r._contactDetail.wechat" style="margin:2px 0">
💬 微信{{ r._contactDetail.wechat }}
</div>
<div v-if="r._contactDetail.whatsapp" style="margin:2px 0">
💬 WhatsApp{{ r._contactDetail.whatsapp }}
</div>
<div v-if="!r._contactDetail.emails?.length && !r._contactDetail.phones?.length && !r._contactDetail.social?.length" style="color:#999">
未提取到联系方式
</div>
</div>
<p v-if="r.contact" style="font-size:12px;color:#999;margin:4px 0"> <p v-if="r.contact" style="font-size:12px;color:#999;margin:4px 0">
联系方式 网址
<template v-if="r.contact.startsWith('http')"> <template v-if="r.contact.startsWith('http')">
<a :href="r.contact" target="_blank" rel="noopener">{{ r.contact.substring(0, 50) }}{{ r.contact.length > 50 ? '…' : '' }}</a> <a :href="r.contact" target="_blank" rel="noopener">{{ r.contact.substring(0, 50) }}{{ r.contact.length > 50 ? '…' : '' }}</a>
</template> </template>
<template v-else>{{ r.contact }}</template> <template v-else>{{ r.contact }}</template>
</p> </p>
<div style="margin-top:8px;display:flex;gap:8px"> <div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap">
<el-button size="small" type="primary" @click="addCustomer(r)">添加为客户</el-button> <el-button size="small" type="primary" @click="addCustomer(r)">添加为客户</el-button>
<el-button v-if="r.contact && r.contact.startsWith('http')" size="small" @click="openUrl(r.contact)">访问网站</el-button> <el-button v-if="r.contact && r.contact.startsWith('http')" size="small" @click="openUrl(r.contact)">访问网站</el-button>
<el-button v-if="r.contact && r.contact.startsWith('http')" size="small" :loading="r._analyzing" :type="r._contactDetail ? 'success' : 'default'" @click="extractContact(r)">{{ r._contactDetail ? '已提取' : '提取联系方式' }}</el-button>
</div> </div>
</el-card> </el-card>
</div> </div>
@@ -60,7 +83,7 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { discoverySearch, discoveryOutreach, createCustomer } from '@/api' import { discoverySearch, discoveryOutreach, discoveryAnalyze, createCustomer } from '@/api'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const tab = ref('search') const tab = ref('search')
@@ -87,19 +110,42 @@ async function search() {
target_market: form.value.market || 'US', target_market: form.value.market || 'US',
}) })
const d = res.data || res const d = res.data || res
results.value = d.companies || d.items || d.results || d || [] results.value = (d.companies || d.items || d.results || d || []).map(r => ({ ...r, _contactDetail: null, _analyzing: false }))
provider.value = d.provider || '' provider.value = d.provider || ''
} catch { ElMessage.error('挖掘失败') } } catch { ElMessage.error('挖掘失败') }
finally { loading.value = false } finally { loading.value = false }
} }
async function addCustomer(r) { async function addCustomer(r) {
const payload = {
name: r._contactDetail?.company_summary || r.name,
company: r.name,
country: r.country || '',
description: r.description || '',
}
if (r._contactDetail?.emails?.length) payload.email = r._contactDetail.emails[0]
if (r._contactDetail?.phones?.length) payload.phone = r._contactDetail.phones[0]
try { try {
await createCustomer({ name: r.name, company: r.name, country: r.country || '', description: r.description || '' }) await createCustomer(payload)
ElMessage.success('已添加为客户') ElMessage.success('已添加为客户')
} catch (e) { ElMessage.error(e?.detail || '添加失败') } } catch (e) { ElMessage.error(e?.detail || '添加失败') }
} }
async function extractContact(r) {
if (r._contactDetail) return
r._analyzing = true
try {
const res = await discoveryAnalyze({
company_url: r.contact,
product_description: form.value.product || r.name,
})
r._contactDetail = res.data || res
} catch {
r._contactDetail = { emails: [], phones: [], social: [] }
}
r._analyzing = false
}
async function generateOutreach() { async function generateOutreach() {
if (!outForm.value.company || !outForm.value.product) { ElMessage.warning('请填写完整'); return } if (!outForm.value.company || !outForm.value.product) { ElMessage.warning('请填写完整'); return }
outLoading.value = true outLoading.value = true