140 lines
6.8 KiB
TypeScript
140 lines
6.8 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import { Card, CardContent } from '@/components/ui/card'
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { RefreshCw, Send } from 'lucide-react'
|
||
import { Pagination } from '@/components/ui/Pagination'
|
||
import { get, post } from '@/api/client'
|
||
|
||
interface MatchRecord {
|
||
id: string; userId: string; matchedUserId: string; matchType: string
|
||
phone?: string; wechatId?: string; userNickname?: string; matchedNickname?: string
|
||
createdAt: string; ckbStatus?: string
|
||
}
|
||
|
||
const typeLabels: Record<string, string> = {
|
||
investor: '资源对接', mentor: '导师顾问', team: '团队招募',
|
||
}
|
||
|
||
export function ResourceDockingTab() {
|
||
const [records, setRecords] = useState<MatchRecord[]>([])
|
||
const [total, setTotal] = useState(0)
|
||
const [page, setPage] = useState(1)
|
||
const [pageSize, setPageSize] = useState(10)
|
||
const [isLoading, setIsLoading] = useState(true)
|
||
const [typeFilter, setTypeFilter] = useState('investor')
|
||
const [pushingId, setPushingId] = useState<string | null>(null)
|
||
|
||
async function load() {
|
||
setIsLoading(true)
|
||
try {
|
||
const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize), matchType: typeFilter })
|
||
const data = await get<{ success?: boolean; records?: MatchRecord[]; total?: number }>(`/api/db/match-records?${params}`)
|
||
if (data?.success) { setRecords(data.records || []); setTotal(data.total ?? 0) }
|
||
} catch (e) { console.error(e) }
|
||
finally { setIsLoading(false) }
|
||
}
|
||
|
||
useEffect(() => { load() }, [page, typeFilter])
|
||
|
||
const pushToCKB = async (record: MatchRecord) => {
|
||
if (!record.phone && !record.wechatId) {
|
||
alert('该记录无联系方式,无法推送到存客宝')
|
||
return
|
||
}
|
||
setPushingId(record.id)
|
||
try {
|
||
const res = await post<{ success?: boolean; message?: string }>('/api/ckb/join', {
|
||
type: record.matchType || 'investor',
|
||
phone: record.phone || '',
|
||
wechat: record.wechatId || '',
|
||
userId: record.userId,
|
||
name: record.userNickname || '',
|
||
})
|
||
alert(res?.message || (res?.success ? '推送成功' : '推送失败'))
|
||
} catch (e) {
|
||
alert('推送失败: ' + (e instanceof Error ? e.message : '网络错误'))
|
||
} finally {
|
||
setPushingId(null)
|
||
}
|
||
}
|
||
|
||
|
||
const totalPages = Math.ceil(total / pageSize) || 1
|
||
const hasContact = (r: MatchRecord) => !!(r.phone || r.wechatId)
|
||
|
||
return (
|
||
<div>
|
||
<div className="flex justify-between items-center mb-4">
|
||
<div>
|
||
<p className="text-gray-400">点击获客:有人填写手机号/微信号的直接显示,可一键推送到存客宝</p>
|
||
<p className="text-gray-500 text-xs mt-1">共 {total} 条记录 — 有联系方式的可触发存客宝添加好友</p>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<select value={typeFilter} onChange={e => { setTypeFilter(e.target.value); setPage(1) }}
|
||
className="bg-[#0f2137] border border-gray-700 text-white rounded-lg px-3 py-2 text-sm">
|
||
{Object.entries(typeLabels).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
|
||
</select>
|
||
<Button onClick={load} disabled={isLoading} variant="outline" className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent">
|
||
<RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} /> 刷新
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
|
||
<CardContent className="p-0">
|
||
{isLoading ? (
|
||
<div className="flex justify-center py-12"><RefreshCw className="w-6 h-6 text-[#38bdac] animate-spin" /><span className="ml-2 text-gray-400">加载中...</span></div>
|
||
) : (
|
||
<>
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="bg-[#0a1628] hover:bg-[#0a1628] border-gray-700">
|
||
<TableHead className="text-gray-400">发起人</TableHead>
|
||
<TableHead className="text-gray-400">匹配到</TableHead>
|
||
<TableHead className="text-gray-400">类型</TableHead>
|
||
<TableHead className="text-gray-400">联系方式</TableHead>
|
||
<TableHead className="text-gray-400">时间</TableHead>
|
||
<TableHead className="text-gray-400 text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{records.map(r => (
|
||
<TableRow key={r.id} className={`border-gray-700/50 ${hasContact(r) ? 'hover:bg-[#0a1628]' : 'opacity-60'}`}>
|
||
<TableCell className="text-white">{r.userNickname || r.userId?.slice(0, 12)}</TableCell>
|
||
<TableCell className="text-white">{r.matchedNickname || r.matchedUserId?.slice(0, 12)}</TableCell>
|
||
<TableCell>
|
||
<Badge className="bg-[#38bdac]/20 text-[#38bdac] border-0">{typeLabels[r.matchType] || r.matchType}</Badge>
|
||
</TableCell>
|
||
<TableCell className="text-sm">
|
||
{r.phone && <div className="text-green-400">📱 {r.phone}</div>}
|
||
{r.wechatId && <div className="text-blue-400">💬 {r.wechatId}</div>}
|
||
{!r.phone && !r.wechatId && <span className="text-gray-600">无联系方式</span>}
|
||
</TableCell>
|
||
<TableCell className="text-gray-400 text-sm">{r.createdAt ? new Date(r.createdAt).toLocaleString() : '-'}</TableCell>
|
||
<TableCell className="text-right">
|
||
{hasContact(r) ? (
|
||
<Button size="sm" onClick={() => pushToCKB(r)} disabled={pushingId === r.id}
|
||
className="bg-[#38bdac] hover:bg-[#2da396] text-white text-xs h-7 px-3">
|
||
<Send className="w-3 h-3 mr-1" />
|
||
{pushingId === r.id ? '推送中...' : '推送CKB'}
|
||
</Button>
|
||
) : (
|
||
<span className="text-gray-600 text-xs">—</span>
|
||
)}
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
{records.length === 0 && <TableRow><TableCell colSpan={6} className="text-center py-12 text-gray-500">暂无记录</TableCell></TableRow>}
|
||
</TableBody>
|
||
</Table>
|
||
<Pagination page={page} totalPages={totalPages} total={total} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={n => { setPageSize(n); setPage(1) }} />
|
||
</>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|