Add contact extraction for discovery results: click to scrape email/phone/WhatsApp/WeChat from company website
This commit is contained in:
@@ -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]:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user