feat: localmpr viewer
This commit is contained in:
parent
3077c78c76
commit
6df7442b9b
|
@ -9,3 +9,8 @@
|
|||
|
||||
|
||||
pnpm config set virtual-store-dir-max-length 70
|
||||
|
||||
|
||||
## 待解决
|
||||
|
||||
- dicom导入后,本地appData建立一个拷贝区域,复制dicom原片,满足二次导出,防止原片的路径修改、移动硬盘被拔电源
|
|
@ -72,6 +72,10 @@ function createWindow() {
|
|||
python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
|
||||
}
|
||||
} else {
|
||||
if (platform !== "macos") {
|
||||
python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
|
||||
}
|
||||
|
||||
win.loadFile(path.join(RENDERER_DIST, "index.html")).then(() => {
|
||||
if (process.argv.length >= 2) {
|
||||
const folderPath = process.argv[2];
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useEffect } from "react";
|
|||
import { Models } from "./pages/Models";
|
||||
import { Tools } from "./pages/Tools";
|
||||
import { Datasource } from "./pages/Datasource";
|
||||
import { Viewer } from "./pages/Viewer";
|
||||
|
||||
function App() {
|
||||
const theme = document.querySelector("html")!.getAttribute("theme") as
|
||||
|
@ -33,6 +34,7 @@ function App() {
|
|||
<Route path="datasource" element={<Datasource />} />
|
||||
<Route path="tools" element={<Tools />} />
|
||||
<Route path="setting" element={<Setting />} />
|
||||
<Route path="viewer" element={<Viewer />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
|
|
|
@ -90,7 +90,7 @@ export const MenuBar = () => {
|
|||
toast({
|
||||
variant: "default",
|
||||
title: "完成",
|
||||
description: `本次操作共导入${structDicom.length}组序列数据,耗时:${(
|
||||
description: `导入${structDicom.length}组序列数据,耗时:${(
|
||||
scanDuration / 1000
|
||||
).toFixed(2)} s`,
|
||||
action: (
|
||||
|
|
|
@ -71,185 +71,6 @@ const columnsAlias: { [K in keyof Partial<Series> as string]: string } = {
|
|||
AcquisitionDate: "采集日期",
|
||||
};
|
||||
|
||||
export const columns: ColumnDef<Series>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
id: "PatientName",
|
||||
accessorKey: "PatientName",
|
||||
header: columnsAlias["PatientName"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientName")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "PatientAge",
|
||||
accessorKey: "PatientAge",
|
||||
header: columnsAlias["PatientAge"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientAge")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "PatientSex",
|
||||
accessorKey: "PatientSex",
|
||||
header: columnsAlias["PatientSex"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientSex")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "SeriesInstanceUID",
|
||||
accessorKey: "SeriesInstanceUID",
|
||||
header: columnsAlias["SeriesInstanceUID"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("SeriesInstanceUID")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "filePaths",
|
||||
accessorKey: "filePaths",
|
||||
header: columnsAlias["filePaths"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">
|
||||
{(row.getValue("filePaths") as string[]).length}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "AcquisitionDate",
|
||||
accessorKey: "AcquisitionDate",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["AcquisitionDate"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("AcquisitionDate") && (
|
||||
<div className="capitalize">
|
||||
<span>{row.getValue("AcquisitionDate")}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
id: "createTime",
|
||||
accessorKey: "createTime",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["createTime"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("createTime") && (
|
||||
<div className="capitalize">
|
||||
<span>{dayjs(row.getValue("createTime")).format("YYYYMMDD")}</span>
|
||||
<Badge className="ml-2 text-xs font-normal">
|
||||
{timeAgo(row.getValue("createTime"))}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "updateTime",
|
||||
accessorKey: "updateTime",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["updateTime"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("updateTime") && (
|
||||
<div className="capitalize">
|
||||
<span>{dayjs(row.getValue("updateTime")).format("YYYYMMDD")}</span>
|
||||
<Badge className="ml-2 text-xs font-normal">
|
||||
{timeAgo(row.getValue("updateTime"))}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "操作",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const payment = row.original;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(payment.id)}
|
||||
>
|
||||
Copy payment ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface SeriesTableProps {
|
||||
data: Series[];
|
||||
}
|
||||
|
@ -265,6 +86,183 @@ export function SeriesTable(props: SeriesTableProps) {
|
|||
const [rowSelection, setRowSelection] = useState({});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const columns: ColumnDef<Series>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
id: "PatientName",
|
||||
accessorKey: "PatientName",
|
||||
header: columnsAlias["PatientName"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientName")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "PatientAge",
|
||||
accessorKey: "PatientAge",
|
||||
header: columnsAlias["PatientAge"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientAge")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "PatientSex",
|
||||
accessorKey: "PatientSex",
|
||||
header: columnsAlias["PatientSex"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("PatientSex")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "SeriesInstanceUID",
|
||||
accessorKey: "SeriesInstanceUID",
|
||||
header: columnsAlias["SeriesInstanceUID"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">{row.getValue("SeriesInstanceUID")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "filePaths",
|
||||
accessorKey: "filePaths",
|
||||
header: columnsAlias["filePaths"],
|
||||
cell: ({ row }) => (
|
||||
<div className="capitalize">
|
||||
{(row.getValue("filePaths") as string[]).length}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "AcquisitionDate",
|
||||
accessorKey: "AcquisitionDate",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["AcquisitionDate"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("AcquisitionDate") && (
|
||||
<div className="capitalize">
|
||||
<span>{row.getValue("AcquisitionDate")}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
id: "createTime",
|
||||
accessorKey: "createTime",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["createTime"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("createTime") && (
|
||||
<div className="capitalize">
|
||||
<span>{dayjs(row.getValue("createTime")).format("YYYYMMDD")}</span>
|
||||
<Badge className="ml-2 text-xs font-normal">
|
||||
{timeAgo(row.getValue("createTime"))}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "updateTime",
|
||||
accessorKey: "updateTime",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{columnsAlias["updateTime"]}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 w-7 ml-2"
|
||||
size="icon"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<ArrowUpDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) =>
|
||||
row.getValue("updateTime") && (
|
||||
<div className="capitalize">
|
||||
<span>{dayjs(row.getValue("updateTime")).format("YYYYMMDD")}</span>
|
||||
<Badge className="ml-2 text-xs font-normal">
|
||||
{timeAgo(row.getValue("updateTime"))}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "操作",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const dicom = row.original;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>操作</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handle2Viewer(dicom)}
|
||||
>阅片</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>移除</DropdownMenuItem>
|
||||
<DropdownMenuItem>重新测量</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: props.data,
|
||||
columns,
|
||||
|
@ -295,6 +293,11 @@ export function SeriesTable(props: SeriesTableProps) {
|
|||
navigate("/", { state: { selectDicoms } });
|
||||
};
|
||||
|
||||
const handle2Viewer = (dicom) => {
|
||||
const { SeriesInstanceUID } = dicom
|
||||
navigate(`/viewer?SeriesInstanceUID=${SeriesInstanceUID}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="flex-shrink-0 flex items-center p-4">
|
||||
|
@ -387,9 +390,9 @@ export function SeriesTable(props: SeriesTableProps) {
|
|||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
|
|
12
apps/desktop/src/pages/Viewer/index.tsx
Normal file
12
apps/desktop/src/pages/Viewer/index.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { useLocation } from "react-router-dom"
|
||||
|
||||
export const Viewer = () => {
|
||||
const location = useLocation()
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const SeriesInstanceUID = queryParams.get('SeriesInstanceUID'); // 获取URL参数
|
||||
|
||||
return <div>
|
||||
{SeriesInstanceUID}
|
||||
<p>找到一个本地dicom的mpr方案</p>
|
||||
</div>
|
||||
}
|
Loading…
Reference in New Issue
Block a user