feat(dc): Complete Tool C quick action buttons Phase 1-2 - 7 functions

Summary:
- Implement 7 quick action functions (filter, recode, binning, conditional, dropna, compute, pivot)
- Refactor to pre-written Python functions architecture (stable and secure)
- Add 7 Python operations modules with full type hints
- Add 7 frontend Dialog components with user-friendly UI
- Fix NaN serialization issues and auto type conversion
- Update all related documentation

Technical Details:
- Python: operations/ module (filter.py, recode.py, binning.py, conditional.py, dropna.py, compute.py, pivot.py)
- Backend: QuickActionService.ts with 7 execute methods
- Frontend: 7 Dialog components with complete validation
- Toolbar: Enable 7 quick action buttons

Status: Phase 1-2 completed, basic testing passed, ready for further testing
This commit is contained in:
2025-12-08 17:38:08 +08:00
parent af325348b8
commit f729699510
158 changed files with 13814 additions and 273 deletions

View File

@@ -0,0 +1,300 @@
import React, { useState, useEffect } from 'react';
import { Modal, Radio, Button, Slider, Alert, App, Statistic, Row, Col, Checkbox } from 'antd';
import { Trash2, AlertTriangle } from 'lucide-react';
interface Props {
visible: boolean;
onClose: () => void;
onApply: (newData: any[]) => void;
columns: Array<{ id: string; name: string }>;
data: any[];
sessionId: string | null;
}
const DropnaDialog: React.FC<Props> = ({
visible,
onClose,
onApply,
columns,
data,
sessionId,
}) => {
const { message } = App.useApp();
const [method, setMethod] = useState<'row' | 'column' | 'both'>('row');
const [threshold, setThreshold] = useState<number>(50); // 百分比
const [selectedColumns, setSelectedColumns] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
// 计算缺失值统计
const [missingStats, setMissingStats] = useState<{
totalMissing: number;
rowsWithMissing: number;
colsWithMissing: number;
}>({ totalMissing: 0, rowsWithMissing: 0, colsWithMissing: 0 });
useEffect(() => {
if (visible && data.length > 0) {
// 计算缺失值
let totalMissing = 0;
let rowsWithMissing = 0;
const colsMissing: Record<string, number> = {};
data.forEach(row => {
let rowHasMissing = false;
Object.keys(row).forEach(key => {
const value = row[key];
if (value === null || value === undefined || value === '') {
totalMissing++;
colsMissing[key] = (colsMissing[key] || 0) + 1;
rowHasMissing = true;
}
});
if (rowHasMissing) {
rowsWithMissing++;
}
});
setMissingStats({
totalMissing,
rowsWithMissing,
colsWithMissing: Object.keys(colsMissing).length,
});
}
}, [visible, data]);
// 执行
const handleApply = async () => {
if (!sessionId) {
message.error('Session ID不存在');
return;
}
// 验证
if (method === 'row' && selectedColumns.length === 0) {
// 删除所有行:不需要指定列
}
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: 'dropna',
params: {
method,
threshold: threshold / 100, // 转换为0-1
subset: selectedColumns.length > 0 ? selectedColumns : undefined,
},
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || '删除缺失值失败');
}
message.success('删除缺失值成功!');
// 更新父组件数据
if (result.data?.newDataPreview) {
onApply(result.data.newDataPreview);
}
// 成功后关闭
onClose();
} catch (error: any) {
message.error(error.message || '执行失败');
} finally {
setLoading(false);
}
};
return (
<Modal
title={
<div className="flex items-center gap-2">
<Trash2 size={20} className="text-red-500" />
<span></span>
</div>
}
open={visible}
onCancel={onClose}
width={700}
footer={[
<Button key="cancel" onClick={onClose}>
</Button>,
<Button
key="apply"
type="primary"
danger
onClick={handleApply}
loading={loading}
icon={<Trash2 size={16} />}
>
</Button>,
]}
>
<div className="space-y-4">
{/* 缺失值统计 */}
<Alert
message="当前数据缺失值统计"
description={
<Row gutter={16} className="mt-3">
<Col span={8}>
<Statistic
title="总缺失值"
value={missingStats.totalMissing}
suffix={`/ ${data.length * columns.length} 单元格`}
/>
</Col>
<Col span={8}>
<Statistic
title="含缺失值的行"
value={missingStats.rowsWithMissing}
suffix={`/ ${data.length}`}
/>
</Col>
<Col span={8}>
<Statistic
title="含缺失值的列"
value={missingStats.colsWithMissing}
suffix={`/ ${columns.length}`}
/>
</Col>
</Row>
}
type="info"
showIcon
className="mb-4"
/>
{/* 删除方式 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<Radio.Group
value={method}
onChange={(e) => setMethod(e.target.value)}
className="w-full"
>
<div className="space-y-3">
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="row">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="column">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
<div className="border rounded-lg p-3 hover:border-blue-400 transition-colors">
<Radio value="both">
<div className="ml-2">
<div className="font-medium"></div>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
</Radio>
</div>
</div>
</Radio.Group>
</div>
{/* 缺失率阈值(仅对按列删除和先删列再删行有效) */}
{(method === 'column' || method === 'both') && (
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
{threshold}%
</label>
<Slider
min={0}
max={100}
value={threshold}
onChange={setThreshold}
marks={{
0: '0%',
30: '30%',
50: '50%',
70: '70%',
100: '100%',
}}
/>
<div className="text-xs text-slate-500 mt-2">
{threshold}%
</div>
</div>
)}
{/* 仅检查指定列(仅对按行删除有效) */}
{method === 'row' && (
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<div className="border rounded-lg p-3 max-h-48 overflow-y-auto">
<Checkbox.Group
value={selectedColumns}
onChange={setSelectedColumns}
className="flex flex-col gap-2"
>
{columns.map((col) => (
<Checkbox key={col.id} value={col.id}>
{col.name}
</Checkbox>
))}
</Checkbox.Group>
</div>
<div className="text-xs text-slate-500 mt-2">
</div>
</div>
)}
{/* 警告提示 */}
<Alert
message={
<div className="flex items-center gap-2">
<AlertTriangle size={16} />
<span className="font-semibold"></span>
</div>
}
description={
<div className="text-xs space-y-1 mt-1">
<div> </div>
<div> 使AI对话功能探索数据</div>
<div> </div>
<div> 使"多重插补"</div>
</div>
}
type="warning"
showIcon={false}
className="bg-orange-50 border-orange-200"
/>
</div>
</Modal>
);
};
export default DropnaDialog;