上传、下载影像
continuous-integration/drone/push Build is passing Details

uat_us
wangxiaoshuang 2024-05-24 16:47:35 +08:00
parent 0baa90d86f
commit 56b94a97fd
3 changed files with 384 additions and 142 deletions

View File

@ -31,3 +31,12 @@ export function addOrUpdateArchiveTaskStudy(data) {
data
})
}
// 删除上传文件
export function deleteTaskStudy(params) {
return request({
url: '/DownloadAndUpload/deleteTaskStudy',
method: 'delete',
params
})
}

View File

@ -7,6 +7,10 @@
:before-close="beforeClose"
>
<span slot="title">{{ $t("trials:uploadImage:title:uploadImages") }}</span>
<el-divider content-position="left">{{
$t("trials:uploadImage:button:uploadTableTitle1")
}}</el-divider>
<!--已上传影像检查-->
<el-table :data="list" style="width: 100%" height="300" :loading="loading">
<!--受试者-->
<el-table-column
@ -23,12 +27,19 @@
prop="OrginalStudyList"
:label="$t('trials:uploadImage:table:orginalStudyListNum')"
>
<template slot-scope="scope">{{
scope.row.OrginalStudyList &&
Array.isArray(scope.row.OrginalStudyList)
? scope.row.OrginalStudyList.length
: 0
}}</template>
<template slot-scope="scope">
<el-button
v-if="
scope.row.OrginalStudyList &&
scope.row.OrginalStudyList.length >= 1
"
type="text"
@click="handleOpenDialog(scope.row, 'study')"
>
<span>{{ scope.row.OrginalStudyList.length }}</span>
</el-button>
<span v-else>0</span>
</template>
</el-table-column>
<!--后处理检查数-->
<el-table-column
@ -64,17 +75,33 @@
</template>
</el-table-column>
</el-table>
<p class="line">{{ $t("trials:uploadImage:button:uploadTableTitle") }}</p>
<el-divider content-position="left">{{
$t("trials:uploadImage:button:uploadTableTitle")
}}</el-divider>
<div
class="top"
style="margin-bottom: 10px"
v-if="Criterion.ImageUploadEnum > 0"
>
<el-button type="primary" size="small" class="el-icon-upload2">
<el-button
type="primary"
size="small"
class="el-icon-upload2"
v-if="!isLoad"
>
<label for="file">
{{ $t("trials:uploadImage:button:selectFolder") }}
</label>
</el-button>
<el-button
type="primary"
size="small"
class="el-icon-upload2"
@click.stop="notUp"
v-else
>
{{ $t("trials:uploadImage:button:selectFolder") }}
</el-button>
<input
type="file"
name="file"
@ -86,21 +113,131 @@
@change="beginScanFiles($event)"
/>
</div>
<!--本地影像检查-->
<el-table
:data="uploadList"
style="width: 100%"
v-adaptive="{ bottomOffset: 40 }"
v-adaptive="{ bottomOffset: 50 }"
>
<!--检查编号-->
<el-table-column
prop="StudyId"
:label="$t('trials:uploadImage:table:StudyId')"
/>
<!--患者姓名-->
<el-table-column
prop="PatientName"
:label="$t('trials:uploadImage:table:PatientName')"
/>
<el-table-column type="index" width="40" />
<!--检查信息-->
<el-table-column min-width="200" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top">
<div slot="content">
{{ $t("trials:uploadImage:table:studyDetail1") }}<br />
{{ $t("trials:uploadImage:table:studyDetail2") }}<br />
{{ $t("trials:uploadedDicoms:table:studyDate") }}
</div>
<span>{{ $t("trials:uploadImage:table:studyInfo") }}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<div>
<span>
<span style="font-weight: 500">Acc:</span>
<span
:class="{
colorOfRed: !scope.row.dicomInfo.AccessionNumber,
}"
>{{ scope.row.dicomInfo.AccessionNumber || "N/A" }}</span
>
</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.Modalities.length > 0">
{{ scope.row.dicomInfo.Modalities.join("、") }},
</span>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.SeriesInstanceUidList.length"
>{{ scope.row.SeriesInstanceUidList.length }} Series,
</span>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.count"
>{{ scope.row.count }} Instances
</span>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.seriesBodyPartExamined">
{{ scope.row.dicomInfo.seriesBodyPartExamined }},
</span>
<span v-else style="color: #f44336">N/A, </span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.dicomInfo.StudyDescription">
{{ scope.row.dicomInfo.StudyDescription }}</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
{{ scope.row.dicomInfo.StudyTime }}
</div>
</div>
</template>
</el-table-column>
<!--患者信息-->
<el-table-column min-width="130" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top">
<div slot="content">
{{ $t("trials:uploadImage:table:pId") }}<br />
{{ $t("trials:uploadImage:table:patientName") }}<br />
{{ $t("trials:uploadImage:table:pInfo") }}
</div>
<span>{{ $t("trials:uploadImage:table:patientInfo") }}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<span v-if="scope.row.dicomInfo.PatientId"
><span style="font-weight: 500">PID: </span
>{{ scope.row.dicomInfo.PatientId }}</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
<div>
<span
:class="[scope.row.dicomInfo.PatientName ? '' : 'colorOfRed']"
>
{{ scope.row.dicomInfo.PatientName || "N/A" }}
</span>
</div>
<div>
<span
:class="[scope.row.dicomInfo.PatientSex ? '' : 'colorOfRed']"
>
{{ scope.row.dicomInfo.PatientSex || "N/A" }},
</span>
<span
:class="[scope.row.dicomInfo.PatientAge ? '' : 'colorOfRed']"
>
{{ scope.row.dicomInfo.PatientAge || "N/A" }},
</span>
<span
:class="[
scope.row.dicomInfo.PatientBirthDate ? '' : 'colorOfRed',
]"
>
{{ scope.row.dicomInfo.PatientBirthDate || "N/A" }}
</span>
</div>
</div>
</template>
</el-table-column>
<!--上传进度-->
<el-table-column
prop="schedule"
@ -123,7 +260,11 @@
>
<template slot-scope="scope">
<el-button
:type="['warning', 'success', 'danger'][scope.row.status]"
:type="
['warning', 'success', 'danger', 'primary', 'success', 'danger'][
scope.row.status
]
"
size="mini"
plain
v-if="scope.row.status || scope.row.status === 0"
@ -160,10 +301,10 @@ import {
getSubjectImageUploadList,
preArchiveDicomStudy,
addOrUpdateArchiveTaskStudy,
deleteTaskStudy,
} from "@/api/load.js";
import { downloadImage } from "@/utils/uploadZip.js";
import { parseDicom, getThumbnail } from "@/utils/parseDicom.js";
import { dcmUpload } from "@/utils/dcmUpload/dcmUpload";
import { parseDicom, getThumbnail, dicomToOSS } from "@/utils/parseDicom.js";
export default {
name: "uploadImage",
props: {
@ -187,6 +328,7 @@ export default {
},
data() {
return {
isLoad: false,
list: [],
StudyInstanceUidList: [],
SopInstanceUidList: [],
@ -203,6 +345,14 @@ export default {
beforeClose() {
this.$emit("update:visible", false);
},
notUp() {
this.$confirm(this.$t("trials:uploadImage:confirmMessage:notUp"), {
type: "warning",
distinguishCancelAndClose: true,
confirmButtonText: this.$t("common:button:confirm"),
cancelButtonText: this.$t("common:button:cancel"),
});
},
//
async getList() {
try {
@ -223,6 +373,10 @@ export default {
data.VisitTaskId = item.VisitTaskId;
data.SourceSubjectVisitId = item.SourceSubjectVisitId;
this.StudyInstanceUidList.push(data);
});
}
if (item.UploadStudyList && Array.isArray(item.UploadStudyList)) {
item.UploadStudyList.forEach((data) => {
data.SopInstanceUidList &&
this.SopInstanceUidList.push(...data.SopInstanceUidList);
});
@ -235,7 +389,32 @@ export default {
}
},
//
remove(item) {},
async remove(item) {
try {
let confirm = await this.$confirm(
this.$t("trials:uploadImage:confirm:delMessage"),
{
type: "warning",
distinguishCancelAndClose: true,
confirmButtonText: this.$t("common:button:confirm"),
cancelButtonText: this.$t("common:button:cancel"),
}
);
if (confirm !== "confirm") return;
let params = {
VisitTaskId: item.VisitTaskId,
};
let res = await deleteTaskStudy(params);
if (res.IsSuccess) {
this.getList();
this.$message.success(
this.$t("trials:uploadImage:message:delSuccess")
);
}
} catch (err) {
console.log(err);
}
},
//
async downloadImage(item) {
try {
@ -248,11 +427,23 @@ export default {
}
},
//
async preArchiveDicomStudy(post) {
async preArchiveDicomStudy(post, index) {
try {
let res = await preArchiveDicomStudy(post);
if (res.IsSuccess) {
return res.Result;
let item = this.uploadList[index];
item.StudyMonitorId = res.Result;
let arr = this.dicomList.filter(
(dicom) => dicom.StudyInstanceUid === item.StudyInstanceUid
);
let num = arr.length > this.requestNum ? this.requestNum : arr.length;
let funArr = [];
for (let i = 0; i < num; i++) {
funArr.push(this.handleUploadTask(arr, i));
}
if (funArr.length > 0) {
let res = await Promise.all(funArr);
}
} else {
return false;
}
@ -275,8 +466,23 @@ export default {
!dicom ||
!has ||
this.SopInstanceUidList.includes(dicom.SopInstanceUid)
)
) {
if (i === files.length - 1 && this.dicomList.length <= 0) {
let confirm = await this.$confirm(
this.$t("trials:uploadImage:confirmMessage:failSubject"),
{
type: "warning",
distinguishCancelAndClose: true,
confirmButtonText: this.$t("common:button:confirm"),
cancelButtonText: this.$t("common:button:cancel"),
}
);
if (confirm !== "confirm") continue;
this.$refs.file.click();
}
continue;
}
this.isLoad = true;
dicom.file = files[i];
dicom.isUpload = 0;
this.StudyInstanceUidList.some((item) => {
@ -287,33 +493,54 @@ export default {
VisitTaskId: item.VisitTaskId,
SourceSubjectVisitId: item.SourceSubjectVisitId,
};
let uploadHas = this.uploadList.some(
(uploadData, uploadIndex) => {
if (uploadData.StudyInstanceUid === dicom.StudyInstanceUid) {
if (uploadData.status > 0) {
uploadData.count = 1;
uploadData.successCount = 0;
uploadData.failCount = 0;
uploadData.schedule = 0;
uploadData.status = 0;
uploadData.FileSize = dicom.file.size;
uploadData.IsDicomReUpload = false;
} else {
uploadData.FileSize += dicom.file.size;
uploadData.count++;
}
if (
item.SopInstanceUidList &&
item.SopInstanceUidList.length > 0
) {
uploadData.IsDicomReUpload = true;
}
dicom.uploadIndex = uploadIndex;
let uploadHas = this.uploadList.some((uploadData) => {
if (uploadData.StudyInstanceUid === dicom.StudyInstanceUid) {
if (uploadData.status > 0) {
uploadData.count = 1;
uploadData.successCount = 0;
uploadData.failCount = 0;
uploadData.schedule = 0;
uploadData.status = 0;
uploadData.FileSize = dicom.file.size;
uploadData.IsDicomReUpload = false;
uploadData.SeriesInstanceUidList = [
dicom.SeriesInstanceUid,
];
uploadData.dicomInfo = {
AccessionNumber: dicom.AccessionNumber,
Modalities: dicom.Modalities,
seriesBodyPartExamined: dicom.seriesBodyPartExamined,
StudyDescription: dicom.StudyDescription,
StudyTime: this.getTime(dicom.StudyDate, dicom.StudyTime),
PatientName: dicom.PatientName,
PatientId: dicom.PatientId,
PatientAge: dicom.PatientAge,
PatientSex: dicom.PatientSex,
PatientBirthDate: dicom.PatientBirthDate,
};
} else {
uploadData.FileSize += dicom.file.size;
uploadData.count++;
}
if (
!uploadData.SeriesInstanceUidList.includes(
dicom.SeriesInstanceUid
)
) {
uploadData.SeriesInstanceUidList.push(
dicom.SeriesInstanceUid
);
}
if (
item.SopInstanceUidList &&
item.SopInstanceUidList.length > 0
) {
uploadData.IsDicomReUpload = true;
}
return uploadData.StudyInstanceUid === dicom.StudyInstanceUid;
}
);
return uploadData.StudyInstanceUid === dicom.StudyInstanceUid;
});
if (!uploadHas) {
let uploadData = {
TrialId: this.$route.query.trialId,
@ -327,7 +554,20 @@ export default {
failCount: 0,
schedule: 0,
status: 0,
SeriesInstanceUidList: [dicom.SeriesInstanceUid],
IsDicomReUpload: false,
dicomInfo: {
AccessionNumber: dicom.AccessionNumber,
Modalities: dicom.Modalities,
seriesBodyPartExamined: dicom.seriesBodyPartExamined,
StudyDescription: dicom.StudyDescription,
StudyTime: this.getTime(dicom.StudyDate, dicom.StudyTime),
PatientName: dicom.PatientName,
PatientId: dicom.PatientId,
PatientAge: dicom.PatientAge,
PatientSex: dicom.PatientSex,
PatientBirthDate: dicom.PatientBirthDate,
},
};
if (
item.SopInstanceUidList &&
@ -336,13 +576,13 @@ export default {
uploadData.IsDicomReUpload = true;
}
this.uploadList.push(uploadData);
dicom.uploadIndex = this.uploadList.length - 1;
}
}
return item.StudyInstanceUid === dicom.StudyInstanceUid;
});
this.dicomList.push(dicom);
}
let funArr = [];
for (let i = 0; i < this.uploadList.length; i++) {
let item = this.uploadList[i];
let data = {
@ -353,20 +593,10 @@ export default {
IsDicomReUpload: item.IsDicomReUpload,
FileCount: item.count,
};
let res = await this.preArchiveDicomStudy(data);
if (res) {
item.StudyMonitorId = res;
let arr = this.dicomList.filter((dicom) => dicom.uploadIndex === i);
let num =
arr.length > this.requestNum ? this.requestNum : arr.length;
let funArr = [];
for (let i = 0; i < num; i++) {
funArr.push(this.handleUploadTask(arr, i));
}
if (funArr.length > 0) {
let res = await Promise.all(funArr);
}
}
funArr.push(this.preArchiveDicomStudy(data, i));
}
if (funArr && funArr.length > 0) {
let res = await Promise.all(funArr);
}
} catch (err) {
console.log(err);
@ -375,42 +605,58 @@ export default {
//
async handleUploadTask(arr, index) {
arr[index].isUpload = 1;
let res = await this.dicomToOSS(
arr[index].file,
arr[index].params,
arr[index]
);
let path = `/${arr[index].params.TrialId}/TaskImage/${
arr[index].params.SubjectId
}/${arr[index].params.VisitTaskId}/${
arr[index].StudyInstanceUid
}/${this.getGuid(
arr[index].StudyInstanceUid +
arr[index].SeriesInstanceUid +
arr[index].SOPInstanceUid +
arr[index].params.TrialId +
arr[index].params.VisitTaskId
)}`;
let uploadDicom = this.uploadList.filter(
(item) => item.StudyInstanceUid === arr[index].StudyInstanceUid
)[0];
let res = await dicomToOSS(arr[index].file, path);
if (res) {
arr[index].path = res;
this.uploadList[arr[index].uploadIndex].successCount++;
uploadDicom.successCount++;
arr[index].isUpload = 2;
} else {
this.uploadList[arr[index].uploadIndex].status = 2;
this.uploadList[arr[index].uploadIndex].failCount++;
uploadDicom.status = 2;
uploadDicom.failCount++;
arr[index].isUpload = 3;
}
this.uploadList[arr[index].uploadIndex].schedule = Math.floor(
((this.uploadList[arr[index].uploadIndex].successCount +
this.uploadList[arr[index].uploadIndex].failCount) /
this.uploadList[arr[index].uploadIndex].count) *
uploadDicom.schedule = Math.floor(
((uploadDicom.successCount + uploadDicom.failCount) /
uploadDicom.count) *
100
);
if (
this.uploadList[arr[index].uploadIndex].schedule >= 100 &&
this.uploadList[arr[index].uploadIndex].successCount ===
this.uploadList[arr[index].uploadIndex].count
uploadDicom.schedule >= 100 &&
uploadDicom.successCount === uploadDicom.count
) {
this.uploadList[arr[index].uploadIndex].status = 1;
let res = await this.formatDicom(arr, arr[index].uploadIndex);
uploadDicom.status = 1;
let res = await this.formatDicom(arr, arr[index].StudyInstanceUid);
if (res) {
this.uploadList[arr[index].uploadIndex].Study =
res[arr[index].StudyId];
this.uploadList[arr[index].uploadIndex].status = 3;
res = await addOrUpdateArchiveTaskStudy(
this.uploadList[arr[index].uploadIndex]
);
if (res.IsSuccess) {
this.getList();
uploadDicom.Study = res[arr[index].StudyId];
uploadDicom.status = 3;
try {
res = await addOrUpdateArchiveTaskStudy(uploadDicom);
if (res.IsSuccess) {
uploadDicom.status = 4;
this.getList();
} else {
uploadDicom.status = 5;
}
let flag = this.uploadList.every((item) => item.status > 3);
if (flag) this.isLoad = false;
} catch (err) {
uploadDicom.status = 5;
let flag = this.uploadList.every((item) => item.status > 3);
if (flag) this.isLoad = false;
}
}
}
@ -422,11 +668,12 @@ export default {
}
},
//
async formatDicom(arr, index) {
async formatDicom(arr, StudyInstanceUid) {
let studyObj = {};
for (let i = 0; i < arr.length; i++) {
let dicom = arr[i];
if (dicom.uploadIndex !== index) continue;
if (dicom.StudyInstanceUid !== StudyInstanceUid) continue;
let thumbnailPath = `/${dicom.params.TrialId}/TaskImage/${dicom.params.SubjectId}/${dicom.params.VisitTaskId}/${dicom.StudyInstanceUid}/${dicom.SeriesInstanceUid}.png`;
if (!studyObj[dicom.StudyId]) {
studyObj[dicom.StudyId] = {
StudyId: dicom.StudyId,
@ -468,7 +715,7 @@ export default {
TriggerTime: dicom.TriggerTime,
ImageResizePath: await getThumbnail(
dicom.file,
dicom.params,
thumbnailPath,
dicom
),
InstanceList: [
@ -505,12 +752,12 @@ export default {
let instance = {
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
ImageRows: dicom.Rows || 0,
ImageColumns: dicom.Columns || 0,
SliceLocation: dicom.SliceLocation,
@ -550,19 +797,19 @@ export default {
TriggerTime: dicom.TriggerTime,
ImageResizePath: await getThumbnail(
dicom.file,
dicom.params,
thumbnailPath,
dicom
),
InstanceList: [
{
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
ImageRows: dicom.Rows || 0,
ImageColumns: dicom.Columns || 0,
SliceLocation: dicom.SliceLocation,
@ -606,26 +853,6 @@ export default {
}
return studyTime;
},
//
async dicomToOSS(file, params, dicomInfo) {
let path = `/${params.TrialId}/TaskImage/${params.SubjectId}/${
params.VisitTaskId
}/${dicomInfo.StudyInstanceUid}/${this.getGuid(
dicomInfo.StudyInstanceUid +
dicomInfo.SeriesInstanceUid +
dicomInfo.SOPInstanceUid +
params.TrialId +
params.VisitTaskId
)}`;
try {
let res = await dcmUpload(path, file);
if (!res || !res.url) return false;
return this.$getObjectName(res.url);
} catch (err) {
console.log(err);
return false;
}
},
},
};
</script>
@ -633,18 +860,12 @@ export default {
label {
cursor: pointer;
}
.line {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin: 20px 0;
&::after {
display: block;
content: "";
flex: 1;
height: 1px;
border-top: 1px solid #ddd;
::v-deep .el-dialog {
.el-dialog__header {
padding-top: 15px;
}
}
.colorOfRed {
color: #f44336;
}
</style>

View File

@ -5,6 +5,7 @@ cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
import { convertBytes } from "@/utils/dicom-character-set";
import Vue from 'vue';
import { dcmUpload } from "@/utils/dcmUpload/dcmUpload";
let dicom = {
PatientName: "x00100010", // 患者姓名
PatientId: "x00100020", // 患者ID
@ -133,8 +134,20 @@ export const parseDicom = (file, name = false) => {
reader.readAsArrayBuffer(file);
});
};
// 影像上传
// 影像上传
export const dicomToOSS = async (file, path) => {
try {
let res = await dcmUpload(path, file);
if (!res || !res.url) return false;
return Vue.prototype.$getObjectName(res.url);
} catch (err) {
console.log(err);
return false;
}
};
// 获取缩略图
export const getThumbnail = async (file, params, dicomInfo) => {
export const getThumbnail = async (file, ossPath, dicomInfo) => {
try {
if (dicomInfo.modality !== "SR") {
let fileId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
@ -144,9 +157,8 @@ export const getThumbnail = async (file, params, dicomInfo) => {
dicomInfo.Rows
);
if (!blob) return "";
let thumbnailPath = `/${params.TrialId}/TaskImage/${params.SubjectId}/${params.VisitTaskId}/${dicomInfo.StudyInstanceUid}/${dicomInfo.SeriesInstanceUid}.png`;
let OSSclient = Vue.prototype.OSSclient;
let seriesRes = await OSSclient.put(thumbnailPath, blob);
let seriesRes = await OSSclient.put(ossPath, blob);
if (seriesRes && seriesRes.url) {
return Vue.prototype.$getObjectName(seriesRes.url);
} else {