Files
aiops/frontend/src/components/cloud/BillingRecordDialog.tsx
T
attacker 55f216cfbb feat(i18n): 安全批量转换 135 文件 — 覆盖率 34%→62%
使用 i18n.t() 全局方式(避免 hook 定位问题):
- 288 目标文件中 135 个成功转换
- 2228 处中文替换为 i18n.t() 调用
- 1006 个新翻译 key 添加到 zh-CN.json + en-US.json
- 词典总量: 3554 keys (22 namespaces)
- tsc 零错误 + Vite 构建成功
- 6 个含代码示例/数据字符串的文件安全回退
2026-07-03 02:14:20 +08:00

152 lines
6.0 KiB
TypeScript

// 云账单成本记录弹窗(新建/编辑)— 自包含状态,从 CloudBilling 提取 // Logan.Li
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import { billingApi } from '@/api';
import { providerLabels } from './CloudBillingHelpers';
import type { JsonData } from '@/types/common';
import i18n from '@/i18n';
const emptyRecord = {
provider: 'aliyun',
billing_date: '',
cost: '',
service: '',
region: '',
account_name: '',
description: '',
};
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
editingRecord: JsonData | null;
onSuccess: () => void;
}
export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSuccess }: Props) {
const [form, setForm] = useState<JsonData>(emptyRecord);
const [submitting, setSubmitting] = useState(false);
// 弹窗打开时初始化表单(新建预填今日日期,编辑回填数据) // Logan.Li
useEffect(() => {
if (!open) return;
if (editingRecord) {
setForm({
provider: editingRecord.provider,
billing_date: editingRecord.billing_date,
cost: editingRecord.cost,
service: editingRecord.service || '',
region: editingRecord.region || '',
account_name: editingRecord.account_name || '',
description: editingRecord.description || '',
});
} else {
setForm({ ...emptyRecord, billing_date: new Date().toISOString().slice(0, 10) });
}
}, [open, editingRecord]);
const handleSave = async () => {
if (!form.provider || !form.billing_date || !form.cost) {
toast.error(i18n.t('cloud.billingrecorddi.sd8a97c17'));
return;
}
setSubmitting(true);
try {
const data = { ...form, cost: parseFloat(form.cost) };
if (editingRecord) {
await billingApi.remove(editingRecord.id);
await billingApi.create(data);
} else {
await billingApi.create(data);
}
onOpenChange(false);
onSuccess();
toast('保存成功');
} catch (e) {
toast.error(i18n.t('kubernetes.msg.saveFailed'), { description: (e as JsonData).message });
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{editingRecord ? i18n.t('cloud.billingrecorddi.s3c91e420') : i18n.t('cloud.recordCost')}</DialogTitle>
<DialogDescription> {i18n.t('cloud.billingrecorddi.s09e541fd')} </DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.billingrecorddi.sb11d8416')} </Label>
<Select value={form.provider} onValueChange={(v) => setForm({ ...form, provider: v })}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
{Object.entries(providerLabels).map(([k, v]) => (
<SelectItem key={k} value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.billingrecorddi.s3abdefe2')} </Label>
<Input type="date" className="mt-1" value={form.billing_date} onChange={(e) => setForm({ ...form, billing_date: e.target.value })} />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.billingrecorddi.s6f252687')} </Label>
<Input type="number" step="0.01" className="mt-1" placeholder="0.00" value={form.cost} onChange={(e) => setForm({ ...form, cost: e.target.value })} />
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.colService')} </Label>
<Input className="mt-1" placeholder="ECS, RDS..." value={form.service} onChange={(e) => setForm({ ...form, service: e.target.value })} />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.colRegion')} </Label>
<Input className="mt-1" placeholder="cn-hangzhou" value={form.region} onChange={(e) => setForm({ ...form, region: e.target.value })} />
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('cloud.accountName')} </Label>
<Input className="mt-1" placeholder={i18n.t('cloud.billingrecorddi.s0010795e')} value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} />
</div>
</div>
<div>
<Label className="text-sm text-[var(--dg-text-secondary)]"> {i18n.t('monitor.contactGroup.notes')} </Label>
<Input className="mt-1" placeholder={i18n.t('rbac.jobRole.placeholder.description')} value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}> {i18n.t('rbac.jobRole.cancel')} </Button>
<Button onClick={handleSave} disabled={submitting}>
{submitting && <Loader2 className="h-4 w-4 mr-1 animate-spin" />}
保存
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}