/** * 生成分类变量(分箱)对话框 - 改进版 * * 改进: * 1. 显示所有列(不过滤) * 2. 自定义切点UI更友好 * 3. 提供示例说明 */ import React, { useState } from 'react'; import { Modal, Select, Input, Button, Radio, Space, Tag, App, Alert } from 'antd'; import { Plus, X, Info } from 'lucide-react'; interface BinningDialogProps { visible: boolean; columns: Array<{ id: string; name: string; type?: string }>; sessionId: string | null; onClose: () => void; onApply: (newData: any[]) => void; } const BinningDialog: React.FC = ({ visible, columns, sessionId, onClose, onApply, }) => { const { message } = App.useApp(); const [selectedColumn, setSelectedColumn] = useState(''); const [method, setMethod] = useState<'custom' | 'equal_width' | 'equal_freq'>('equal_width'); const [newColumnName, setNewColumnName] = useState(''); // 自定义切点(改进:只存储切点值,标签自动生成) const [customBins, setCustomBins] = useState('18, 60'); const [customLabels, setCustomLabels] = useState('青少年, 成年, 老年'); // 等宽/等频 const [numBins, setNumBins] = useState(3); const [autoLabels, setAutoLabels] = useState(['低', '中', '高']); const [loading, setLoading] = useState(false); // 更新列选择 const handleColumnChange = (value: string) => { setSelectedColumn(value); const column = columns.find((c) => c.id === value); if (column) { setNewColumnName(`${column.name}_分组`); } }; // 执行分箱 const handleApply = async () => { if (!sessionId || !selectedColumn) { message.error('请选择列'); return; } if (!newColumnName) { message.warning('请输入新列名'); return; } let params: any = { column: selectedColumn, method, newColumnName, }; if (method === 'custom') { // 解析切点 const binsArray = customBins.split(',').map(b => parseFloat(b.trim())).filter(b => !isNaN(b)); if (binsArray.length < 2) { message.warning('至少需要2个切点(用逗号分隔,如:18, 60)'); return; } // 检查是否升序 const sorted = [...binsArray].sort((a, b) => a - b); if (JSON.stringify(binsArray) !== JSON.stringify(sorted)) { message.warning('切点必须按从小到大排列'); return; } // 解析标签 const labelsArray = customLabels.split(',').map(l => l.trim()).filter(l => l); if (labelsArray.length > 0 && labelsArray.length !== binsArray.length - 1) { message.warning(`需要${binsArray.length - 1}个标签(切点数-1),或留空自动生成`); return; } params.bins = binsArray; params.labels = labelsArray.length > 0 ? labelsArray : undefined; } else { // 等宽/等频 params.numBins = numBins; // 解析标签 const labelsArray = autoLabels.filter(l => l); if (labelsArray.length > 0 && labelsArray.length !== numBins) { message.warning(`需要${numBins}个标签,或留空自动生成`); return; } if (labelsArray.length > 0) { params.labels = labelsArray; } } setLoading(true); try { const response = await fetch('/api/v1/dc/tool-c/quick-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, action: 'binning', params, }), }); const result = await response.json(); if (result.success) { message.success('分箱成功!'); onApply(result.data.newDataPreview); onClose(); } else { message.error({ content: result.error || '分箱失败', duration: 5, }); } } catch (error: any) { console.error('[BinningDialog] 执行失败:', error); message.error({ content: '网络错误,请检查服务是否正常运行', duration: 5, }); } finally { setLoading(false); } }; return (
{/* 选择列 */}
{ setNumBins(value); if (value === 3) { setAutoLabels(['低', '中', '高']); } else if (value === 4) { setAutoLabels(['低', '中低', '中高', '高']); } else if (value === 5) { setAutoLabels(['极低', '低', '中', '高', '极高']); } else { setAutoLabels(Array.from({ length: value }, (_, i) => `组${i + 1}`)); } }} style={{ width: '100%' }} options={[ { value: 2, label: '2组(二分类)' }, { value: 3, label: '3组(低、中、高)' }, { value: 4, label: '4组(四分位)' }, { value: 5, label: '5组(五分类)' }, ]} />
{autoLabels.map((label, index) => ( {label} ))}
)} {/* 自定义切点配置(改进版) */} {method === 'custom' && (
切点:用逗号分隔的数字,如 18, 60
结果:生成3组(<18、18-60、>60)
标签:可选,用逗号分隔,如 青少年, 成年, 老年
注意:切点数量-1 = 标签数量(如2个切点需要3个标签)
} type="info" showIcon icon={} className="mb-3" />
setCustomBins(e.target.value)} />
setCustomLabels(e.target.value)} />
留空则使用默认区间标签(如:[18.0, 60.0))
)} {/* 新列名 */}
setNewColumnName(e.target.value)} />
)} {/* 操作按钮 */}
); }; export default BinningDialog;