diff --git a/miniapp/src/api/lamp.ts b/miniapp/src/api/lamp.ts index 4a68621..1ecd9b1 100644 --- a/miniapp/src/api/lamp.ts +++ b/miniapp/src/api/lamp.ts @@ -4,6 +4,7 @@ import { getApiBaseUrl } from './config'; export interface LampTest { id: string; + method?: string; roomId?: string; diseases?: string[]; status: string; diff --git a/miniapp/src/pages/lamp/index.tsx b/miniapp/src/pages/lamp/index.tsx index 5162fdc..ae1262f 100644 --- a/miniapp/src/pages/lamp/index.tsx +++ b/miniapp/src/pages/lamp/index.tsx @@ -25,6 +25,13 @@ const RESULT_LABELS: Record = { invalid: '无效', }; +const METHOD_LABELS: Record = { + lamp: 'LAMP', + qpcr: 'qPCR', + sers: 'SERS', + hyperspectral: '高光谱', +}; + const LampPage: React.FC = () => { const [tests, setTests] = useState([]); const [stepsMap, setStepsMap] = useState>({}); @@ -120,7 +127,7 @@ const LampPage: React.FC = () => { {(t.diseases || []).join('、') || 'LAMP 检测'} - {STATUS_LABELS[t.status] || t.status} + {METHOD_LABELS[t.method || 'lamp']} · {STATUS_LABELS[t.status] || t.status} {t.sampleInfo ? ` · ${t.sampleInfo}` : ''} · {formatRelativeTime(t.createdAt)} diff --git a/web/src/dal/lamp.ts b/web/src/dal/lamp.ts index d2f84ab..c999240 100644 --- a/web/src/dal/lamp.ts +++ b/web/src/dal/lamp.ts @@ -2,6 +2,7 @@ import { get, post, patch, del } from '../api/http'; export interface LampTest { id: string; + method?: string; roomId?: string; batchId?: string; diseases?: string[]; @@ -13,6 +14,7 @@ export interface LampTest { resultedAt?: string; crossStatus?: string; crossReason?: string; + extraData?: any; note?: string; createdAt?: string; } @@ -40,3 +42,32 @@ export const uploadLampResultImage = (id: string, file: File) => { fd.append('file', file); return post<{ url: string }>(`/lamp-tests/${id}/result-image`, fd); }; + +export const judgeQPCR = (id: string, ctValues: number[], threshold?: number) => + post(`/lamp-tests/${id}/judge-qpcr`, { ctValues, threshold }); + +export const uploadLampSpectrum = (id: string, file: File) => { + const fd = new FormData(); + fd.append('file', file); + return post<{ url: string }>(`/lamp-tests/${id}/spectrum`, fd); +}; + +export interface SpectrumEntry { + id: string; + disease: string; + source?: string; + dataUrl?: string; + note?: string; + createdAt?: string; +} + +export const listSpectrumEntries = (params?: any) => + get('/spectrum-entries', { params }); +export const createSpectrumEntry = (data: Partial) => + post('/spectrum-entries', data); +export const uploadSpectrumEntryFile = (file: File) => { + const fd = new FormData(); + fd.append('file', file); + return post<{ url: string }>('/spectrum-entries/upload', fd); +}; +export const deleteSpectrumEntry = (id: string) => del(`/spectrum-entries/${id}`); diff --git a/web/src/pages/LampTests.tsx b/web/src/pages/LampTests.tsx index 0cc02bd..4c24d4b 100644 --- a/web/src/pages/LampTests.tsx +++ b/web/src/pages/LampTests.tsx @@ -14,8 +14,15 @@ import { listLampSteps, updateLampStep, uploadLampResultImage, + judgeQPCR, + uploadLampSpectrum, + listSpectrumEntries, + createSpectrumEntry, + uploadSpectrumEntryFile, + deleteSpectrumEntry, type LampTest, type LampTestStep, + type SpectrumEntry, } from '../dal/lamp'; import { listDiseases } from '../dal/knowledge'; import { listHouses, type SilkwormHouse } from '../dal/silkworm'; @@ -34,6 +41,13 @@ const RESULT_LABELS: Record = { invalid: '无效', }; +const METHOD_LABELS: Record = { + lamp: 'LAMP', + qpcr: 'qPCR', + sers: 'SERS', + hyperspectral: '高光谱', +}; + const canWrite = () => authService.hasPermission('lamp:write'); function LampTab() { @@ -50,6 +64,8 @@ function LampTab() { const [stepsOpen, setStepsOpen] = useState(false); const [stepsTarget, setStepsTarget] = useState(null); const [steps, setSteps] = useState([]); + const [ctValues, setCtValues] = useState(''); + const [threshold, setThreshold] = useState(35); useEffect(() => { Promise.all([ @@ -83,6 +99,7 @@ function LampTab() { const columns: ProColumns[] = [ { title: '序号', valueType: 'indexBorder', search: false, width: 60 }, + { title: '方式', dataIndex: 'method', search: false, width: 90, render: (_, r) => {METHOD_LABELS[r.method || 'lamp']} }, { title: '蚕房', dataIndex: 'roomId', valueType: 'select', valueEnum: roomEnum, render: (_, r) => roomName(r.roomId) }, { title: '检测病种', dataIndex: 'diseases', search: false, render: (_, r) => (r.diseases || []).join('、') || '-' }, { title: '采样信息', dataIndex: 'sampleInfo', search: false, render: (_, r) => r.sampleInfo || '-' }, @@ -183,6 +200,9 @@ function LampTab() { ({ value, label }))} /> + setCtValues(e.target.value)} + style={{ flex: 1 }} + /> + setThreshold(Number(e.target.value))} + style={{ width: 90 }} + /> + + + ) : ( + + )} + {resultTarget?.method === 'sers' ? ( + + { + if (!resultTarget) return; + try { + const res = await uploadLampSpectrum(resultTarget.id, file as File); + message.success('光谱已上传'); + onSuccess?.(res); + } catch (e) { + onError?.(e as Error); + } + }} + > + + + + ) : null} {resultTarget?.crossStatus && resultTarget.crossStatus !== 'pending' ? (
@@ -296,11 +369,120 @@ function LampTab() { ); } +function SpectrumTab() { + const actionRef = useRef(); + const [modalOpen, setModalOpen] = useState(false); + const [form] = Form.useForm>(); + const [uploadedUrl, setUploadedUrl] = useState(''); + + const columns: ProColumns[] = [ + { title: '序号', valueType: 'indexBorder', search: false, width: 60 }, + { title: '病种', dataIndex: 'disease' }, + { title: '来源', dataIndex: 'source', search: false, render: (_, r) => r.source || '-' }, + { title: '数据文件', dataIndex: 'dataUrl', search: false, render: (_, r) => (r.dataUrl ? 查看 : '-') }, + { title: '备注', dataIndex: 'note', search: false, render: (_, r) => r.note || '-' }, + { + title: '操作', + valueType: 'option', + render: (_, r) => [ + { + await deleteSpectrumEntry(r.id); + message.success('已删除'); + actionRef.current?.reload(); + }} + > + 删除 + , + ], + }, + ]; + + return ( + <> + + actionRef={actionRef} + rowKey="id" + columns={columns} + search={false} + request={async () => { + const res = await listSpectrumEntries(); + return { data: res, total: res.length, success: true }; + }} + toolBarRender={() => [ + , + ]} + /> + setModalOpen(false)} + onOk={async () => { + const v = await form.validateFields(); + await createSpectrumEntry({ ...v, dataUrl: uploadedUrl || v.dataUrl }); + message.success('已保存'); + setModalOpen(false); + actionRef.current?.reload(); + }} + > +
+ + + + + + + + { + try { + const res = await uploadSpectrumEntryFile(file as File); + setUploadedUrl(res.url); + message.success('文件已上传'); + onSuccess?.(res); + } catch (e) { + onError?.(e as Error); + } + }} + > + + + + + + + + + +
+
+ + ); +} + export default function LampTestsPage() { return ( }]} + items={[ + { key: 'lamp', label: '分子检测', children: }, + { key: 'spectrum', label: '光谱库', children: }, + ]} /> ); }