"use client"; import { useState, useEffect } from 'react'; import CreateLinkModal from '../components/ui/CreateLinkModal'; import { Link, StatsOverview, Tag } from '../api/types'; // Define type for link data interface LinkData { name: string; originalUrl: string; customSlug: string; expiresAt: string; tags: string[]; } // 映射API数据到UI所需格式 interface UILink { id: string; name: string; shortUrl: string; originalUrl: string; creator: string; createdAt: string; visits: number; visitChange: number; uniqueVisitors: number; uniqueVisitorsChange: number; avgTime: string; avgTimeChange: number; conversionRate: number; conversionChange: number; status: string; tags: string[]; } export default function LinksPage() { const [links, setLinks] = useState([]); const [allTags, setAllTags] = useState([]); const [stats, setStats] = useState({ totalLinks: 0, activeLinks: 0, totalVisits: 0, conversionRate: 0 }); const [searchQuery, setSearchQuery] = useState(''); const [showCreateModal, setShowCreateModal] = useState(false); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // 映射API数据到UI所需格式的函数 const mapApiLinkToUiLink = (apiLink: Link): UILink => { // 生成短URL显示 - 因为数据库中没有short_url字段 const shortUrlDisplay = generateShortUrlDisplay(apiLink.link_id, apiLink.original_url); return { id: apiLink.link_id, name: apiLink.title || 'Untitled Link', shortUrl: shortUrlDisplay, originalUrl: apiLink.original_url, creator: apiLink.created_by, createdAt: new Date(apiLink.created_at).toLocaleDateString(), visits: apiLink.visits, visitChange: 0, // API doesn't provide change data yet uniqueVisitors: apiLink.unique_visits, uniqueVisitorsChange: 0, avgTime: '0m 0s', // API doesn't provide average time yet avgTimeChange: 0, conversionRate: 0, // API doesn't provide conversion rate yet conversionChange: 0, status: apiLink.is_active ? 'active' : 'inactive', tags: apiLink.tags || [] }; }; // 从link_id和原始URL生成短URL显示 const generateShortUrlDisplay = (linkId: string, originalUrl: string): string => { try { // 尝试从原始URL提取域名 const urlObj = new URL(originalUrl); const domain = urlObj.hostname.replace('www.', ''); // 使用link_id的前8个字符作为短代码 const shortCode = linkId.substring(0, 8); return `${domain}/${shortCode}`; } catch { // 如果URL解析失败,返回一个基于linkId的默认值 return `short.link/${linkId.substring(0, 8)}`; } }; // 获取链接数据 useEffect(() => { const fetchLinks = async () => { try { setIsLoading(true); setError(null); // 获取链接列表 const linksResponse = await fetch('/api/links'); if (!linksResponse.ok) { throw new Error(`Failed to fetch links: ${linksResponse.statusText}`); } const linksData = await linksResponse.json(); // 获取标签列表 const tagsResponse = await fetch('/api/tags'); if (!tagsResponse.ok) { throw new Error(`Failed to fetch tags: ${tagsResponse.statusText}`); } const tagsData = await tagsResponse.json(); // 获取统计数据 const statsResponse = await fetch('/api/stats'); if (!statsResponse.ok) { throw new Error(`Failed to fetch stats: ${statsResponse.statusText}`); } const statsData = await statsResponse.json(); // 处理并设置数据 const uiLinks = linksData.data.map(mapApiLinkToUiLink); setLinks(uiLinks); setAllTags(tagsData); setStats(statsData); } catch (err) { console.error('Data loading failed:', err); setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setIsLoading(false); } }; fetchLinks(); }, []); const filteredLinks = links.filter(link => link.name.toLowerCase().includes(searchQuery.toLowerCase()) || link.shortUrl.toLowerCase().includes(searchQuery.toLowerCase()) || link.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) ); const handleOpenLinkDetails = (id: string) => { window.location.href = `/links/${id}`; }; const handleCreateLink = async (linkData: LinkData) => { try { setIsLoading(true); // 在实际应用中,这里会发送 POST 请求到 API console.log('创建链接:', linkData); // 刷新链接列表 const response = await fetch('/api/links'); if (!response.ok) { throw new Error(`刷新链接列表失败: ${response.statusText}`); } const newData = await response.json(); const uiLinks = newData.data.map(mapApiLinkToUiLink); setLinks(uiLinks); setShowCreateModal(false); } catch (err) { console.error('创建链接失败:', err); setError(err instanceof Error ? err.message : '未知错误'); } finally { setIsLoading(false); } }; // 加载状态 if (isLoading && links.length === 0) { return (

Loading data...

); } // 错误状态 if (error && links.length === 0) { return (

Loading Failed

{error}

); } return (
{/* Header */}

Link Management

View and manage all your shortened links

setSearchQuery(e.target.value)} />
{/* Stats Summary */}

Total Links

{stats.totalLinks}

Active Links

{stats.activeLinks}

Total Visits

{stats.totalVisits.toLocaleString()}

Conversion Rate

{(stats.conversionRate * 100).toFixed(1)}%

{/* Links Table */}
{isLoading && links.length === 0 ? ( ) : filteredLinks.length === 0 ? ( ) : ( filteredLinks.map((link) => ( handleOpenLinkDetails(link.id)} > )) )}
Link Info Visits Unique Visitors Avg Time Conversion Status Actions
Loading...
No links found matching your search criteria
{link.name}
{link.shortUrl}
{link.visits.toLocaleString()}
= 0 ? 'text-accent-green' : 'text-accent-red'}`}> = 0 ? '' : 'transform rotate-180'}`} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > {Math.abs(link.visitChange)}%
{link.uniqueVisitors.toLocaleString()}
= 0 ? 'text-accent-green' : 'text-accent-red'}`}> = 0 ? '' : 'transform rotate-180'}`} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > {Math.abs(link.uniqueVisitorsChange)}%
{link.avgTime}
= 0 ? 'text-accent-green' : 'text-accent-red'}`}> = 0 ? '' : 'transform rotate-180'}`} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > {Math.abs(link.avgTimeChange)}%
{link.conversionRate}%
= 0 ? 'text-accent-green' : 'text-accent-red'}`}> = 0 ? '' : 'transform rotate-180'}`} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > {Math.abs(link.conversionChange)}%
{link.status === 'active' ? 'Active' : link.status === 'inactive' ? 'Inactive' : 'Expired'}
{/* Tags Section */} {allTags.length > 0 && (

Tags

{allTags.map(tagItem => ( setSearchQuery(tagItem.tag)} style={{ cursor: 'pointer' }} > {tagItem.tag} {tagItem.count} ))}
)}
{/* Create Link Modal */} {showCreateModal && ( setShowCreateModal(false)} onSubmit={handleCreateLink} /> )}
); }