feat(web): 患者快捷导航 + 列表页 URL patient_id 筛选 + AI 列表患者 Link
- 患者详情页增加快捷导航卡片(预约/咨询/透析/随访/AI) - 5 个列表页支持 URL ?patient_id=xxx 自动筛选 - AI 分析列表患者 ID 改为可点击 Link 跳转详情
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
|
import { useSearchParams, Link } from 'react-router-dom';
|
||||||
import { Table, Select, Tag, Space, Button, message, Typography } from 'antd';
|
import { Table, Select, Tag, Space, Button, message, Typography } from 'antd';
|
||||||
import {
|
import {
|
||||||
RobotOutlined,
|
RobotOutlined,
|
||||||
@@ -247,12 +248,15 @@ function SuggestionPanel({ analysisId, isDark }: { analysisId: string; isDark: b
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export default function AiAnalysisList() {
|
export default function AiAnalysisList() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const urlPatientId = searchParams.get('patient_id');
|
||||||
const [data, setData] = useState<AnalysisItem[]>([]);
|
const [data, setData] = useState<AnalysisItem[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [query, setQuery] = useState<{ page: number; page_size: number; analysis_type?: string }>({
|
const [query, setQuery] = useState<{ page: number; page_size: number; analysis_type?: string; patient_id?: string }>({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
|
patient_id: urlPatientId || undefined,
|
||||||
});
|
});
|
||||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||||
const [detail, setDetail] = useState<AnalysisItem | null>(null);
|
const [detail, setDetail] = useState<AnalysisItem | null>(null);
|
||||||
@@ -313,12 +317,14 @@ export default function AiAnalysisList() {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '患者 ID',
|
title: '患者',
|
||||||
dataIndex: 'patient_id',
|
dataIndex: 'patient_id',
|
||||||
key: 'patient_id',
|
key: 'patient_id',
|
||||||
width: 120,
|
width: 140,
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<span style={{ fontFamily: 'monospace', fontSize: 12 }}>{v.slice(0, 8)}</span>
|
<Link to={`/health/patients/${v}`} style={{ fontFamily: 'monospace', fontSize: 12 }}>
|
||||||
|
{v.slice(0, 8)}
|
||||||
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
@@ -83,6 +84,8 @@ interface AppointmentFilters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AppointmentList() {
|
export default function AppointmentList() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const urlPatientId = searchParams.get('patient_id');
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ export default function AppointmentList() {
|
|||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
status: filters.status || undefined,
|
status: filters.status || undefined,
|
||||||
date: dateStart === dateEnd ? dateStart : undefined,
|
date: dateStart === dateEnd ? dateStart : undefined,
|
||||||
patient_id: undefined, // 后端暂不支持 patientSearch 文本搜索
|
patient_id: urlPatientId || undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { PlusOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
import { PlusOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
||||||
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
|
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { consultationApi, type Session, type CreateSessionReq } from '../../api/health/consultations';
|
import { consultationApi, type Session, type CreateSessionReq } from '../../api/health/consultations';
|
||||||
import { StatusTag } from './components/StatusTag';
|
import { StatusTag } from './components/StatusTag';
|
||||||
import { PatientSelect } from './components/PatientSelect';
|
import { PatientSelect } from './components/PatientSelect';
|
||||||
@@ -49,6 +49,8 @@ interface ConsultationFilters {
|
|||||||
|
|
||||||
export default function ConsultationList() {
|
export default function ConsultationList() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const urlPatientId = searchParams.get('patient_id');
|
||||||
|
|
||||||
// Close session
|
// Close session
|
||||||
const [closingId, setClosingId] = useState<string | null>(null);
|
const [closingId, setClosingId] = useState<string | null>(null);
|
||||||
@@ -63,13 +65,14 @@ export default function ConsultationList() {
|
|||||||
async (page: number, pageSize: number, filters: ConsultationFilters) => {
|
async (page: number, pageSize: number, filters: ConsultationFilters) => {
|
||||||
const params: Record<string, unknown> = { page, page_size: pageSize };
|
const params: Record<string, unknown> = { page, page_size: pageSize };
|
||||||
if (filters.status) params.status = filters.status;
|
if (filters.status) params.status = filters.status;
|
||||||
|
if (urlPatientId) params.patient_id = urlPatientId;
|
||||||
if (filters.dateRange) {
|
if (filters.dateRange) {
|
||||||
params.created_start = filters.dateRange[0];
|
params.created_start = filters.dateRange[0];
|
||||||
params.created_end = filters.dateRange[1];
|
params.created_end = filters.dateRange[1];
|
||||||
}
|
}
|
||||||
return consultationApi.listSessions(params as Parameters<typeof consultationApi.listSessions>[0]);
|
return consultationApi.listSessions(params as Parameters<typeof consultationApi.listSessions>[0]);
|
||||||
},
|
},
|
||||||
[],
|
[urlPatientId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { Table, Tag, Button, Modal, Form, InputNumber, DatePicker, Select, message, Popconfirm, Space, Input } from 'antd';
|
import { Table, Tag, Button, Modal, Form, InputNumber, DatePicker, Select, message, Popconfirm, Space, Input } from 'antd';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, AuditOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, AuditOutlined } from '@ant-design/icons';
|
||||||
import { dayjs } from '../../utils/dayjs';
|
import { dayjs } from '../../utils/dayjs';
|
||||||
import type { Dayjs } from 'dayjs';
|
import type { Dayjs } from 'dayjs';
|
||||||
@@ -27,7 +28,9 @@ interface PatientOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DialysisManageList() {
|
export default function DialysisManageList() {
|
||||||
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(null);
|
const [searchParams] = useSearchParams();
|
||||||
|
const urlPatientId = searchParams.get('patient_id');
|
||||||
|
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(urlPatientId);
|
||||||
const [patientOptions, setPatientOptions] = useState<PatientOption[]>([]);
|
const [patientOptions, setPatientOptions] = useState<PatientOption[]>([]);
|
||||||
const [patientSearch, setPatientSearch] = useState('');
|
const [patientSearch, setPatientSearch] = useState('');
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
@@ -38,6 +41,15 @@ export default function DialysisManageList() {
|
|||||||
const [reviewRecord, setReviewRecord] = useState<DialysisRecord | null>(null);
|
const [reviewRecord, setReviewRecord] = useState<DialysisRecord | null>(null);
|
||||||
const [reviewSubmitting, setReviewSubmitting] = useState(false);
|
const [reviewSubmitting, setReviewSubmitting] = useState(false);
|
||||||
|
|
||||||
|
// URL 带了 patient_id 时预加载患者选项
|
||||||
|
useEffect(() => {
|
||||||
|
if (urlPatientId) {
|
||||||
|
patientApi.get(urlPatientId).then((p) => {
|
||||||
|
if (p) setPatientOptions([{ id: p.id, name: p.name }]);
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}, [urlPatientId]);
|
||||||
|
|
||||||
const searchPatients = async (keyword: string) => {
|
const searchPatients = async (keyword: string) => {
|
||||||
if (!keyword.trim()) {
|
if (!keyword.trim()) {
|
||||||
setPatientOptions([]);
|
setPatientOptions([]);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Select,
|
Select,
|
||||||
@@ -61,6 +62,9 @@ interface AssignFormValues {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function FollowUpTaskList() {
|
export default function FollowUpTaskList() {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const urlPatientId = searchParams.get('patient_id');
|
||||||
|
|
||||||
// --- Paginated data with usePaginatedData ---
|
// --- Paginated data with usePaginatedData ---
|
||||||
const fetchFn = useCallback(
|
const fetchFn = useCallback(
|
||||||
async (page: number, pageSize: number, filters: FollowUpFilters) => {
|
async (page: number, pageSize: number, filters: FollowUpFilters) => {
|
||||||
@@ -68,13 +72,14 @@ export default function FollowUpTaskList() {
|
|||||||
if (filters.status) params.status = filters.status;
|
if (filters.status) params.status = filters.status;
|
||||||
if (filters.followUpType) params.follow_up_type = filters.followUpType;
|
if (filters.followUpType) params.follow_up_type = filters.followUpType;
|
||||||
if (filters.assigneeId) params.assigned_to = filters.assigneeId;
|
if (filters.assigneeId) params.assigned_to = filters.assigneeId;
|
||||||
|
if (urlPatientId) params.patient_id = urlPatientId;
|
||||||
if (filters.dateRange) {
|
if (filters.dateRange) {
|
||||||
params.planned_date_start = filters.dateRange[0];
|
params.planned_date_start = filters.dateRange[0];
|
||||||
params.planned_date_end = filters.dateRange[1];
|
params.planned_date_end = filters.dateRange[1];
|
||||||
}
|
}
|
||||||
return followUpApi.listTasks(params as Parameters<typeof followUpApi.listTasks>[0]);
|
return followUpApi.listTasks(params as Parameters<typeof followUpApi.listTasks>[0]);
|
||||||
},
|
},
|
||||||
[],
|
[urlPatientId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
Reference in New Issue
Block a user