'use client'; import * as React from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import { toast } from 'sonner'; import { Layers, RefreshCw } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import type { TableDataResponse } from '@/lib/services/db-manage'; import { DbManageService } from '@/lib/services/db-manage'; /** * 格式化数字,每3位加逗号 */ const formatNumber = (num: number | string) => { if (num === undefined || num === null) return '0'; return num.toString().replace(/\B(?=(\d{3})+(?!\B))/g, ','); }; /** * 格式化表格单元格内容 */ const formatCellValue = (val: unknown): string => { if (val === null || val === undefined) return '-'; if (typeof val === 'boolean') return val ? 'true' : 'false'; if (typeof val === 'object') { try { return JSON.stringify(val); } catch { return '[Object]'; } } return String(val); }; interface TableBrowserProps { tables: string[]; loadingTables: boolean; refreshTrigger: number; } export function TableBrowser({ tables, loadingTables, refreshTrigger, }: TableBrowserProps) { const t = useTranslations('admin.database.tableBrowser'); const [selectedTable, setSelectedTable] = useState(''); const [tableData, setTableData] = useState(null); const [page, setPage] = useState(1); const [loadingData, setLoadingData] = useState(false); const pageSize = 10; // 获取具体表数据 const fetchTableData = useCallback( async (tableName: string, targetPage: number, size: number) => { if (!tableName) return; setLoadingData(true); try { const data = await DbManageService.getTableData({ table: tableName, page: targetPage, pageSize: size, }); setTableData(data); } catch (err) { toast.error(t('fetchTableFailed', { table: tableName }), { description: err instanceof Error ? err.message : t('unknownError'), }); } finally { setLoadingData(false); } }, [t], ); // 默认选择第一张表 useEffect(() => { if (tables.length > 0 && !selectedTable) { setSelectedTable(tables[0]); } }, [tables, selectedTable]); // 当选中的表、页码或外部刷新触发器改变时获取数据 useEffect(() => { if (selectedTable) { fetchTableData(selectedTable, page, pageSize); } }, [selectedTable, page, pageSize, refreshTrigger, fetchTableData]); // 处理页码改变 const handlePageChange = (newPage: number) => { setPage(newPage); }; // 处理表切换 const handleTableChange = (value: string) => { setSelectedTable(value); setPage(1); // 切换表重置为第一页 }; return (
{t('tableBrowser')} {t('tableBrowserDesc')}
{loadingTables ? ( ) : ( )}
{loadingData && !tableData ? (
) : tableData && tableData.columns.length > 0 ? (
{/* 数据表格区域 */}
{loadingData && (
)} {tableData.columns.map((col) => ( {col} ))} {tableData.results && tableData.results.length > 0 ? ( tableData.results.map((row, rIndex) => ( {tableData.columns.map((col) => ( {formatCellValue(row[col])} ))} )) ) : ( {t('noRecordsInTable')} )}
{/* 分页控制 */}
{t('totalRows')}:{' '} {formatNumber(tableData.total)} {' '} {t('records')}
{t('pageIndicator', { current: page, total: Math.max(1, Math.ceil(tableData.total / pageSize)), })}
) : (
{t('noTableSelected')}
)}
); }