Add discovery search history with auto-save, fix timeout causing search failure
- Save every search result to DB for later review - Add '搜索历史' tab with timeline view, load/delete records - Raise discovery search timeout from 30s to 120s (Bing Puppeteer needs ~40s) - Reduce search queries from 4 to 3 for faster response - New model: DiscoveryRecord (user_id, product, market, companies JSON) - API: POST/GET/DELETE /api/v1/discovery/records - Migration: discovery_records table
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
"""add discovery_records table
|
||||||
|
|
||||||
|
Revision ID: 0798c5c09c8c
|
||||||
|
Revises: 7fe16f1f9962
|
||||||
|
Create Date: 2026-05-27 15:54:21.092439
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
revision: str = '0798c5c09c8c'
|
||||||
|
down_revision: Union[str, None] = '7fe16f1f9962'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('discovery_records',
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column('product', sa.String(length=500), nullable=False),
|
||||||
|
sa.Column('market', sa.String(length=200), nullable=True),
|
||||||
|
sa.Column('companies', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_discovery_records_user_id'), 'discovery_records', ['user_id'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_discovery_records_user_id'), table_name='discovery_records')
|
||||||
|
op.drop_table('discovery_records')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, desc
|
||||||
|
from typing import List, Optional
|
||||||
|
from app.database import get_db
|
||||||
|
from app.api.v1.deps import get_current_user_id
|
||||||
|
from app.models.discovery_record import DiscoveryRecord
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from datetime import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class SaveRecordRequest(BaseModel):
|
||||||
|
product: str
|
||||||
|
market: str = ""
|
||||||
|
companies: list
|
||||||
|
|
||||||
|
|
||||||
|
class RecordResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
product: str
|
||||||
|
market: str
|
||||||
|
companies: list
|
||||||
|
created_at: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/records")
|
||||||
|
async def save_record(
|
||||||
|
req: SaveRecordRequest,
|
||||||
|
user_id: str = Depends(get_current_user_id),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
record = DiscoveryRecord(
|
||||||
|
user_id=uuid.UUID(user_id),
|
||||||
|
product=req.product,
|
||||||
|
market=req.market,
|
||||||
|
companies=req.companies,
|
||||||
|
)
|
||||||
|
db.add(record)
|
||||||
|
await db.commit()
|
||||||
|
return {"success": True, "data": {"id": str(record.id)}}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/records")
|
||||||
|
async def list_records(
|
||||||
|
page: int = Query(1, ge=1),
|
||||||
|
size: int = Query(20, ge=1, le=50),
|
||||||
|
user_id: str = Depends(get_current_user_id),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
stmt = (
|
||||||
|
select(DiscoveryRecord)
|
||||||
|
.where(DiscoveryRecord.user_id == uuid.UUID(user_id))
|
||||||
|
.order_by(desc(DiscoveryRecord.created_at))
|
||||||
|
.offset((page - 1) * size)
|
||||||
|
.limit(size)
|
||||||
|
)
|
||||||
|
result = await db.execute(stmt)
|
||||||
|
records = result.scalars().all()
|
||||||
|
total_stmt = select(DiscoveryRecord).where(DiscoveryRecord.user_id == uuid.UUID(user_id))
|
||||||
|
total_result = await db.execute(total_stmt)
|
||||||
|
total = len(total_result.scalars().all())
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": str(r.id),
|
||||||
|
"product": r.product,
|
||||||
|
"market": r.market,
|
||||||
|
"companies": r.companies or [],
|
||||||
|
"created_at": r.created_at.isoformat() if r.created_at else "",
|
||||||
|
}
|
||||||
|
for r in records
|
||||||
|
],
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"size": size,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/records/{record_id}")
|
||||||
|
async def get_record(
|
||||||
|
record_id: str,
|
||||||
|
user_id: str = Depends(get_current_user_id),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
rid = uuid.UUID(record_id)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid record ID")
|
||||||
|
result = await db.execute(
|
||||||
|
select(DiscoveryRecord).where(
|
||||||
|
DiscoveryRecord.id == rid,
|
||||||
|
DiscoveryRecord.user_id == uuid.UUID(user_id),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r = result.scalar_one_or_none()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(status_code=404, detail="Record not found")
|
||||||
|
return {
|
||||||
|
"id": str(r.id),
|
||||||
|
"product": r.product,
|
||||||
|
"market": r.market,
|
||||||
|
"companies": r.companies or [],
|
||||||
|
"created_at": r.created_at.isoformat() if r.created_at else "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/records/{record_id}")
|
||||||
|
async def delete_record(
|
||||||
|
record_id: str,
|
||||||
|
user_id: str = Depends(get_current_user_id),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
rid = uuid.UUID(record_id)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid record ID")
|
||||||
|
result = await db.execute(
|
||||||
|
select(DiscoveryRecord).where(
|
||||||
|
DiscoveryRecord.id == rid,
|
||||||
|
DiscoveryRecord.user_id == uuid.UUID(user_id),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
r = result.scalar_one_or_none()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(status_code=404, detail="Record not found")
|
||||||
|
await db.delete(r)
|
||||||
|
await db.commit()
|
||||||
|
return {"success": True}
|
||||||
+2
-1
@@ -54,7 +54,7 @@ async def health():
|
|||||||
return {"status": "ok", "app": settings.APP_NAME, "version": "1.0.0"}
|
return {"status": "ok", "app": settings.APP_NAME, "version": "1.0.0"}
|
||||||
|
|
||||||
|
|
||||||
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant, discovery, certification, invoice, usage, referral, admin_search, search
|
from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant, discovery, discovery_record, certification, invoice, usage, referral, admin_search, search
|
||||||
|
|
||||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
||||||
app.include_router(marketing.router, prefix="/api/v1/marketing", tags=["marketing"])
|
app.include_router(marketing.router, prefix="/api/v1/marketing", tags=["marketing"])
|
||||||
@@ -79,6 +79,7 @@ app.include_router(training.router, prefix="/api/v1/training", tags=["training"]
|
|||||||
app.include_router(followup.router, prefix="/api/v1/followup", tags=["followup"])
|
app.include_router(followup.router, prefix="/api/v1/followup", tags=["followup"])
|
||||||
app.include_router(ai_assistant.router, prefix="/api/v1/ai", tags=["ai-assistant"])
|
app.include_router(ai_assistant.router, prefix="/api/v1/ai", tags=["ai-assistant"])
|
||||||
app.include_router(discovery.router, prefix="/api/v1/discovery", tags=["discovery"])
|
app.include_router(discovery.router, prefix="/api/v1/discovery", tags=["discovery"])
|
||||||
|
app.include_router(discovery_record.router, prefix="/api/v1/discovery", tags=["discovery"])
|
||||||
app.include_router(certification.router, prefix="/api/v1/certification", tags=["certification"])
|
app.include_router(certification.router, prefix="/api/v1/certification", tags=["certification"])
|
||||||
app.include_router(invoice.router, prefix="/api/v1/invoices", tags=["invoices"])
|
app.include_router(invoice.router, prefix="/api/v1/invoices", tags=["invoices"])
|
||||||
app.include_router(usage.router, prefix="/api/v1/usage", tags=["usage"])
|
app.include_router(usage.router, prefix="/api/v1/usage", tags=["usage"])
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from .certification import Certification, CertType, CertStatus
|
|||||||
from .invoice import Invoice, InvoiceType, InvoiceStatus
|
from .invoice import Invoice, InvoiceType, InvoiceStatus
|
||||||
from .referral import ReferralCode, Referral
|
from .referral import ReferralCode, Referral
|
||||||
from .search_provider import SearchProvider
|
from .search_provider import SearchProvider
|
||||||
|
from .discovery_record import DiscoveryRecord
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User", "Product",
|
"User", "Product",
|
||||||
@@ -36,4 +37,5 @@ __all__ = [
|
|||||||
"Invoice", "InvoiceType", "InvoiceStatus",
|
"Invoice", "InvoiceType", "InvoiceStatus",
|
||||||
"ReferralCode", "Referral",
|
"ReferralCode", "Referral",
|
||||||
"SearchProvider",
|
"SearchProvider",
|
||||||
|
"DiscoveryRecord",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy import Column, String, DateTime, Text
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||||
|
from datetime import datetime
|
||||||
|
from app.database import Base
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryRecord(Base):
|
||||||
|
__tablename__ = "discovery_records"
|
||||||
|
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
user_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||||
|
product = Column(String(500), nullable=False)
|
||||||
|
market = Column(String(200), default="")
|
||||||
|
companies = Column(JSONB, default=[])
|
||||||
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
@@ -125,7 +125,7 @@ URL: {company_url}
|
|||||||
|
|
||||||
async def _web_search_all(self, queries: list) -> dict:
|
async def _web_search_all(self, queries: list) -> dict:
|
||||||
try:
|
try:
|
||||||
results = await search_bing_batch(queries[:4], max_per_query=5)
|
results = await search_bing_batch(queries[:3], max_per_query=4)
|
||||||
if results:
|
if results:
|
||||||
return {"results": self._dedup_and_filter(results)[:15], "provider": "bing"}
|
return {"results": self._dedup_and_filter(results)[:15], "provider": "bing"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -71,9 +71,13 @@ export function generateMarketing(data) { return http.post('/marketing/generate'
|
|||||||
export function getKeywords(data) { return http.post('/marketing/keywords', data) }
|
export function getKeywords(data) { return http.post('/marketing/keywords', data) }
|
||||||
export function competitorAnalysis(data) { return http.post('/marketing/competitor-analysis', data) }
|
export function competitorAnalysis(data) { return http.post('/marketing/competitor-analysis', data) }
|
||||||
|
|
||||||
export function discoverySearch(data) { return http.post('/discovery/search', data) }
|
export function discoverySearch(data) { return http.post('/discovery/search', data, { timeout: 120000 }) }
|
||||||
export function discoveryAnalyze(data) { return http.post('/discovery/analyze', data) }
|
export function discoveryAnalyze(data) { return http.post('/discovery/analyze', data, { timeout: 60000 }) }
|
||||||
export function discoveryOutreach(data) { return http.post('/discovery/outreach', data) }
|
export function discoveryOutreach(data) { return http.post('/discovery/outreach', data, { timeout: 60000 }) }
|
||||||
|
export function saveDiscoveryRecord(data) { return http.post('/discovery/records', data) }
|
||||||
|
export function listDiscoveryRecords(params) { return http.get('/discovery/records', { params }) }
|
||||||
|
export function getDiscoveryRecord(id) { return http.get(`/discovery/records/${id}`) }
|
||||||
|
export function deleteDiscoveryRecord(id) { return http.delete(`/discovery/records/${id}`) }
|
||||||
|
|
||||||
export function getFollowupStats() { return http.get('/followup/stats') }
|
export function getFollowupStats() { return http.get('/followup/stats') }
|
||||||
export function getFollowupPending() { return http.get('/followup/pending') }
|
export function getFollowupPending() { return http.get('/followup/pending') }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-tabs v-model="tab">
|
<el-tabs v-model="tab" @tab-change="onTabChange">
|
||||||
<el-tab-pane label="客户挖掘" name="search">
|
<el-tab-pane label="客户挖掘" name="search">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-form :model="form" label-width="100">
|
<el-form :model="form" label-width="100">
|
||||||
@@ -58,6 +58,33 @@
|
|||||||
<el-empty v-if="!loading && !results.length && searched" description="未找到匹配客户" :image-size="60" />
|
<el-empty v-if="!loading && !results.length && searched" description="未找到匹配客户" :image-size="60" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="搜索历史" name="history">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div v-if="historyLoading" style="text-align:center;padding:40px">
|
||||||
|
<el-icon class="is-loading" :size="24"><Loading /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="history.length">
|
||||||
|
<el-timeline>
|
||||||
|
<el-timeline-item v-for="h in history" :key="h.id" :timestamp="formatTime(h.created_at)" placement="top">
|
||||||
|
<el-card shadow="hover" style="cursor:pointer" @click="loadHistory(h)">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||||
|
<div>
|
||||||
|
<strong>{{ h.product }}</strong>
|
||||||
|
<el-tag v-if="h.market" size="small" style="margin-left:8px">{{ h.market }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-button size="small" type="danger" link @click.stop="deleteRecord(h.id)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="color:#999;font-size:12px;margin-top:4px">{{ h.companies?.length || 0 }} 条结果</div>
|
||||||
|
</el-card>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-if="hasMore" style="text-align:center;margin-top:16px">
|
||||||
|
<el-button :loading="historyLoadingMore" @click="loadMoreHistory">加载更多</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-else description="暂无搜索历史" :image-size="60" />
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
<el-tab-pane label="开发信生成" name="outreach">
|
<el-tab-pane label="开发信生成" name="outreach">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<el-form :model="outForm" label-width="100">
|
<el-form :model="outForm" label-width="100">
|
||||||
@@ -83,14 +110,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { discoverySearch, discoveryOutreach, discoveryAnalyze, createCustomer } from '@/api'
|
import {
|
||||||
import { ElMessage } from 'element-plus'
|
discoverySearch, discoveryOutreach, discoveryAnalyze, createCustomer,
|
||||||
|
saveDiscoveryRecord, listDiscoveryRecords, getDiscoveryRecord, deleteDiscoveryRecord,
|
||||||
|
} from '@/api'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const tab = ref('search')
|
const tab = ref('search')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const searched = ref(false)
|
const searched = ref(false)
|
||||||
const results = ref([])
|
const results = ref([])
|
||||||
const provider = ref('')
|
const provider = ref('')
|
||||||
|
const lastSearch = ref({ product: '', market: '' })
|
||||||
|
|
||||||
function openUrl(url) { window.open(url, '_blank') }
|
function openUrl(url) { window.open(url, '_blank') }
|
||||||
const form = ref({ product: '', market: '' })
|
const form = ref({ product: '', market: '' })
|
||||||
@@ -98,21 +129,95 @@ const outForm = ref({ company: '', product: '', channel: 'email' })
|
|||||||
const outLoading = ref(false)
|
const outLoading = ref(false)
|
||||||
const outreachResult = ref('')
|
const outreachResult = ref('')
|
||||||
|
|
||||||
|
const history = ref([])
|
||||||
|
const historyLoading = ref(false)
|
||||||
|
const historyLoadingMore = ref(false)
|
||||||
|
const historyPage = ref(1)
|
||||||
|
const hasMore = ref(false)
|
||||||
|
|
||||||
function scoreType(s) { if (s >= 80) return 'success'; if (s >= 60) return 'warning'; return 'info' }
|
function scoreType(s) { if (s >= 80) return 'success'; if (s >= 60) return 'warning'; return 'info' }
|
||||||
|
|
||||||
|
function formatTime(iso) {
|
||||||
|
if (!iso) return ''
|
||||||
|
const d = new Date(iso)
|
||||||
|
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHistory(page = 1, append = false) {
|
||||||
|
try {
|
||||||
|
if (!append) historyLoading.value = true
|
||||||
|
else historyLoadingMore.value = true
|
||||||
|
const res = await listDiscoveryRecords({ page, size: 20 })
|
||||||
|
const d = res.data || res
|
||||||
|
const items = d.items || d || []
|
||||||
|
if (append) history.value.push(...items)
|
||||||
|
else history.value = items
|
||||||
|
hasMore.value = items.length >= 20
|
||||||
|
historyPage.value = page
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
finally {
|
||||||
|
historyLoading.value = false
|
||||||
|
historyLoadingMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadHistory(h) {
|
||||||
|
try {
|
||||||
|
const res = await getDiscoveryRecord(h.id)
|
||||||
|
const d = res.data || res
|
||||||
|
results.value = (d.companies || []).map(r => ({ ...r, _contactDetail: null, _analyzing: false }))
|
||||||
|
provider.value = 'history'
|
||||||
|
form.value.product = d.product
|
||||||
|
form.value.market = d.market
|
||||||
|
searched.value = true
|
||||||
|
tab.value = 'search'
|
||||||
|
} catch { ElMessage.error('加载失败') }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRecord(id) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定删除该搜索记录?', '提示')
|
||||||
|
await deleteDiscoveryRecord(id)
|
||||||
|
history.value = history.value.filter(h => h.id !== id)
|
||||||
|
ElMessage.success('已删除')
|
||||||
|
} catch { /* cancel */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreHistory() {
|
||||||
|
fetchHistory(historyPage.value + 1, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTabChange(name) {
|
||||||
|
if (name === 'history') fetchHistory()
|
||||||
|
}
|
||||||
|
|
||||||
async function search() {
|
async function search() {
|
||||||
if (!form.value.product) { ElMessage.warning('请输入产品'); return }
|
if (!form.value.product) { ElMessage.warning('请输入产品'); return }
|
||||||
loading.value = true
|
loading.value = true
|
||||||
searched.value = true
|
searched.value = true
|
||||||
|
lastSearch.value = { product: form.value.product, market: form.value.market }
|
||||||
try {
|
try {
|
||||||
const res = await discoverySearch({
|
const res = await discoverySearch({
|
||||||
product_description: form.value.product,
|
product_description: form.value.product,
|
||||||
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 || []).map(r => ({ ...r, _contactDetail: null, _analyzing: false }))
|
const companies = d.companies || d.items || d.results || d || []
|
||||||
|
results.value = companies.map(r => ({ ...r, _contactDetail: null, _analyzing: false }))
|
||||||
provider.value = d.provider || ''
|
provider.value = d.provider || ''
|
||||||
} catch { ElMessage.error('挖掘失败') }
|
|
||||||
|
saveDiscoveryRecord({
|
||||||
|
product: form.value.product,
|
||||||
|
market: form.value.market || 'US',
|
||||||
|
companies: companies.map(r => ({
|
||||||
|
name: r.name, description: r.description, country: r.country,
|
||||||
|
match_score: r.match_score, contact: r.contact, source: r.source,
|
||||||
|
})),
|
||||||
|
}).catch(() => {})
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e?.detail || e?.message || '挖掘失败(超时或服务错误)'
|
||||||
|
ElMessage.error(msg)
|
||||||
|
}
|
||||||
finally { loading.value = false }
|
finally { loading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user