sync: soul-admin 页面 | 原因: 前端页面修改

This commit is contained in:
卡若
2026-03-08 10:25:38 +08:00
parent 0865b1df5e
commit b3fd87e5dc

View File

@@ -1,14 +1,20 @@
import { useState, useEffect } from 'react'
import { Card, CardContent } from '@/components/ui/card'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { RefreshCw } from 'lucide-react'
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 } from '@/api/client'
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
createdAt: string; ckbStatus?: string
}
const typeLabels: Record<string, string> = {
investor: '资源对接', mentor: '导师顾问', team: '团队招募',
}
export function ResourceDockingTab() {
@@ -17,32 +23,62 @@ export function ResourceDockingTab() {
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: 'investor' })
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])
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"> {total} </p>
<p className="text-gray-500 text-xs mt-1"></p>
<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>
<button type="button" onClick={load} disabled={isLoading}
className="flex items-center gap-2 px-4 py-2 rounded-lg border border-gray-600 text-gray-300 hover:bg-gray-700/50 transition-colors disabled:opacity-50">
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
</button>
</div>
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
@@ -56,24 +92,40 @@ export function ResourceDockingTab() {
<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="hover:bg-[#0a1628] border-gray-700/50">
<TableCell className="text-white">{r.userNickname || r.userId}</TableCell>
<TableCell className="text-white">{r.matchedNickname || r.matchedUserId}</TableCell>
<TableCell className="text-gray-400 text-sm">
{r.phone && <div>📱 {r.phone}</div>}
{r.wechatId && <div>💬 {r.wechatId}</div>}
{!r.phone && !r.wechatId && '-'}
<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>
<TableCell className="text-gray-400">{r.createdAt ? new Date(r.createdAt).toLocaleString() : '-'}</TableCell>
</TableRow>
))}
{records.length === 0 && <TableRow><TableCell colSpan={4} className="text-center py-12 text-gray-500"></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) }} />