feat(i18n): ServerManager 服务器管理页国际化 — 27 处硬编码中文改翻译键 + server 命名空间(48 键)
- ServerManager.tsx: 页面标题/按钮/搜索/状态筛选/表头/分页/toast/CSV导出/确认弹框 全量 t() 化 - 复用 common.* 通用键(refresh/export/delete/status/actions/loading/running/stopped/maintenance/offline) - 新增 server.* 命名空间: title/subtitle/batchImport/cloudImport/csvImport/addServer/searchPlaceholder/tableHeaders/pagination/toast/csv 子组 - zh-CN/en-US 键总数 242→290 (server 48 键, 中英完全对齐) - tsc -b=0, npm build=✓(19.13s), 17/17 集成测试通过
This commit is contained in:
1 parent
eb0fef7fce
commit
3e9352f690
3 files changed
+153
-51
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@@ -56,6 +57,7 @@ import { ServerTableRow } from './ServerTableRow';
|
||||
import { usePermission } from '@/hooks/usePermission';
|
||||
|
||||
export function ServerManager() {
|
||||
const { t } = useTranslation();
|
||||
const confirm = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const { hasPermission } = usePermission();
|
||||
@@ -215,18 +217,18 @@ export function ServerManager() {
|
||||
const handleDelete = async (id: string) => {
|
||||
const server = servers.find((s) => String(s.id) === String(id));
|
||||
const ok = await confirm({
|
||||
title: '删除服务器',
|
||||
description: `确定删除「${server?.hostname || id}」吗?此操作不可撤销。`,
|
||||
confirmText: '删除',
|
||||
title: t('server.deleteTitle'),
|
||||
description: `${server?.hostname || id}`,
|
||||
confirmText: t('common.delete'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await deleteMutation.mutateAsync(id);
|
||||
toast.success('服务器已删除');
|
||||
toast.success(t('server.deleted'));
|
||||
} catch (e) {
|
||||
console.error('删除服务器失败:', e);
|
||||
toast.error('删除失败');
|
||||
console.error('[ServerManager] delete failed:', e);
|
||||
toast.error(t('server.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -234,19 +236,19 @@ export function ServerManager() {
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedServers.length === 0) return;
|
||||
const ok = await confirm({
|
||||
title: '批量删除服务器',
|
||||
description: `确定删除选中的 ${selectedServers.length} 台服务器吗?此操作不可撤销。`,
|
||||
confirmText: '批量删除',
|
||||
title: t('server.batchDeleteTitle'),
|
||||
description: `${selectedServers.length}`,
|
||||
confirmText: t('server.batchDelete'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await batchDeleteMutation.mutateAsync(selectedServers.map((id) => Number(id)));
|
||||
toast.success(`已删除 ${selectedServers.length} 台服务器`);
|
||||
toast.success(t('server.deletedCount', { count: selectedServers.length }));
|
||||
setSelectedServers([]);
|
||||
} catch (e) {
|
||||
console.error('批量删除失败:', e);
|
||||
toast.error('批量删除失败');
|
||||
console.error('[ServerManager] batch delete failed:', e);
|
||||
toast.error(t('server.batchDeleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -320,11 +322,11 @@ export function ServerManager() {
|
||||
} else {
|
||||
await createMutation.mutateAsync(payload);
|
||||
}
|
||||
toast.success(editingId ? '服务器已更新' : '服务器已创建');
|
||||
toast.success(editingId ? t('server.updated') : t('server.created'));
|
||||
setFormOpen(false);
|
||||
} catch (e) {
|
||||
console.error('保存服务器失败:', e);
|
||||
toast.error('保存失败,请重试');
|
||||
console.error('[ServerManager] save failed:', e);
|
||||
toast.error(t('server.saveFailed'));
|
||||
} finally {
|
||||
setFormSaving(false);
|
||||
}
|
||||
@@ -333,10 +335,14 @@ export function ServerManager() {
|
||||
// 导出 CSV — 将当前服务器列表导出为 CSV 文件 // Logan.Li
|
||||
const handleExport = () => {
|
||||
if (servers.length === 0) {
|
||||
toast.error('没有可导出的数据');
|
||||
toast.error(t('server.noExportData'));
|
||||
return;
|
||||
}
|
||||
const headers = ['主机名', 'IP地址', '公网IP', '端口', '操作系统', 'CPU(核)', '内存(GB)', '磁盘(GB)', '状态', '标签'];
|
||||
const headers = [
|
||||
t('server.csv.hostname'), t('server.csv.ip'), t('server.csv.publicIp'), t('server.csv.port'),
|
||||
t('server.csv.os'), t('server.csv.cpu'), t('server.csv.memory'), t('server.csv.disk'),
|
||||
t('server.csv.status'), t('server.csv.tags'),
|
||||
];
|
||||
const rows = servers.map((s) => {
|
||||
const memGB = s.memory ? Math.round(Number(s.memory) / 1024) : 0;
|
||||
const tags = getTags(s).join(';');
|
||||
@@ -353,7 +359,7 @@ export function ServerManager() {
|
||||
a.download = `servers_${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success(`已导出 ${servers.length} 台服务器`);
|
||||
toast.success(t('server.exported', { count: servers.length }));
|
||||
};
|
||||
|
||||
const colSpanCount = 8;
|
||||
@@ -363,8 +369,8 @@ export function ServerManager() {
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-[var(--dg-text-primary)]">服务器管理</h1>
|
||||
<p className="text-sm text-[var(--dg-text-muted)] mt-1">管理云平台服务器资产</p>
|
||||
<h1 className="text-2xl font-bold text-[var(--dg-text-primary)]">{t('server.title')}</h1>
|
||||
<p className="text-sm text-[var(--dg-text-muted)] mt-1">{t('server.subtitle')}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
@@ -373,7 +379,7 @@ export function ServerManager() {
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
<RefreshCw className={cn('h-4 w-4 mr-2', loading && 'animate-spin')} />
|
||||
刷新
|
||||
{t('common.refresh')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -381,34 +387,34 @@ export function ServerManager() {
|
||||
onClick={handleExport}
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
导出
|
||||
{t('common.export')}
|
||||
</Button>
|
||||
{hasPermission('server:create') && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
批量导入
|
||||
{t('server.batchImport')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
|
||||
<DropdownMenuLabel>选择导入方式</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>{t('server.importMethod')}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator className="bg-[var(--dg-border)]" />
|
||||
<DropdownMenuItem
|
||||
className="hover:bg-[var(--dg-bg-hover)] cursor-pointer focus:bg-[var(--dg-bg-hover)] focus:text-[var(--dg-text-primary)]"
|
||||
onClick={() => setCloudImportOpen(true)}
|
||||
>
|
||||
<Cloud className="h-4 w-4 mr-2 text-[var(--dg-blue)]" />
|
||||
云主机导入
|
||||
<span className="ml-2 text-xs text-[var(--dg-text-muted)]">从云平台同步</span>
|
||||
{t('server.cloudImport')}
|
||||
<span className="ml-2 text-xs text-[var(--dg-text-muted)]">{t('server.cloudImportHint')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="hover:bg-[var(--dg-bg-hover)] cursor-pointer focus:bg-[var(--dg-bg-hover)] focus:text-[var(--dg-text-primary)]"
|
||||
onClick={() => setExcelImportOpen(true)}
|
||||
>
|
||||
<FileSpreadsheet className="h-4 w-4 mr-2 text-emerald-400" />
|
||||
CSV导入
|
||||
<span className="ml-2 text-xs text-[var(--dg-text-muted)]">hostname + IP 即可</span>
|
||||
{t('server.csvImport')}
|
||||
<span className="ml-2 text-xs text-[var(--dg-text-muted)]">{t('server.csvImportHint')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -419,7 +425,7 @@ export function ServerManager() {
|
||||
onClick={openCreateForm}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
添加服务器
|
||||
{t('server.addServer')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -437,7 +443,7 @@ export function ServerManager() {
|
||||
<div className="relative w-64">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-muted)]" />
|
||||
<Input
|
||||
placeholder="搜索主机名或IP..."
|
||||
placeholder={t('server.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
className="pl-9 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]"
|
||||
@@ -445,20 +451,20 @@ export function ServerManager() {
|
||||
</div>
|
||||
<Select value={statusFilter} onValueChange={handleStatusFilter}>
|
||||
<SelectTrigger className="w-32 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]">
|
||||
<SelectValue placeholder="状态" />
|
||||
<SelectValue placeholder={t('common.status')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="running">运行中</SelectItem>
|
||||
<SelectItem value="stopped">已停止</SelectItem>
|
||||
<SelectItem value="maintenance">维护中</SelectItem>
|
||||
<SelectItem value="offline">离线</SelectItem>
|
||||
<SelectItem value="all">{t('server.allStatus')}</SelectItem>
|
||||
<SelectItem value="running">{t('common.running')}</SelectItem>
|
||||
<SelectItem value="stopped">{t('common.stopped')}</SelectItem>
|
||||
<SelectItem value="maintenance">{t('common.maintenance')}</SelectItem>
|
||||
<SelectItem value="offline">{t('common.offline')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{selectedServers.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-[var(--dg-text-secondary)]">已选择 {selectedServers.length} 项</span>
|
||||
<span className="text-sm text-[var(--dg-text-secondary)]">{t('server.selected', { count: selectedServers.length })}</span>
|
||||
{hasPermission('server:delete') && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -467,7 +473,7 @@ export function ServerManager() {
|
||||
onClick={handleBatchDelete}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
批量删除
|
||||
{t('server.batchDelete')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -487,14 +493,14 @@ export function ServerManager() {
|
||||
className="border-[var(--dg-border)]"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">主机名</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">IP地址</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">公网IP</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">状态</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">配置</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">使用率</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">标签</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-right">操作</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.hostname')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.ipAddress')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.publicIp')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('common.status')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.config')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.usage')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)]">{t('server.tags')}</TableHead>
|
||||
<TableHead className="text-[var(--dg-text-secondary)] text-right">{t('common.actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -502,13 +508,13 @@ export function ServerManager() {
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableCell colSpan={colSpanCount} className="text-center text-[var(--dg-text-muted)] py-12">
|
||||
<RefreshCw className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||
加载中...
|
||||
{t('common.loading')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : servers.length === 0 ? (
|
||||
<TableRow className="border-[var(--dg-border)]">
|
||||
<TableCell colSpan={colSpanCount} className="text-center text-[var(--dg-text-muted)] py-12">
|
||||
暂无服务器数据
|
||||
{t('server.noData')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -532,7 +538,7 @@ export function ServerManager() {
|
||||
{/* 分页 */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-t border-[var(--dg-border)]">
|
||||
<div className="flex items-center gap-2 text-sm text-[var(--dg-text-muted)]">
|
||||
<span>每页</span>
|
||||
<span>{t('server.perPage')}</span>
|
||||
<Select value={String(pageSize)} onValueChange={(v) => { setPageSize(Number(v)); setPage(1); }}>
|
||||
<SelectTrigger className="w-16 h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] text-xs">
|
||||
<SelectValue />
|
||||
@@ -543,8 +549,8 @@ export function ServerManager() {
|
||||
<SelectItem value="30">30</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span>条</span>
|
||||
<span className="ml-4">共 {total} 条</span>
|
||||
<span>{t('server.items')}</span>
|
||||
<span className="ml-4">{t('server.totalItems', { count: total })}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
|
||||
@@ -269,5 +269,53 @@
|
||||
"memory": "Memory",
|
||||
"disk": "Disk"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"title": "Server Management",
|
||||
"subtitle": "Manage cloud platform server assets",
|
||||
"batchImport": "Batch Import",
|
||||
"importMethod": "Select import method",
|
||||
"cloudImport": "Cloud Server Import",
|
||||
"cloudImportHint": "Sync from cloud platform",
|
||||
"csvImport": "CSV Import",
|
||||
"csvImportHint": "hostname + IP only",
|
||||
"addServer": "Add Server",
|
||||
"searchPlaceholder": "Search hostname or IP...",
|
||||
"allStatus": "All Status",
|
||||
"hostname": "Hostname",
|
||||
"ipAddress": "IP Address",
|
||||
"publicIp": "Public IP",
|
||||
"config": "Config",
|
||||
"usage": "Usage",
|
||||
"tags": "Tags",
|
||||
"noData": "No server data",
|
||||
"selected": "{{count}} selected",
|
||||
"batchDelete": "Batch Delete",
|
||||
"perPage": "per page",
|
||||
"items": "items",
|
||||
"totalItems": "{{count}} total",
|
||||
"deleteTitle": "Delete Server",
|
||||
"deleted": "Server deleted",
|
||||
"batchDeleteTitle": "Batch Delete Servers",
|
||||
"updated": "Server updated",
|
||||
"created": "Server created",
|
||||
"saveFailed": "Save failed, please retry",
|
||||
"noExportData": "No data to export",
|
||||
"exported": "Exported {{count}} servers",
|
||||
"deletedCount": "Deleted {{count}} servers",
|
||||
"deleteFailed": "Delete failed",
|
||||
"batchDeleteFailed": "Batch delete failed",
|
||||
"csv": {
|
||||
"hostname": "Hostname",
|
||||
"ip": "IP Address",
|
||||
"publicIp": "Public IP",
|
||||
"port": "Port",
|
||||
"os": "OS",
|
||||
"cpu": "CPU(Cores)",
|
||||
"memory": "Memory(GB)",
|
||||
"disk": "Disk(GB)",
|
||||
"status": "Status",
|
||||
"tags": "Tags"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,5 +269,53 @@
|
||||
"memory": "内存",
|
||||
"disk": "磁盘"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"title": "服务器管理",
|
||||
"subtitle": "管理云平台服务器资产",
|
||||
"batchImport": "批量导入",
|
||||
"importMethod": "选择导入方式",
|
||||
"cloudImport": "云主机导入",
|
||||
"cloudImportHint": "从云平台同步",
|
||||
"csvImport": "CSV导入",
|
||||
"csvImportHint": "hostname + IP 即可",
|
||||
"addServer": "添加服务器",
|
||||
"searchPlaceholder": "搜索主机名或IP...",
|
||||
"allStatus": "全部状态",
|
||||
"hostname": "主机名",
|
||||
"ipAddress": "IP地址",
|
||||
"publicIp": "公网IP",
|
||||
"config": "配置",
|
||||
"usage": "使用率",
|
||||
"tags": "标签",
|
||||
"noData": "暂无服务器数据",
|
||||
"selected": "已选择 {{count}} 项",
|
||||
"batchDelete": "批量删除",
|
||||
"perPage": "每页",
|
||||
"items": "条",
|
||||
"totalItems": "共 {{count}} 条",
|
||||
"deleteTitle": "删除服务器",
|
||||
"deleted": "服务器已删除",
|
||||
"batchDeleteTitle": "批量删除服务器",
|
||||
"updated": "服务器已更新",
|
||||
"created": "服务器已创建",
|
||||
"saveFailed": "保存失败,请重试",
|
||||
"noExportData": "没有可导出的数据",
|
||||
"exported": "已导出 {{count}} 台服务器",
|
||||
"deletedCount": "已删除 {{count}} 台服务器",
|
||||
"deleteFailed": "删除失败",
|
||||
"batchDeleteFailed": "批量删除失败",
|
||||
"csv": {
|
||||
"hostname": "主机名",
|
||||
"ip": "IP地址",
|
||||
"publicIp": "公网IP",
|
||||
"port": "端口",
|
||||
"os": "操作系统",
|
||||
"cpu": "CPU(核)",
|
||||
"memory": "内存(GB)",
|
||||
"disk": "磁盘(GB)",
|
||||
"status": "状态",
|
||||
"tags": "标签"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user