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

@@ -10,6 +10,13 @@ import Header from './components/Header';
import Toolbar from './components/Toolbar';
import DataGrid from './components/DataGrid';
import Sidebar from './components/Sidebar';
import FilterDialog from './components/FilterDialog';
import RecodeDialog from './components/RecodeDialog';
import BinningDialog from './components/BinningDialog';
import ConditionalDialog from './components/ConditionalDialog';
import DropnaDialog from './components/DropnaDialog';
import ComputeDialog from './components/ComputeDialog';
import PivotDialog from './components/PivotDialog';
import * as api from '../../api/toolC';
// ==================== 类型定义 ====================
@@ -29,6 +36,15 @@ interface ToolCState {
// UI状态
isLoading: boolean;
isSidebarOpen: boolean;
// ✨ 功能按钮对话框状态
filterDialogVisible: boolean;
recodeDialogVisible: boolean;
binningDialogVisible: boolean;
conditionalDialogVisible: boolean;
dropnaDialogVisible: boolean;
computeDialogVisible: boolean;
pivotDialogVisible: boolean;
}
interface Message {
@@ -53,6 +69,13 @@ const ToolC = () => {
messages: [],
isLoading: false,
isSidebarOpen: true,
filterDialogVisible: false,
recodeDialogVisible: false,
binningDialogVisible: false,
conditionalDialogVisible: false,
dropnaDialogVisible: false,
computeDialogVisible: false,
pivotDialogVisible: false,
});
// 更新状态辅助函数
@@ -77,9 +100,38 @@ const ToolC = () => {
// 获取预览数据
const preview = await api.getPreviewData(result.data.sessionId);
console.log('[ToolC] 📊 后端返回的预览数据:', preview);
console.log('[ToolC] 📊 preview.data:', preview.data);
console.log('[ToolC] 📊 preview.data.previewData:', preview.data.previewData);
console.log('[ToolC] 📊 preview.data.previewData长度:', preview.data.previewData?.length);
if (preview.success) {
const previewData = preview.data.previewData || preview.data.rows || [];
console.log('[ToolC] 📊 实际使用的数据:', previewData);
console.log('[ToolC] 📊 数据长度:', previewData.length);
console.log('[ToolC] 📊 第一行数据:', previewData[0]);
// ✅ 关键调试查看数据的keys和列定义是否匹配
if (previewData[0]) {
const dataKeys = Object.keys(previewData[0]);
const definedColumns = preview.data.columns;
console.log('[ToolC] 🔑 数据的实际keys:', dataKeys);
console.log('[ToolC] 📋 后端返回的columns:', definedColumns);
console.log('[ToolC] ❓ keys和columns是否匹配:',
dataKeys.length === definedColumns.length &&
dataKeys.every(key => definedColumns.includes(key))
);
// 输出第一行数据的详细内容
console.log('[ToolC] 📝 第一行数据详情:');
dataKeys.slice(0, 5).forEach(key => {
console.log(` ${key}: ${previewData[0][key]}`);
});
}
updateState({
data: preview.data.previewData || preview.data.rows || [],
data: previewData,
columns: preview.data.columns.map((col) => ({
id: col,
name: col,
@@ -111,8 +163,51 @@ const ToolC = () => {
}
};
// ==================== AI消息发送已由 ChatContainer 处理) ====================
// 此功能已移至 Sidebar 中的 ChatContainer 内部
// ==================== 功能按钮数据更新 ====================
const handleQuickActionDataUpdate = (newData: any[]) => {
if (newData && newData.length > 0) {
const newColumns = Object.keys(newData[0]).map(key => ({
id: key,
name: key,
type: 'text',
}));
updateState({
data: newData,
columns: newColumns,
});
}
};
// ==================== 导出Excel ====================
const handleExport = async () => {
if (!state.sessionId) {
alert('请先上传文件');
return;
}
try {
// ✅ 从后端读取完整数据AI处理后的数据已保存到OSS
const response = await fetch(`/api/v1/dc/tool-c/sessions/${state.sessionId}/export`);
if (!response.ok) {
throw new Error('导出失败');
}
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${state.fileName.replace(/\.[^/.]+$/, '')}_cleaned.xlsx`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error: any) {
console.error('导出失败:', error);
alert('导出失败:' + error.message);
}
};
// ==================== 心跳机制 ====================
useEffect(() => {
@@ -137,7 +232,7 @@ const ToolC = () => {
{/* 顶部栏 */}
<Header
fileName={state.fileName || '未上传文件'}
onExport={() => alert('导出功能开发中...')}
onExport={handleExport}
isSidebarOpen={state.isSidebarOpen}
onToggleSidebar={() => updateState({ isSidebarOpen: !state.isSidebarOpen })}
/>
@@ -146,7 +241,16 @@ const ToolC = () => {
<div className="flex-1 flex overflow-hidden">
{/* 左侧:表格区域 */}
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
<Toolbar />
<Toolbar
sessionId={state.sessionId}
onFilterClick={() => updateState({ filterDialogVisible: true })}
onRecodeClick={() => updateState({ recodeDialogVisible: true })}
onBinningClick={() => updateState({ binningDialogVisible: true })}
onConditionalClick={() => updateState({ conditionalDialogVisible: true })}
onDropnaClick={() => updateState({ dropnaDialogVisible: true })}
onComputeClick={() => updateState({ computeDialogVisible: true })}
onPivotClick={() => updateState({ pivotDialogVisible: true })}
/>
<div className="flex-1 p-4 overflow-hidden">
<DataGrid data={state.data} columns={state.columns} />
</div>
@@ -159,10 +263,85 @@ const ToolC = () => {
onClose={() => updateState({ isSidebarOpen: false })}
sessionId={state.sessionId}
onFileUpload={handleFileUpload}
onDataUpdate={(newData) => updateState({ data: newData })}
onDataUpdate={(newData) => {
// ✅ 修复:同时更新列定义(从新数据中提取列名)
if (newData && newData.length > 0) {
const newColumns = Object.keys(newData[0]).map(key => ({
id: key,
name: key,
type: 'text',
}));
updateState({
data: newData,
columns: newColumns,
});
} else {
updateState({ data: newData });
}
}}
/>
)}
</div>
{/* ✨ 功能按钮对话框 */}
<FilterDialog
visible={state.filterDialogVisible}
columns={state.columns}
sessionId={state.sessionId}
onClose={() => updateState({ filterDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<RecodeDialog
visible={state.recodeDialogVisible}
columns={state.columns}
data={state.data}
sessionId={state.sessionId}
onClose={() => updateState({ recodeDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<BinningDialog
visible={state.binningDialogVisible}
columns={state.columns}
sessionId={state.sessionId}
onClose={() => updateState({ binningDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<ConditionalDialog
visible={state.conditionalDialogVisible}
columns={state.columns.map(col => ({ field: col.id, headerName: col.name }))}
data={state.data}
sessionId={state.sessionId}
onClose={() => updateState({ conditionalDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<DropnaDialog
visible={state.dropnaDialogVisible}
columns={state.columns}
data={state.data}
sessionId={state.sessionId}
onClose={() => updateState({ dropnaDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<ComputeDialog
visible={state.computeDialogVisible}
columns={state.columns}
sessionId={state.sessionId}
onClose={() => updateState({ computeDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
<PivotDialog
visible={state.pivotDialogVisible}
columns={state.columns}
sessionId={state.sessionId}
onClose={() => updateState({ pivotDialogVisible: false })}
onApply={handleQuickActionDataUpdate}
/>
</div>
);
};