diff --git a/PACS_DICOM处理/数据处理网页端/README.md b/PACS_DICOM处理/数据处理网页端/README.md index f1801e5..9688f06 100644 --- a/PACS_DICOM处理/数据处理网页端/README.md +++ b/PACS_DICOM处理/数据处理网页端/README.md @@ -36,8 +36,8 @@ uvicorn app:app --host 127.0.0.1 --port 8107 ## 功能 -- 左侧检查列表:来自 PostgreSQL `pacs_dicom_files`。 -- 中间序列列表:从已处理 DICOM 目录扫描 DICOM 头信息。 +- 左侧检查列表:来自 PostgreSQL `pacs_dicom_files`,支持按检查时间或未标注序列数升降序排序。 +- 中间序列列表:从已处理 DICOM 目录扫描 DICOM 头信息,支持按时间或张数升降序排序。 - 右侧查看器:支持轴位原始、矢状位重建、冠状位重建,窗宽窗位、旋转、切片进度条。 - 影像操作:支持鼠标滚轮缩放、拖拽平移、按钮缩放和复位。 - 图像叠层:可显示/隐藏患者、检查、张数、窗宽窗位和缩放信息。 diff --git a/PACS_DICOM处理/数据处理网页端/static/app.js b/PACS_DICOM处理/数据处理网页端/static/app.js index 3b42bdb..8ce8131 100644 --- a/PACS_DICOM处理/数据处理网页端/static/app.js +++ b/PACS_DICOM处理/数据处理网页端/static/app.js @@ -20,7 +20,10 @@ const app = { saving: false, pendingImage: null, drag: null, - seriesSort: "timeAsc", + studySortField: "time", + studySortDir: "desc", + seriesSortField: "time", + seriesSortDir: "asc", showOverlay: true, thumbUrls: new Map(), thumbObserver: null, @@ -99,6 +102,16 @@ function timeKey(series) { return String(series.series_time || series.first_time || series.study_time || "").replace(/\D/g, "").padEnd(6, "0"); } +function studyTimeKey(study) { + const date = String(study.study_date || "").replace(/\D/g, "").padEnd(8, "0"); + const time = String(study.study_time || "").replace(/\D/g, "").slice(0, 6).padEnd(6, "0"); + return `${date}${time}`; +} + +function sortArrow(direction) { + return direction === "asc" ? "↑" : "↓"; +} + function phaseLabel(value) { return { arterial: "动脉期", portal_venous: "门静脉期", delayed: "延迟期", unknown: "无法判别" }[value] || ""; } @@ -185,10 +198,34 @@ function sortSeries(list) { if (skipA !== skipB) return skipA - skipB; const timeResult = timeKey(a).localeCompare(timeKey(b)) || String(a.series_number || "").localeCompare(String(b.series_number || ""), undefined, { numeric: true }); const countResult = Number(a.count || 0) - Number(b.count || 0) || timeResult; - if (app.seriesSort === "timeDesc") return -timeResult; - if (app.seriesSort === "countAsc") return countResult; - if (app.seriesSort === "countDesc") return -countResult; - return timeResult; + const result = app.seriesSortField === "count" ? countResult : timeResult; + return app.seriesSortDir === "desc" ? -result : result; + }); +} + +function sortStudies(list) { + return [...list].sort((a, b) => { + const timeResult = + studyTimeKey(a).localeCompare(studyTimeKey(b)) || + String(a.ct_number || "").localeCompare(String(b.ct_number || ""), undefined, { numeric: true }); + const unannotatedResult = Number(a.unannotated_series || 0) - Number(b.unannotated_series || 0) || timeResult; + const result = app.studySortField === "unannotated" ? unannotatedResult : timeResult; + return app.studySortDir === "desc" ? -result : result; + }); +} + +function updateSortButtons() { + document.querySelectorAll("[data-study-sort]").forEach((button) => { + const active = button.dataset.studySort === app.studySortField; + button.classList.toggle("active", active); + const arrow = button.querySelector(".sort-arrow"); + if (arrow) arrow.textContent = sortArrow(active ? app.studySortDir : "desc"); + }); + document.querySelectorAll("[data-series-sort]").forEach((button) => { + const active = button.dataset.seriesSort === app.seriesSortField; + button.classList.toggle("active", active); + const arrow = button.querySelector(".sort-arrow"); + if (arrow) arrow.textContent = sortArrow(active ? app.seriesSortDir : "asc"); }); } @@ -196,6 +233,7 @@ function setSeries(list) { app.series = sortSeries(list || []); $("seriesCount").textContent = String(app.series.length); renderSeries(); + updateSortButtons(); } function isSeriesAnnotated(series) { @@ -219,7 +257,7 @@ function refreshStudyAnnotationSummary() { async function loadStudies() { const q = encodeURIComponent($("studySearch").value.trim()); - app.studies = await json(`/api/studies?q=${q}&limit=500`); + app.studies = sortStudies(await json(`/api/studies?q=${q}&limit=500`)); $("studyCount").textContent = String(app.studies.length); renderStudies(); if (!app.study && app.studies.length) selectStudy(app.studies[0].ct_number); @@ -227,6 +265,8 @@ async function loadStudies() { function renderStudies() { const list = $("studyList"); + app.studies = sortStudies(app.studies); + updateSortButtons(); list.innerHTML = ""; for (const study of app.studies) { const button = document.createElement("button"); @@ -901,16 +941,29 @@ function wireImageGestures() { }); } -function setSort(direction) { - app.seriesSort = direction; - document.querySelectorAll("[data-sort]").forEach((button) => { - button.classList.toggle("active", button.dataset.sort === direction); - }); +function setStudySort(field) { + if (app.studySortField === field) { + app.studySortDir = app.studySortDir === "asc" ? "desc" : "asc"; + } else { + app.studySortField = field; + app.studySortDir = "desc"; + } + renderStudies(); +} + +function setSeriesSort(field) { + if (app.seriesSortField === field) { + app.seriesSortDir = app.seriesSortDir === "asc" ? "desc" : "asc"; + } else { + app.seriesSortField = field; + app.seriesSortDir = field === "count" ? "desc" : "asc"; + } app.series = sortSeries(app.series); renderSeries(); if (app.activeSeries) { app.activeSeries = app.series.find((item) => item.series_uid === app.activeSeries.series_uid) || app.activeSeries; } + updateSortButtons(); } function wire() { @@ -925,8 +978,11 @@ function wire() { app.showOverlay = !app.showOverlay; updateOverlay(); }); - document.querySelectorAll("[data-sort]").forEach((button) => { - button.addEventListener("click", () => setSort(button.dataset.sort)); + document.querySelectorAll("[data-study-sort]").forEach((button) => { + button.addEventListener("click", () => setStudySort(button.dataset.studySort)); + }); + document.querySelectorAll("[data-series-sort]").forEach((button) => { + button.addEventListener("click", () => setSeriesSort(button.dataset.seriesSort)); }); $("studySearch").addEventListener("input", () => { clearTimeout(app.searchTimer); diff --git a/PACS_DICOM处理/数据处理网页端/static/index.html b/PACS_DICOM处理/数据处理网页端/static/index.html index 4266c1e..34a3789 100644 --- a/PACS_DICOM处理/数据处理网页端/static/index.html +++ b/PACS_DICOM处理/数据处理网页端/static/index.html @@ -44,7 +44,11 @@