refactor: 拆分CloudBilling.tsx(663→467行)提取BillingRecordDialog+BillingRuleDialog子模块
This commit is contained in:
1 parent
f865d41547
commit
accbfb53c3
4 files changed
+340
-220
No files matched your search
@@ -0,0 +1,149 @@
|
||||
// 云账单成本记录弹窗(新建/编辑)— 自包含状态,从 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 '@/services/api';
|
||||
import { providerLabels } from './CloudBillingHelpers';
|
||||
|
||||
const emptyRecord = {
|
||||
provider: 'aliyun',
|
||||
billing_date: '',
|
||||
cost: '',
|
||||
service: '',
|
||||
region: '',
|
||||
account_name: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
editingRecord: any | null;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function BillingRecordDialog({ open, onOpenChange, editingRecord, onSuccess }: Props) {
|
||||
const [form, setForm] = useState<any>(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('请填写必填字段');
|
||||
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: any) {
|
||||
toast.error('保存失败', { description: e.message });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingRecord ? '编辑成本记录' : '记录成本'}</DialogTitle>
|
||||
<DialogDescription>记录一条每日云资源成本</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="text-sm text-slate-300">厂商 *</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-slate-300">日期 *</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-slate-300">费用(元) *</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-slate-300">服务</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-slate-300">区域</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-slate-300">账号名称</Label>
|
||||
<Input className="mt-1" placeholder="主账号" value={form.account_name} onChange={(e) => setForm({ ...form, account_name: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm text-slate-300">备注</Label>
|
||||
<Input className="mt-1" placeholder="可选" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>取消</Button>
|
||||
<Button onClick={handleSave} disabled={submitting}>
|
||||
{submitting && <Loader2 className="h-4 w-4 mr-1 animate-spin" />}
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user