add DICOM metadata details modal
This commit is contained in:
@@ -30,7 +30,9 @@ import {
|
||||
FolderOpen,
|
||||
Server,
|
||||
AlertCircle,
|
||||
Eye
|
||||
Eye,
|
||||
Info,
|
||||
X
|
||||
} from 'lucide-react';
|
||||
|
||||
// --- Types ---
|
||||
@@ -66,6 +68,16 @@ type LibraryItem = {
|
||||
source?: 'seed' | 'upload';
|
||||
};
|
||||
|
||||
type LibraryInfo = {
|
||||
id: string;
|
||||
patientId: string;
|
||||
fileCount: number;
|
||||
groups: {
|
||||
title: string;
|
||||
items: { label: string; value: string }[];
|
||||
}[];
|
||||
};
|
||||
|
||||
const API_BASE = typeof window === 'undefined'
|
||||
? 'http://127.0.0.1:8787'
|
||||
: `${window.location.protocol}//${window.location.hostname}:8787`;
|
||||
@@ -183,6 +195,8 @@ export default function App() {
|
||||
const [libraryData, setLibraryData] = useState<LibraryItem[]>([]);
|
||||
const [selectedLibraryId, setSelectedLibraryId] = useState('');
|
||||
const [isUploadingDicom, setIsUploadingDicom] = useState(false);
|
||||
const [libraryInfo, setLibraryInfo] = useState<LibraryInfo | null>(null);
|
||||
const [isLibraryInfoLoading, setIsLibraryInfoLoading] = useState(false);
|
||||
const folderUploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const zipUploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -416,6 +430,19 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const showLibraryInfo = async (item: LibraryItem) => {
|
||||
setIsLibraryInfoLoading(true);
|
||||
setLibraryInfo(null);
|
||||
try {
|
||||
const data = await apiRequest(`/api/library/info?id=${encodeURIComponent(item.id)}`) as LibraryInfo;
|
||||
setLibraryInfo(data);
|
||||
} catch (error) {
|
||||
showToast((error as Error).message);
|
||||
} finally {
|
||||
setIsLibraryInfoLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = (userId: string, newPass: string) => {
|
||||
setUsers(users.map(u => u.id === userId ? { ...u, password: newPass } : u));
|
||||
setPwChangeInput('');
|
||||
@@ -904,7 +931,7 @@ export default function App() {
|
||||
<span>{item.fileCount || 0} 张</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 mt-2">
|
||||
<div className="grid grid-cols-3 gap-3 mt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (item.status === 'processed') {
|
||||
@@ -919,13 +946,19 @@ export default function App() {
|
||||
}}
|
||||
className={`py-2.5 text-[11px] font-black rounded-xl transition-all ${item.status === 'processed' ? 'bg-blue-600 text-white hover:bg-black shadow-lg shadow-blue-500/20' : 'bg-slate-100 text-slate-300 cursor-not-allowed'}`}
|
||||
>
|
||||
调阅工作站
|
||||
调阅
|
||||
</button>
|
||||
<button
|
||||
onClick={() => showLibraryInfo(item)}
|
||||
className="py-2.5 bg-slate-900 text-white text-[11px] font-black rounded-xl hover:bg-blue-600 transition-all flex items-center justify-center gap-1"
|
||||
>
|
||||
<Info size={12} /> 信息
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteImage(item.id)}
|
||||
className="py-2.5 bg-slate-50 text-slate-400 text-[11px] font-black rounded-xl hover:bg-red-50 hover:text-red-500 transition-all border border-slate-100"
|
||||
>
|
||||
删除影像
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1093,6 +1126,54 @@ export default function App() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{(libraryInfo || isLibraryInfoLoading) && (
|
||||
<div className="fixed inset-0 bg-slate-950/45 backdrop-blur-sm z-40 flex items-center justify-center p-6">
|
||||
<div className="w-full max-w-4xl max-h-[85vh] bg-white rounded-2xl shadow-2xl border border-slate-200 overflow-hidden">
|
||||
<div className="px-7 py-5 border-b flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">DICOM 基本信息</p>
|
||||
<h3 className="text-xl font-black text-slate-800 mt-1">
|
||||
{libraryInfo?.patientId || '正在读取影像信息'}
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setLibraryInfo(null);
|
||||
setIsLibraryInfoLoading(false);
|
||||
}}
|
||||
className="w-10 h-10 rounded-xl bg-slate-50 text-slate-400 hover:bg-red-50 hover:text-red-500 flex items-center justify-center transition-all"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isLibraryInfoLoading ? (
|
||||
<div className="h-72 flex items-center justify-center text-slate-400 text-sm font-bold">
|
||||
正在读取 DICOM 头信息...
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-7 overflow-y-auto max-h-[68vh]">
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
{libraryInfo?.groups.map(group => (
|
||||
<div key={group.title} className="border border-slate-100 rounded-2xl p-5 bg-slate-50/50">
|
||||
<h4 className="text-xs font-black text-slate-700 mb-4">{group.title}</h4>
|
||||
<div className="space-y-3">
|
||||
{group.items.map(item => (
|
||||
<div key={`${group.title}-${item.label}`} className="flex items-start justify-between gap-4 text-xs">
|
||||
<span className="text-slate-400 font-bold shrink-0">{item.label}</span>
|
||||
<span className="text-slate-700 font-mono text-right break-all">{item.value || '-'}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Toast */}
|
||||
{toastMessage && (
|
||||
<div className="fixed bottom-10 right-10 bg-slate-900 text-white px-6 py-4 rounded-2xl shadow-2xl z-50 flex items-center gap-3 animate-in fade-in slide-in-from-right-10">
|
||||
|
||||
Reference in New Issue
Block a user