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,315 @@
import React, { useState } from 'react';
import { Modal, Input, Button, Alert, Collapse, Tag, App } from 'antd';
import { Calculator, Lightbulb, BookOpen } from 'lucide-react';
interface Props {
visible: boolean;
onClose: () => void;
onApply: (newData: any[]) => void;
columns: Array<{ id: string; name: string }>;
sessionId: string | null;
}
const ComputeDialog: React.FC<Props> = ({
visible,
onClose,
onApply,
columns,
sessionId,
}) => {
const { message } = App.useApp();
const [newColumnName, setNewColumnName] = useState('新列');
const [formula, setFormula] = useState('');
const [loading, setLoading] = useState(false);
// 公式示例
const examples = [
{
name: 'BMI计算',
formula: '体重 / (身高/100)**2',
description: '需要身高(cm)和体重(kg)列',
},
{
name: '年龄分组',
formula: 'round(年龄 / 10) * 10',
description: '按10岁为一组',
},
{
name: '综合得分',
formula: '(FMA得分 * 0.6 + ADL得分 * 0.4)',
description: '加权平均分',
},
{
name: '变化率(%)',
formula: '(随访值 - 基线值) / 基线值 * 100',
description: '计算变化百分比',
},
{
name: '对数转换',
formula: 'log(值 + 1)',
description: '对数变换(处理偏态分布)',
},
];
// 支持的函数
const functions = [
{ name: 'abs(x)', desc: '绝对值' },
{ name: 'round(x)', desc: '四舍五入' },
{ name: 'sqrt(x)', desc: '平方根' },
{ name: 'log(x)', desc: '自然对数' },
{ name: 'log10(x)', desc: '常用对数' },
{ name: 'exp(x)', desc: '指数函数' },
{ name: 'floor(x)', desc: '向下取整' },
{ name: 'ceil(x)', desc: '向上取整' },
{ name: 'min(a,b)', desc: '最小值' },
{ name: 'max(a,b)', desc: '最大值' },
];
// 使用示例公式
const useExample = (exampleFormula: string) => {
setFormula(exampleFormula);
};
// 执行
const handleApply = async () => {
if (!sessionId) {
message.error('Session ID不存在');
return;
}
if (!newColumnName.trim()) {
message.warning('请输入新列名');
return;
}
if (!formula.trim()) {
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: 'compute',
params: {
newColumnName: newColumnName.trim(),
formula: formula.trim(),
},
}),
});
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">
<Calculator size={20} className="text-blue-500" />
<span></span>
</div>
}
open={visible}
onCancel={onClose}
width={800}
footer={[
<Button key="cancel" onClick={onClose}>
</Button>,
<Button
key="apply"
type="primary"
onClick={handleApply}
loading={loading}
icon={<Calculator size={16} />}
>
</Button>,
]}
>
<div className="space-y-4">
{/* 说明 */}
<Alert
title="如何使用"
description={
<div className="text-xs space-y-1">
<div> </div>
<div> +, -, *, /**</div>
<div> abs, round, sqrt, log等</div>
<div> , , </div>
</div>
}
type="info"
showIcon
className="mb-4"
/>
{/* 新列名 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
</label>
<Input
placeholder="如BMI, 综合得分"
value={newColumnName}
onChange={(e) => setNewColumnName(e.target.value)}
size="large"
/>
</div>
{/* 计算公式 */}
<div>
<label className="text-sm font-medium text-slate-700 mb-1 block">
</label>
<Input.TextArea
placeholder="如:体重 / (身高/100)**2"
value={formula}
onChange={(e) => setFormula(e.target.value)}
rows={3}
size="large"
className="font-mono"
/>
<div className="text-xs text-slate-500 mt-1">
使 +-*/**()
</div>
</div>
{/* 可用列名 */}
<div>
<div className="text-sm font-medium text-slate-700 mb-2 flex items-center gap-2">
<BookOpen size={16} />
</div>
<div className="flex flex-wrap gap-2 p-3 bg-slate-50 rounded-lg max-h-32 overflow-y-auto">
{columns.map((col) => (
<Tag
key={col.id}
color="blue"
className="cursor-pointer hover:bg-blue-100"
onClick={() => {
setFormula(formula + (formula ? ' ' : '') + col.name);
}}
>
{col.name}
</Tag>
))}
</div>
</div>
{/* 公式示例 */}
<Collapse
items={[
{
key: 'examples',
label: (
<div className="flex items-center gap-2">
<Lightbulb size={16} className="text-yellow-500" />
<span className="font-medium">使</span>
</div>
),
children: (
<div className="space-y-2">
{examples.map((example, index) => (
<div
key={index}
className="border rounded-lg p-3 hover:bg-blue-50 cursor-pointer transition-colors"
onClick={() => useExample(example.formula)}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="font-medium text-sm text-blue-600">
{example.name}
</div>
<div className="text-xs text-slate-600 mt-1">
{example.description}
</div>
<code className="text-xs bg-slate-100 px-2 py-1 rounded mt-2 block font-mono">
{example.formula}
</code>
</div>
</div>
</div>
))}
</div>
),
},
{
key: 'functions',
label: (
<div className="flex items-center gap-2">
<Calculator size={16} className="text-green-500" />
<span className="font-medium"></span>
</div>
),
children: (
<div className="grid grid-cols-2 gap-2">
{functions.map((func, index) => (
<div
key={index}
className="flex items-center justify-between p-2 bg-slate-50 rounded"
>
<code className="text-xs font-mono text-blue-600">
{func.name}
</code>
<span className="text-xs text-slate-600">
{func.desc}
</span>
</div>
))}
</div>
),
},
]}
defaultActiveKey={['examples']}
className="bg-white"
/>
{/* 温馨提示 */}
<Alert
message={
<div className="text-xs">
<strong></strong>
<div className="mt-1 space-y-1">
<div> </div>
<div> NaN值</div>
<div> logsqrt等函数对负数/NaN</div>
<div> 使</div>
</div>
</div>
}
type="warning"
showIcon={false}
className="bg-yellow-50 border-yellow-200"
/>
</div>
</Modal>
);
};
export default ComputeDialog;