import React, { useState, useEffect } from 'react'; import { Card, Table, Button, Input, Select, Space, Form, message, Switch, Typography, InputNumber, Tabs, Modal, Popconfirm, } from 'antd'; import { SearchOutlined, ReloadOutlined, EditOutlined, PlusOutlined, DeleteOutlined, CheckOutlined } from '@ant-design/icons'; import { VmPricingApi } from '../services/api'; const { Option } = Select; const { Title } = Typography; const { TabPane } = Tabs; interface VmSpecItem { id: number; spec_type: string; cpu_cores: number; memory_gb: number; description?: string; cost_price_per_minute: number; markup_rate: number; is_active: boolean; created_at: string; updated_at: string; } interface VmTemplateItem { id: number; spec_type: string; template_id: string; is_default: boolean; created_at: string; updated_at: string; } const VmPricing: React.FC = () => { const [loading, setLoading] = useState(false); const [specsData, setSpecsData] = useState([]); const [templatesData, setTemplatesData] = useState([]); const [specsTotal, setSpecsTotal] = useState(0); const [templatesTotal, setTemplatesTotal] = useState(0); const [specsPage, setSpecsPage] = useState(1); const [templatesPage, setTemplatesPage] = useState(1); const [specsPageSize, setSpecsPageSize] = useState(10); const [templatesPageSize, setTemplatesPageSize] = useState(10); const [activeTab, setActiveTab] = useState('specs'); const [editingCell, setEditingCell] = useState<{ rowId: number; field: string } | null>(null); const [editingValue, setEditingValue] = useState(0); const [specModalVisible, setSpecModalVisible] = useState(false); const [templateModalVisible, setTemplateModalVisible] = useState(false); const [specsForm] = Form.useForm(); const [templatesForm] = Form.useForm(); const [templateForm] = Form.useForm(); const [specForm] = Form.useForm(); const [specTypes, setSpecTypes] = useState([]); // 获取规格类型列表(用于模板表单) const fetchSpecTypes = async () => { try { const response = await VmPricingApi.getSpecs({ size: 1000 }); const types = [...new Set((response.data || []).map((item: VmSpecItem) => item.spec_type))]; setSpecTypes(types); } catch (error) { console.error('获取规格类型失败:', error); } }; // 获取规格数据 const fetchSpecs = async (searchParams: any = {}) => { setLoading(true); try { const response = await VmPricingApi.getSpecs({ ...searchParams, page: specsPage, size: specsPageSize, }); setSpecsData(response.data || []); setSpecsTotal(response.total || 0); } catch (error) { console.error('获取规格数据失败:', error); message.error('获取规格数据失败'); } finally { setLoading(false); } }; // 获取模板数据 const fetchTemplates = async (searchParams: any = {}) => { setLoading(true); try { const response = await VmPricingApi.getTemplates({ ...searchParams, page: templatesPage, size: templatesPageSize, }); setTemplatesData(response.data || []); setTemplatesTotal(response.total || 0); } catch (error) { console.error('获取模板数据失败:', error); message.error('获取模板数据失败'); } finally { setLoading(false); } }; useEffect(() => { fetchSpecTypes(); // 获取规格类型列表 if (activeTab === 'specs') { fetchSpecs(); } else { fetchTemplates(); } }, [specsPage, specsPageSize, templatesPage, templatesPageSize, activeTab]); useEffect(() => { // 当规格数据更新时,更新规格类型列表 if (specsData.length > 0) { const types = [...new Set(specsData.map(item => item.spec_type))]; setSpecTypes(types); } }, [specsData]); // 更新规格 const handleUpdateSpec = async (id: number, field: string, value: number | boolean) => { try { await VmPricingApi.updateSpec(id, { [field]: value }); message.success('更新成功'); setEditingCell(null); fetchSpecs(); } catch (error) { message.error('更新失败'); console.error('更新规格失败:', error); } }; // 更新状态 const handleUpdateStatus = async (id: number, isActive: boolean) => { handleUpdateSpec(id, 'is_active', isActive); }; // 搜索规格 const handleSpecsSearch = (values: any) => { setSpecsPage(1); fetchSpecs(values); }; // 重置规格搜索 const handleSpecsReset = () => { specsForm.resetFields(); setSpecsPage(1); fetchSpecs(); }; // 搜索模板 const handleTemplatesSearch = (values: any) => { setTemplatesPage(1); fetchTemplates(values); }; // 重置模板搜索 const handleTemplatesReset = () => { templatesForm.resetFields(); setTemplatesPage(1); fetchTemplates(); }; // 开始编辑 const handleEdit = (rowId: number, field: string, value: number) => { setEditingCell({ rowId, field }); setEditingValue(value); }; // 保存编辑 const handleSaveEdit = (rowId: number, field: string) => { let valueToSave = editingValue; // 加价率需要转换为小数(前端显示为百分比,后端存储为小数) if (field === 'markup_rate') { valueToSave = editingValue / 100; } handleUpdateSpec(rowId, field, valueToSave); }; // 取消编辑 const handleCancelEdit = () => { setEditingCell(null); }; // 创建规格 const handleCreateSpec = async (values: any) => { try { // 转换加价率:前端输入百分比,后端存储小数 const data = { ...values, markup_rate: values.markup_rate ? values.markup_rate / 100 : undefined, }; await VmPricingApi.createSpec(data); message.success('规格创建成功'); setSpecModalVisible(false); specForm.resetFields(); fetchSpecs(); fetchSpecTypes(); // 刷新规格类型列表 } catch (error: any) { message.error(error?.response?.data?.message || '规格创建失败'); console.error('创建规格失败:', error); } }; // 创建模板 const handleCreateTemplate = async (values: any) => { try { await VmPricingApi.createTemplate(values); message.success('模板创建成功'); setTemplateModalVisible(false); templateForm.resetFields(); fetchTemplates(); } catch (error: any) { message.error(error?.response?.data?.message || '模板创建失败'); console.error('创建模板失败:', error); } }; // 删除规格 const handleDeleteSpec = async (id: number) => { try { await VmPricingApi.deleteSpec(id); message.success('规格删除成功'); fetchSpecs(); fetchSpecTypes(); // 刷新规格类型列表 } catch (error) { message.error('规格删除失败'); console.error('删除规格失败:', error); } }; // 删除模板 const handleDeleteTemplate = async (id: number) => { try { await VmPricingApi.deleteTemplate(id); message.success('模板删除成功'); fetchTemplates(); } catch (error) { message.error('模板删除失败'); console.error('删除模板失败:', error); } }; // 设置默认模板 const handleSetDefaultTemplate = async (id: number) => { try { await VmPricingApi.setDefaultTemplate(id); message.success('默认模板设置成功'); fetchTemplates(); } catch (error) { message.error('设置默认模板失败'); console.error('设置默认模板失败:', error); } }; // 规格表格列 const specsColumns = [ { title: '配置类型', dataIndex: 'spec_type', key: 'spec_type', width: 120, }, { title: 'CPU核心', dataIndex: 'cpu_cores', key: 'cpu_cores', width: 100, }, { title: '内存(GB)', dataIndex: 'memory_gb', key: 'memory_gb', width: 100, }, { title: '描述', dataIndex: 'description', key: 'description', width: 150, render: (text: string) => text || '-', }, { title: '成本价($/分钟)', dataIndex: 'cost_price_per_minute', key: 'cost_price_per_minute', width: 160, render: (value: number, record: VmSpecItem) => { if (editingCell?.rowId === record.id && editingCell?.field === 'cost_price_per_minute') { return ( setEditingValue(val || 0)} precision={8} min={0} style={{ width: 120 }} autoFocus /> ); } return (
handleEdit(record.id, 'cost_price_per_minute', value)} > ${value.toFixed(8)}
); }, }, { title: '加价率(%)', dataIndex: 'markup_rate', key: 'markup_rate', width: 140, render: (value: number, record: VmSpecItem) => { if (editingCell?.rowId === record.id && editingCell?.field === 'markup_rate') { return ( setEditingValue(val || 0)} precision={4} min={0} max={100} style={{ width: 100 }} autoFocus formatter={(value) => `${value}%`} parser={(value) => value!.replace('%', '')} /> ); } return (
handleEdit(record.id, 'markup_rate', value * 100)} > {(value * 100).toFixed(2)}%
); }, }, { title: '状态', dataIndex: 'is_active', key: 'is_active', width: 80, render: (value: boolean, record: VmSpecItem) => ( handleUpdateStatus(record.id, checked)} /> ), }, { title: '操作', key: 'action', width: 100, render: (_: any, record: VmSpecItem) => ( handleDeleteSpec(record.id)} okText="确定" cancelText="取消" > ), }, ]; // 模板表格列 const templatesColumns = [ { title: '配置类型', dataIndex: 'spec_type', key: 'spec_type', width: 120, }, { title: '模板ID', dataIndex: 'template_id', key: 'template_id', width: 200, }, { title: '默认模板', dataIndex: 'is_default', key: 'is_default', width: 100, render: (value: boolean, record: VmTemplateItem) => ( value ? ( ) : ( ) ), }, { title: '操作', key: 'action', width: 120, render: (_: any, record: VmTemplateItem) => ( handleDeleteTemplate(record.id)} okText="确定" cancelText="取消" > ), }, ]; return ( 虚拟机价格配置}>
`共 ${total} 条`, onChange: (page, pageSize) => { setSpecsPage(page); setSpecsPageSize(pageSize || 10); }, }} />
`共 ${total} 条`, onChange: (page, pageSize) => { setTemplatesPage(page); setTemplatesPageSize(pageSize || 10); }, }} /> {/* 添加规格模态框 */} { setSpecModalVisible(false); specForm.resetFields(); }} footer={null} >
`${value}%`} parser={(value) => value!.replace('%', '')} />
{/* 添加模板模态框 */} { setTemplateModalVisible(false); templateForm.resetFields(); }} footer={null} >
); }; export default VmPricing;