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,262 @@
import React, { useState, useEffect } from 'react';
import { Modal, Select, Button, Alert, Checkbox, Radio, App, Tag } from 'antd';
import { ArrowLeftRight, Info } from 'lucide-react';
interface Props {
visible: boolean;
onClose: () => void;
onApply: (newData: any[]) => void;
columns: Array<{ id: string; name: string }>;
sessionId: string | null;
}
const PivotDialog: React.FC<Props> = ({
visible,
onClose,
onApply,
columns,
sessionId,
}) => {
const { message } = App.useApp();
const [indexColumn, setIndexColumn] = useState<string>('');
const [pivotColumn, setPivotColumn] = useState<string>('');
const [valueColumns, setValueColumns] = useState<string[]>([]);
const [aggfunc, setAggfunc] = useState<'first' | 'last' | 'mean' | 'sum'>('first');
const [loading, setLoading] = useState(false);
// 重置状态
useEffect(() => {
if (visible) {
setIndexColumn('');
setPivotColumn('');
setValueColumns([]);
setAggfunc('first');
}
}, [visible]);
// 执行
const handleApply = async () => {
if (!sessionId) {
message.error('Session ID不存在');
return;
}
// 验证
if (!indexColumn) {
message.warning('请选择索引列(唯一标识列)');
return;
}
if (!pivotColumn) {
message.warning('请选择透视列(要变成列名的列)');
return;
}
if (valueColumns.length === 0) {
message.warning('请至少选择一个值列');
return;
}
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: 'pivot',
params: {
indexColumn,
pivotColumn,
valueColumns,
aggfunc,
},
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Pivot转换失败');
}
message.success('Pivot转换成功');
// 更新父组件数据
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">
<ArrowLeftRight size={20} className="text-purple-500" />
<span>Pivot</span>
</div>
}
open={visible}
onCancel={onClose}
width={700}
footer={[
<Button key="cancel" onClick={onClose}>
</Button>,
<Button
key="apply"
type="primary"
onClick={handleApply}
loading={loading}
icon={<ArrowLeftRight size={16} />}
>
</Button>,
]}
>
<div className="space-y-4">
{/* 说明 */}
<Alert
title="功能说明"
description={
<div className="text-xs space-y-1">
<div> "一人多行""一人一行"</div>
<div> 访线访2访1</div>
<div> Event_Name列的"基线""随访2周" FMA_基线FMA_随访2周</div>
</div>
}
type="info"
showIcon
icon={<Info size={16} />}
className="mb-4"
/>
{/* 索引列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
ID<span className="text-red-500">*</span>
</label>
<Select
placeholder="选择唯一标识列"
value={indexColumn || undefined}
onChange={setIndexColumn}
className="w-full"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={columns.map(col => ({ label: col.name, value: col.id }))}
/>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
{/* 透视列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
访<span className="text-red-500">*</span>
</label>
<Select
placeholder="选择透视列"
value={pivotColumn || undefined}
onChange={setPivotColumn}
className="w-full"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={columns
.filter(col => col.id !== indexColumn)
.map(col => ({ label: col.name, value: col.id }))}
/>
<div className="text-xs text-slate-500 mt-1">
</div>
</div>
{/* 值列 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
<span className="text-red-500">*</span>
</label>
<div className="border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50">
<Checkbox.Group
value={valueColumns}
onChange={setValueColumns}
className="flex flex-col gap-2"
>
{columns
.filter(col => col.id !== indexColumn && col.id !== pivotColumn)
.map((col) => (
<Checkbox key={col.id} value={col.id}>
{col.name}
</Checkbox>
))}
</Checkbox.Group>
</div>
<div className="text-xs text-slate-500 mt-1">
FMA_基线FMA_随访2周
</div>
</div>
{/* 聚合方式 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-2 block">
</label>
<Radio.Group value={aggfunc} onChange={(e) => setAggfunc(e.target.value)}>
<div className="space-y-2">
<Radio value="first">
<div className="ml-2">
<span className="font-medium"></span>
<span className="text-xs text-slate-500 ml-2"></span>
</div>
</Radio>
<Radio value="last">
<span className="ml-2 font-medium"></span>
</Radio>
<Radio value="mean">
<span className="ml-2 font-medium"></span>
</Radio>
<Radio value="sum">
<span className="ml-2 font-medium"></span>
</Radio>
</div>
</Radio.Group>
<div className="text-xs text-slate-500 mt-2">
+
</div>
</div>
{/* 警告 */}
<Alert
title="重要提示"
description={
<div className="text-xs space-y-1">
<div> Pivot操作会显著改变数据结构</div>
<div> </div>
<div> AI对话探索数据结构</div>
<div> ID</div>
</div>
}
type="warning"
showIcon={false}
className="bg-orange-50 border-orange-200"
/>
</div>
</Modal>
);
};
export default PivotDialog;