feat: 多检测方式推荐引擎(#20,设备/紧急/操作者/成本偏好)

This commit is contained in:
weijuesen
2026-08-12 21:57:42 +08:00
parent 164ae63392
commit 47a2da3d0d
6 changed files with 338 additions and 1 deletions
+12
View File
@@ -71,3 +71,15 @@ export const uploadSpectrumEntryFile = (file: File) => {
return post<{ url: string }>('/spectrum-entries/upload', fd);
};
export const deleteSpectrumEntry = (id: string) => del(`/spectrum-entries/${id}`);
export interface DetectionRecommendation {
method: string;
name: string;
reason: string;
time: string;
cost: string;
difficulty: string;
}
export const recommendDetectionMethod = (params: any) =>
get<DetectionRecommendation[]>('/detection-methods/recommend', { params });
+86 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import {
Button, Checkbox, Form, Input, Modal, Popconfirm, Radio, Select, Switch, Tabs, Tag, Upload, message,
Button, Card, Checkbox, Form, Input, Modal, Popconfirm, Radio, Select, Space, Switch, Tabs, Tag, Upload, message,
} from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd';
@@ -20,6 +20,8 @@ import {
createSpectrumEntry,
uploadSpectrumEntryFile,
deleteSpectrumEntry,
recommendDetectionMethod,
type DetectionRecommendation,
type LampTest,
type LampTestStep,
type SpectrumEntry,
@@ -66,6 +68,11 @@ function LampTab() {
const [steps, setSteps] = useState<LampTestStep[]>([]);
const [ctValues, setCtValues] = useState('');
const [threshold, setThreshold] = useState<number>(35);
const [recDevices, setRecDevices] = useState({ hasLamp: true, hasSers: false, hasQpcr: false, hasHyperspectral: false });
const [recUrgency, setRecUrgency] = useState('routine');
const [recOperator, setRecOperator] = useState('expert');
const [recCost, setRecCost] = useState('balanced');
const [recommendations, setRecommendations] = useState<DetectionRecommendation[]>([]);
useEffect(() => {
Promise.all([
@@ -200,6 +207,84 @@ function LampTab() {
<Form.Item label="蚕房" name="roomId">
<Select allowClear options={rooms.map((r) => ({ value: r.id, label: r.name }))} />
</Form.Item>
<Card size="small" title="推荐检测方式" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: '100%' }}>
<Space wrap>
<Checkbox
checked={recDevices.hasLamp}
onChange={(e) => setRecDevices((p) => ({ ...p, hasLamp: e.target.checked }))}
>
LAMP
</Checkbox>
<Checkbox
checked={recDevices.hasSers}
onChange={(e) => setRecDevices((p) => ({ ...p, hasSers: e.target.checked }))}
>
SERS
</Checkbox>
<Checkbox
checked={recDevices.hasQpcr}
onChange={(e) => setRecDevices((p) => ({ ...p, hasQpcr: e.target.checked }))}
>
qPCR
</Checkbox>
<Checkbox
checked={recDevices.hasHyperspectral}
onChange={(e) => setRecDevices((p) => ({ ...p, hasHyperspectral: e.target.checked }))}
>
</Checkbox>
</Space>
<Space wrap>
<Select
value={recUrgency}
style={{ width: 110 }}
onChange={setRecUrgency}
options={[
{ value: 'routine', label: '常规' },
{ value: 'urgent', label: '紧急' },
]}
/>
<Select
value={recOperator}
style={{ width: 110 }}
onChange={setRecOperator}
options={[
{ value: 'expert', label: '熟练' },
{ value: 'novice', label: '新手' },
]}
/>
<Select
value={recCost}
style={{ width: 130 }}
onChange={setRecCost}
options={[
{ value: 'balanced', label: '平衡' },
{ value: 'low_cost', label: '最低成本' },
{ value: 'fastest', label: '最快' },
]}
/>
<Button
onClick={async () => {
const res = await recommendDetectionMethod({ ...recDevices, urgency: recUrgency, operatorLevel: recOperator, costPreference: recCost });
setRecommendations(res);
}}
>
</Button>
</Space>
{recommendations.map((r) => (
<div key={r.method}>
<Tag color="blue">{r.name}</Tag>
<span style={{ marginRight: 8 }}>{r.time} · {r.difficulty}</span>
<div style={{ color: '#666', fontSize: 12 }}>{r.reason}</div>
</div>
))}
{recommendations.length === 0 ? (
<span style={{ color: '#999' }}></span>
) : null}
</Space>
</Card>
<Form.Item label="检测方式" name="method" initialValue="lamp">
<Select options={Object.entries(METHOD_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>