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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user