上传、下载影像
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
307403d897
commit
d7a21af10c
|
@ -14,4 +14,20 @@ export function getSubjectImageUploadList(params) {
|
|||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 预上传
|
||||
export function preArchiveDicomStudy(data) {
|
||||
return request({
|
||||
url: '/DownloadAndUpload/preArchiveDicomStudy',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 归档
|
||||
export function addOrUpdateArchiveTaskStudy(data) {
|
||||
return request({
|
||||
url: '/DownloadAndUpload/addOrUpdateArchiveTaskStudy',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
<template>
|
||||
<div class="base-model-wrapper">
|
||||
<el-dialog v-if="config.visible" v-dialogDrag :title="config.title" :append-to-body="config.appendToBody" :visible.sync="config.visible" :close-on-click-modal="false" :show-close="config.showClose" :width="config.width" :fullscreen="config.fullscreen">
|
||||
<el-dialog
|
||||
v-if="config.visible"
|
||||
:v-dialogDrag="!config.dialogDrag"
|
||||
:title="config.title"
|
||||
:append-to-body="config.appendToBody"
|
||||
:visible.sync="config.visible"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="config.showClose"
|
||||
:width="config.width"
|
||||
:fullscreen="config.fullscreen"
|
||||
>
|
||||
<div class="base-modal-body">
|
||||
<slot name="dialog-body" />
|
||||
</div>
|
||||
|
@ -12,46 +22,52 @@
|
|||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseDialog',
|
||||
name: "BaseDialog",
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
visible: false,
|
||||
title: '',
|
||||
title: "",
|
||||
closeOnClickModal: false,
|
||||
showClose: true,
|
||||
appendToBody: false,
|
||||
width: '100px',
|
||||
fullscreen: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
width: "100px",
|
||||
fullscreen: false,
|
||||
dialogDrag: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.base-model-wrapper{
|
||||
.el-dialog__header{
|
||||
padding: 10px;
|
||||
.el-dialog__headerbtn{
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
.el-dialog__body{
|
||||
padding:10px 10px 10px 10px;
|
||||
.base-modal-body{
|
||||
min-height: 100px;
|
||||
max-height:650px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
}
|
||||
.el-dialog__footer{
|
||||
padding: 10px;
|
||||
.base-model-wrapper {
|
||||
.el-dialog__header {
|
||||
padding: 10px;
|
||||
.el-dialog__headerbtn {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 10px 10px 10px 10px;
|
||||
.base-modal-body {
|
||||
min-height: 100px;
|
||||
max-height: 650px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
.notFooter {
|
||||
.is-fullscreen .el-dialog__footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,104 +1,169 @@
|
|||
<template>
|
||||
<base-model :config="model_cfg">
|
||||
<div slot="dialog-body">
|
||||
<div class="top" style="margin-bottom: 10px">
|
||||
<el-button type="primary" size="small" class="el-icon-upload2">
|
||||
<label for="file">
|
||||
{{ $t("trials:uploadImage:button:selectFolder") }}
|
||||
</label>
|
||||
</el-button>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
ref="file"
|
||||
style="display: none"
|
||||
webkitdirectory
|
||||
multiple
|
||||
@change="beginScanFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
<el-table
|
||||
:data="list"
|
||||
border
|
||||
style="width: 100%"
|
||||
height="300"
|
||||
:loading="loading"
|
||||
<el-dialog
|
||||
:visible.sync="visible"
|
||||
:fullscreen="true"
|
||||
:close-on-click-modal="false"
|
||||
custom-class="upload-dialog"
|
||||
:before-close="beforeClose"
|
||||
>
|
||||
<span slot="title">{{ $t("trials:uploadImage:title:uploadImages") }}</span>
|
||||
<el-table :data="list" style="width: 100%" height="300" :loading="loading">
|
||||
<!--受试者-->
|
||||
<el-table-column
|
||||
prop="SubjectCode"
|
||||
:label="$t('trials:uploadImage:table:subjectCode')"
|
||||
/>
|
||||
<!--任务名称-->
|
||||
<el-table-column
|
||||
prop="TaskBlindName"
|
||||
:label="$t('trials:uploadImage:table:taskBlindName')"
|
||||
/>
|
||||
<!--原始检查数-->
|
||||
<el-table-column
|
||||
prop="OrginalStudyList"
|
||||
:label="$t('trials:uploadImage:table:orginalStudyListNum')"
|
||||
>
|
||||
<!--受试者-->
|
||||
<el-table-column
|
||||
prop="subjectCode"
|
||||
:label="$t('trials:uploadImage:table:subjectCode')"
|
||||
/>
|
||||
<!--任务名称-->
|
||||
<el-table-column
|
||||
prop="taskBlindName"
|
||||
:label="$t('trials:uploadImage:table:taskBlindName')"
|
||||
/>
|
||||
<!--原始检查数-->
|
||||
<el-table-column
|
||||
prop="orginalStudyList"
|
||||
:label="$t('trials:uploadImage:table:orginalStudyListNum')"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.orginalStudyList.length
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<!--后处理检查数-->
|
||||
<el-table-column
|
||||
prop="uploadStudyList"
|
||||
:label="$t('trials:uploadImage:table:uploadStudyListNum')"
|
||||
min-width="140"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.orginalStudyList.length
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<!--上传进度-->
|
||||
<el-table-column
|
||||
prop="schedule"
|
||||
:label="$t('trials:uploadImage:table:schedule')"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.orginalStudyList.length
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<!--上传状态-->
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('trials:uploadImage:table:status')"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.orginalStudyList.length
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<!--已有/成功/失败-->
|
||||
<el-table-column
|
||||
prop="scheduleNum"
|
||||
:label="$t('trials:uploadImage:table:scheduleNum')"
|
||||
min-width="140"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.orginalStudyList.length
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common:action:action')" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
circle
|
||||
icon="el-icon-delete"
|
||||
:title="$t('trials:uploadImage:button:delete')"
|
||||
@click.stop="remove(scope.row, item)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.OrginalStudyList &&
|
||||
Array.isArray(scope.row.OrginalStudyList)
|
||||
? scope.row.OrginalStudyList.length
|
||||
: 0
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<!--后处理检查数-->
|
||||
<el-table-column
|
||||
prop="UploadStudyList"
|
||||
:label="$t('trials:uploadImage:table:uploadStudyListNum')"
|
||||
>
|
||||
<template slot-scope="scope">{{
|
||||
scope.row.UploadStudyList && Array.isArray(scope.row.UploadStudyList)
|
||||
? scope.row.UploadStudyList.length
|
||||
: 0
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('common:action:action')"
|
||||
fixed="right"
|
||||
width="140"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<!--下载--->
|
||||
<el-button
|
||||
circle
|
||||
icon="el-icon-download"
|
||||
:title="$t('trials:uploadImage:button:download')"
|
||||
@click.stop="downloadImage(scope.row)"
|
||||
/>
|
||||
<!--删除--->
|
||||
<el-button
|
||||
circle
|
||||
icon="el-icon-delete"
|
||||
:title="$t('trials:uploadImage:button:delete')"
|
||||
@click.stop="remove(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<p class="line">{{ $t("trials:uploadImage:button:uploadTableTitle") }}</p>
|
||||
<div
|
||||
class="top"
|
||||
style="margin-bottom: 10px"
|
||||
v-if="Criterion.ImageUploadEnum > 0"
|
||||
>
|
||||
<el-button type="primary" size="small" class="el-icon-upload2">
|
||||
<label for="file">
|
||||
{{ $t("trials:uploadImage:button:selectFolder") }}
|
||||
</label>
|
||||
</el-button>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
ref="file"
|
||||
style="display: none"
|
||||
webkitdirectory
|
||||
multiple
|
||||
@change="beginScanFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</base-model>
|
||||
<el-table
|
||||
:data="uploadList"
|
||||
style="width: 100%"
|
||||
v-adaptive="{ bottomOffset: 40 }"
|
||||
>
|
||||
<!--检查编号-->
|
||||
<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
|
||||
prop="schedule"
|
||||
:label="$t('trials:uploadImage:table:schedule')"
|
||||
min-width="150"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-progress
|
||||
color="#409eff"
|
||||
:percentage="scope.row.schedule"
|
||||
v-if="scope.row.schedule || scope.row.schedule === 0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--上传状态-->
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('trials:uploadImage:table:status')"
|
||||
min-width="120"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
:type="['warning', 'success', 'danger'][scope.row.status]"
|
||||
size="mini"
|
||||
plain
|
||||
v-if="scope.row.status || scope.row.status === 0"
|
||||
>{{ $fd("UploadStatus", scope.row.status) }}</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--已有/成功/失败-->
|
||||
<el-table-column
|
||||
prop="scheduleNum"
|
||||
:label="$t('trials:uploadImage:table:scheduleNum')"
|
||||
min-width="140"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.count">{{
|
||||
`${scope.row.count} /${scope.row.successCount} / ${scope.row.failCount}`
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('common:action:action')"
|
||||
fixed="right"
|
||||
width="140"
|
||||
>
|
||||
<!-- <template slot-scope="scope">
|
||||
</template> -->
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import baseModel from "@/components/BaseModel";
|
||||
import { getSubjectImageUploadList } from "@/api/load.js";
|
||||
import {
|
||||
getSubjectImageUploadList,
|
||||
preArchiveDicomStudy,
|
||||
addOrUpdateArchiveTaskStudy,
|
||||
} from "@/api/load.js";
|
||||
import { downloadImage } from "@/utils/uploadZip.js";
|
||||
import { parseDicom, getThumbnail } from "@/utils/parseDicom.js";
|
||||
import { dcmUpload } from "@/utils/dcmUpload/dcmUpload";
|
||||
export default {
|
||||
name: "uploadImage",
|
||||
props: {
|
||||
|
@ -122,35 +187,22 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
model_cfg: {
|
||||
visible: false,
|
||||
showClose: true,
|
||||
width: "1000px",
|
||||
title: this.$t("trials:uploadImage:title:uploadImages"),
|
||||
appendToBody: true,
|
||||
},
|
||||
list: [],
|
||||
StudyInstanceUidList: [],
|
||||
SopInstanceUidList: [],
|
||||
|
||||
uploadList: [], // 待上传文件列表
|
||||
dicomList: [], // 上传的文件列表
|
||||
requestNum: 6,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible: {
|
||||
handler() {
|
||||
this.model_cfg.visible = this.visible;
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
"model_cfg.visible": {
|
||||
handler() {
|
||||
this.$emit("update:visible", this.model_cfg.visible);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
beforeClose() {
|
||||
this.$emit("update:visible", false);
|
||||
},
|
||||
// 获取列表
|
||||
async getList() {
|
||||
try {
|
||||
|
@ -161,7 +213,21 @@ export default {
|
|||
let res = await getSubjectImageUploadList(params);
|
||||
this.loading = false;
|
||||
if (res.IsSuccess) {
|
||||
this.StudyInstanceUidList = [];
|
||||
this.SopInstanceUidList = [];
|
||||
this.list = res.Result;
|
||||
res.Result.forEach((item) => {
|
||||
if (item.OrginalStudyList && Array.isArray(item.OrginalStudyList)) {
|
||||
item.OrginalStudyList.forEach((data) => {
|
||||
data.SubjectId = item.SubejctId;
|
||||
data.VisitTaskId = item.VisitTaskId;
|
||||
data.SourceSubjectVisitId = item.SourceSubjectVisitId;
|
||||
this.StudyInstanceUidList.push(data);
|
||||
data.SopInstanceUidList &&
|
||||
this.SopInstanceUidList.push(...data.SopInstanceUidList);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
@ -170,6 +236,396 @@ export default {
|
|||
},
|
||||
// 删除
|
||||
remove(item) {},
|
||||
// 打包下载
|
||||
async downloadImage(item) {
|
||||
try {
|
||||
await downloadImage(
|
||||
this.$route.query.trialId,
|
||||
item.SourceSubjectVisitId
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
// 预上传
|
||||
async preArchiveDicomStudy(post) {
|
||||
try {
|
||||
let res = await preArchiveDicomStudy(post);
|
||||
if (res.IsSuccess) {
|
||||
return res.Result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// 上传
|
||||
async beginScanFiles(e) {
|
||||
let files = e.target.files;
|
||||
try {
|
||||
this.dicomList = [];
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
let dicom = await parseDicom(files[i]);
|
||||
let has = this.StudyInstanceUidList.some(
|
||||
(item) => item.StudyInstanceUid === dicom.StudyInstanceUid
|
||||
);
|
||||
if (
|
||||
!dicom ||
|
||||
!has ||
|
||||
this.SopInstanceUidList.includes(dicom.SopInstanceUid)
|
||||
)
|
||||
continue;
|
||||
dicom.file = files[i];
|
||||
dicom.isUpload = 0;
|
||||
this.StudyInstanceUidList.some((item) => {
|
||||
if (item.StudyInstanceUid === dicom.StudyInstanceUid) {
|
||||
dicom.params = {
|
||||
TrialId: this.$route.query.trialId,
|
||||
SubjectId: item.SubjectId,
|
||||
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;
|
||||
}
|
||||
|
||||
return uploadData.StudyInstanceUid === dicom.StudyInstanceUid;
|
||||
}
|
||||
);
|
||||
if (!uploadHas) {
|
||||
let uploadData = {
|
||||
TrialId: this.$route.query.trialId,
|
||||
SubjectId: item.SubjectId,
|
||||
SourceSubjectVisitId: item.SourceSubjectVisitId,
|
||||
StudyId: dicom.StudyId,
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
FileSize: dicom.file.size,
|
||||
count: 1,
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
schedule: 0,
|
||||
status: 0,
|
||||
IsDicomReUpload: false,
|
||||
};
|
||||
if (
|
||||
item.SopInstanceUidList &&
|
||||
item.SopInstanceUidList.length > 0
|
||||
) {
|
||||
uploadData.IsDicomReUpload = true;
|
||||
}
|
||||
this.uploadList.push(uploadData);
|
||||
dicom.uploadIndex = this.uploadList.length - 1;
|
||||
}
|
||||
}
|
||||
return item.StudyInstanceUid === dicom.StudyInstanceUid;
|
||||
});
|
||||
this.dicomList.push(dicom);
|
||||
}
|
||||
for (let i = 0; i < this.uploadList.length; i++) {
|
||||
let item = this.uploadList[i];
|
||||
let data = {
|
||||
TrialId: item.TrialId,
|
||||
SubjectId: item.SubjectId,
|
||||
SubjectVisitId: item.SourceSubjectVisitId,
|
||||
FileSize: item.FileSize,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
// 并发上传
|
||||
async handleUploadTask(arr, index) {
|
||||
arr[index].isUpload = 1;
|
||||
let res = await this.dicomToOSS(
|
||||
arr[index].file,
|
||||
arr[index].params,
|
||||
arr[index]
|
||||
);
|
||||
if (res) {
|
||||
arr[index].path = res;
|
||||
this.uploadList[arr[index].uploadIndex].successCount++;
|
||||
arr[index].isUpload = 2;
|
||||
} else {
|
||||
this.uploadList[arr[index].uploadIndex].status = 2;
|
||||
this.uploadList[arr[index].uploadIndex].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) *
|
||||
100
|
||||
);
|
||||
if (
|
||||
this.uploadList[arr[index].uploadIndex].schedule >= 100 &&
|
||||
this.uploadList[arr[index].uploadIndex].successCount ===
|
||||
this.uploadList[arr[index].uploadIndex].count
|
||||
) {
|
||||
this.uploadList[arr[index].uploadIndex].status = 1;
|
||||
let res = await this.formatDicom(arr, arr[index].uploadIndex);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
let ind = arr.findIndex((item) => !item.isUpload);
|
||||
if (ind >= 0) {
|
||||
return this.handleUploadTask(arr, ind);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// 数据整理
|
||||
async formatDicom(arr, index) {
|
||||
let studyObj = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let dicom = arr[i];
|
||||
if (dicom.uploadIndex !== index) continue;
|
||||
if (!studyObj[dicom.StudyId]) {
|
||||
studyObj[dicom.StudyId] = {
|
||||
StudyId: dicom.StudyId,
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
StudyTime: this.getTime(dicom.StudyDate, dicom.StudyTime),
|
||||
Modalities: dicom.Modalities,
|
||||
Description: dicom.StudyDescription,
|
||||
SeriesCount: 1,
|
||||
InstanceCount: 1,
|
||||
InstitutionName: dicom.InstitutionName,
|
||||
PatientId: dicom.PatientId,
|
||||
PatientName: dicom.PatientName,
|
||||
PatientAge: dicom.PatientAge,
|
||||
PatientSex: dicom.PatientSex,
|
||||
AccessionNumber: dicom.AccessionNumber,
|
||||
PatientBirthDate: dicom.PatientBirthDate,
|
||||
AcquisitionTime: dicom.AcquisitionTime,
|
||||
AcquisitionNumber: dicom.AcquisitionNumber,
|
||||
TriggerTime: dicom.TriggerTime,
|
||||
BodyPartExamined: dicom.BodyPartExamined,
|
||||
SeriesList: [
|
||||
{
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
SeriesInstanceUid: dicom.SeriesInstanceUid,
|
||||
SeriesNumber: dicom.SeriesNumber,
|
||||
SeriesTime: this.getTime(dicom.SeriesDate, dicom.SeriesTime),
|
||||
Modality: dicom.Modality,
|
||||
Description: dicom.SeriesDescription,
|
||||
InstanceCount: 1,
|
||||
SliceThickness: dicom.SliceThickness,
|
||||
ImagePositionPatient: dicom.ImagePosition,
|
||||
ImageOrientationPatient: dicom.ImageOrientation,
|
||||
BodyPartExamined: dicom.seriesBodyPartExamined,
|
||||
SequenceName: dicom.SequenceName,
|
||||
ProtocolName: dicom.ProtocolName,
|
||||
ImagerPixelSpacing: dicom.ImagePixelSpacing,
|
||||
AcquisitionTime: dicom.AcquisitionTime,
|
||||
AcquisitionNumber: dicom.AcquisitionNumber,
|
||||
TriggerTime: dicom.TriggerTime,
|
||||
ImageResizePath: await getThumbnail(
|
||||
dicom.file,
|
||||
dicom.params,
|
||||
dicom
|
||||
),
|
||||
InstanceList: [
|
||||
{
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
SeriesInstanceUid: dicom.SeriesInstanceUid,
|
||||
SopInstanceUid: dicom.SopInstanceUid,
|
||||
InstanceNumber: dicom.ImageOrInstanceNumber,
|
||||
InstanceTime: this.getTime(
|
||||
dicom.ContentDate,
|
||||
dicom.ContentTime
|
||||
),
|
||||
ImageRows: dicom.Rows || 0,
|
||||
ImageColumns: dicom.Columns || 0,
|
||||
SliceLocation: dicom.SliceLocation,
|
||||
SliceThickness: dicom.SliceThickness,
|
||||
NumberOfFrames: dicom.NumberOfFrames || 0,
|
||||
PixelSpacing: dicom.PixelSpacing,
|
||||
ImagerPixelSpacing: dicom.ImagePixelSpacing,
|
||||
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
|
||||
WindowCenter: dicom.WindowCenter,
|
||||
WindowWidth: dicom.WindowWidth,
|
||||
Path: dicom.path,
|
||||
HtmlPath: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
let seriesHas = studyObj[dicom.StudyId].SeriesList.some((item) => {
|
||||
if (item.SeriesInstanceUid === dicom.SeriesInstanceUid) {
|
||||
item.InstanceCount++;
|
||||
let instance = {
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
SeriesInstanceUid: dicom.SeriesInstanceUid,
|
||||
SopInstanceUid: dicom.SopInstanceUid,
|
||||
InstanceNumber: dicom.ImageOrInstanceNumber,
|
||||
InstanceTime: this.getTime(
|
||||
dicom.ContentDate,
|
||||
dicom.ContentTime
|
||||
),
|
||||
ImageRows: dicom.Rows || 0,
|
||||
ImageColumns: dicom.Columns || 0,
|
||||
SliceLocation: dicom.SliceLocation,
|
||||
SliceThickness: dicom.SliceThickness,
|
||||
NumberOfFrames: dicom.NumberOfFrames || 0,
|
||||
PixelSpacing: dicom.PixelSpacing,
|
||||
ImagerPixelSpacing: dicom.ImagePixelSpacing,
|
||||
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
|
||||
WindowCenter: dicom.WindowCenter,
|
||||
WindowWidth: dicom.WindowWidth,
|
||||
Path: dicom.path,
|
||||
HtmlPath: "",
|
||||
};
|
||||
item.InstanceList.push(instance);
|
||||
}
|
||||
return item.SeriesInstanceUid === dicom.SeriesInstanceUid;
|
||||
});
|
||||
if (!seriesHas) {
|
||||
studyObj[dicom.StudyId].SeriesCount++;
|
||||
let series = {
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
SeriesInstanceUid: dicom.SeriesInstanceUid,
|
||||
SeriesNumber: dicom.SeriesNumber,
|
||||
SeriesTime: this.getTime(dicom.SeriesDate, dicom.SeriesTime),
|
||||
Modality: dicom.Modality,
|
||||
Description: dicom.SeriesDescription,
|
||||
InstanceCount: 1,
|
||||
SliceThickness: dicom.SliceThickness,
|
||||
ImagePositionPatient: dicom.ImagePosition,
|
||||
ImageOrientationPatient: dicom.ImageOrientation,
|
||||
BodyPartExamined: dicom.seriesBodyPartExamined,
|
||||
SequenceName: dicom.SequenceName,
|
||||
ProtocolName: dicom.ProtocolName,
|
||||
ImagerPixelSpacing: dicom.ImagePixelSpacing,
|
||||
AcquisitionTime: dicom.AcquisitionTime,
|
||||
AcquisitionNumber: dicom.AcquisitionNumber,
|
||||
TriggerTime: dicom.TriggerTime,
|
||||
ImageResizePath: await getThumbnail(
|
||||
dicom.file,
|
||||
dicom.params,
|
||||
dicom
|
||||
),
|
||||
InstanceList: [
|
||||
{
|
||||
StudyInstanceUid: dicom.StudyInstanceUid,
|
||||
SeriesInstanceUid: dicom.SeriesInstanceUid,
|
||||
SopInstanceUid: dicom.SopInstanceUid,
|
||||
InstanceNumber: dicom.ImageOrInstanceNumber,
|
||||
InstanceTime: this.getTime(
|
||||
dicom.ContentDate,
|
||||
dicom.ContentTime
|
||||
),
|
||||
ImageRows: dicom.Rows || 0,
|
||||
ImageColumns: dicom.Columns || 0,
|
||||
SliceLocation: dicom.SliceLocation,
|
||||
SliceThickness: dicom.SliceThickness,
|
||||
NumberOfFrames: dicom.NumberOfFrames || 0,
|
||||
PixelSpacing: dicom.PixelSpacing,
|
||||
ImagerPixelSpacing: dicom.ImagePixelSpacing,
|
||||
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
|
||||
WindowCenter: dicom.WindowCenter,
|
||||
WindowWidth: dicom.WindowWidth,
|
||||
Path: dicom.path,
|
||||
HtmlPath: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
studyObj[dicom.StudyId].SeriesList.push(series);
|
||||
}
|
||||
studyObj[dicom.StudyId].InstanceCount++;
|
||||
}
|
||||
}
|
||||
console.log(studyObj);
|
||||
return studyObj;
|
||||
},
|
||||
// dicom时间转换
|
||||
getTime(date, time) {
|
||||
let studyTime = "";
|
||||
if (date) {
|
||||
date = `${date.substring(0, 4)}-${date.substring(
|
||||
4,
|
||||
6
|
||||
)}-${date.substring(6, 8)}`;
|
||||
}
|
||||
if (time) {
|
||||
time = `${time.substring(0, 2)}:${time.substring(
|
||||
2,
|
||||
4
|
||||
)}:${time.substring(4, 6)}`;
|
||||
}
|
||||
if (date) {
|
||||
studyTime = time ? `${date} ${time}` : `${date} 00:00:00`;
|
||||
}
|
||||
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>
|
||||
|
@ -177,4 +633,18 @@ 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;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,12 +1,13 @@
|
|||
import Vue from 'vue'
|
||||
import { encoder } from './encoder'
|
||||
import { anonymization } from './anonymization'
|
||||
export const dcmUpload = async function(name, file, config){
|
||||
export const dcmUpload = async function (name, file, config) {
|
||||
return new Promise(async resolve => {
|
||||
try {
|
||||
// let blob = await encoder(file, config)
|
||||
// let blob = await fileToBlob(file)
|
||||
let blob = await anonymization(file, config)
|
||||
let blob = await fileToBlob(file)
|
||||
if (config) {
|
||||
blob = await anonymization(file, config)
|
||||
}
|
||||
let res = await Vue.prototype.OSSclient.put(name, blob.blob)
|
||||
resolve({
|
||||
...res,
|
||||
|
@ -35,7 +36,7 @@ function fileToBlob(file) {
|
|||
} else {
|
||||
blob = e.target.result
|
||||
}
|
||||
resolve(blob)
|
||||
resolve({ blob })
|
||||
})
|
||||
// FileReader 以 ArrayBuffer 格式 读取 File 对象中数据
|
||||
reader.readAsArrayBuffer(file)
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
import * as dicomParser from "dicom-parser";
|
||||
import * as cornerstone from "cornerstone-core";
|
||||
import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader";
|
||||
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
|
||||
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
||||
import { convertBytes } from "@/utils/dicom-character-set";
|
||||
import Vue from 'vue';
|
||||
let dicom = {
|
||||
PatientName: "x00100010", // 患者姓名
|
||||
PatientId: "x00100020", // 患者ID
|
||||
PatientBirthDate: "x00100030", // 患者出生日期
|
||||
PatientBirthTime: "x00100032", // 患者出生时间
|
||||
PatientSex: "x00100040", // 患者性别
|
||||
PatientWeight: "x00101030", // 患者体重
|
||||
PregnancyStatus: "x001021c0", // 怀孕状态
|
||||
PatientAge: "x00101010", // 患者年龄(做检查时刻的患者年龄,而不是此刻患者的真实年龄)
|
||||
AcquisitionTime: "x00080032",
|
||||
AcquisitionNumber: "x00200012",
|
||||
TriggerTime: "x00181060",
|
||||
AccessionNumber: "x00080050", // 检查号
|
||||
StudyId: "x00200010", // 检查ID
|
||||
StudyInstanceUid: "x0020000d", // 检查实例号
|
||||
StudyDate: "x00080020", // 检查日期
|
||||
StudyTime: "x00080030", // 检查时间
|
||||
Modalities: "x00080061", // 一个检查中含有的不同检查类型
|
||||
BodyPartExamined: "x00080015", // 检查的部位
|
||||
StudyDescription: "x00081030", // 检查的描述
|
||||
InstitutionName: "x00080080",
|
||||
SeriesNumber: "x00200011", // 序列号
|
||||
SeriesInstanceUid: "x0020000e", // 序列实例号
|
||||
Modality: "x00080060", // 检查模态
|
||||
SeriesDescription: "x0008103e", // 检查描述和说明
|
||||
SeriesDate: "x00080021", // 检查日期
|
||||
SeriesTime: "x00080031", // 检查时间
|
||||
SequenceName: "x00180024",
|
||||
ProtocolName: "x00181030",
|
||||
ImagePosition: "x00200032", // 图像位置
|
||||
ImageOrientation: "x00200037", // 图像方位
|
||||
ImagePixelSpacing: "x00181164",
|
||||
SliceThickness: "x00180050", // 层厚
|
||||
SpacingBetweenSlices: "x00180088", // 层间距
|
||||
SliceLocation: "x00201041", // 相对位置
|
||||
MRAcquisition: "x00180023",
|
||||
seriesBodyPartExamined: "x00180015", // 身体部位
|
||||
ImageType: "x00080008",
|
||||
SopInstanceUid: "x00080018", // SOP实例UID
|
||||
ContentDate: "x00080023", // 影像拍摄日期
|
||||
ContentTime: "x00080033", // 影像拍摄时间
|
||||
ImageOrInstanceNumber: "x00200013", // 图像码
|
||||
SamplesPerPixel: "x00280002", // 图像采样率
|
||||
PhotometricInterpretation: "x00280004", // 光度计(对于CT图像,用两个枚举值MONOCHROME1,MONOCHROME2 用来判断图像是否是彩色的;MONOCHROME 1/2是灰度图,RGB则是真彩色图)
|
||||
Rows: "x00280010", // 行数
|
||||
Columns: "x00280011", // 列数
|
||||
NumberOfFrames: "x00280008",
|
||||
PixelSpacing: "x00280030", // 像素间距
|
||||
BitsAllocated: "x00280100", // 分配的位数
|
||||
BitsStored: "x00280101", // 存储的位数
|
||||
HighBit: "x00280102", // 高位
|
||||
PixelRepresentation: "x00280103", // 像素数据的表现类型(一个枚举值,分别为十六进制数0000和0001.0000H = 无符号整型,0001H = 2的补码)
|
||||
WindowCenter: "x00281050", // 窗位
|
||||
WindowWidth: "x002811051", // 窗宽
|
||||
RescaleIntercept: "x00281052", // 截距
|
||||
RescaleSlope: "x00281053", // 斜率
|
||||
RescaleType: "x00281054", // 输出值的单位
|
||||
ParsingFormat: "x00080005",
|
||||
FrameOfReferenceUID: "x00200052",
|
||||
};
|
||||
// 需要设置默认值
|
||||
let defaultKey = ['Rows', 'Columns', 'SliceLocation', 'NumberOfFrames'];
|
||||
// 需要格式解析
|
||||
let pormatParseKey = ['PatientName', 'SeriesDescription', 'StudyDescription'];
|
||||
// 解析dicom文件
|
||||
export const parseDicom = (file, name = false) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
try {
|
||||
let data = dicomParser.parseDicom(new Uint8Array(e.target.result));
|
||||
let res = {};
|
||||
if (name && Array.isArray(name)) {
|
||||
name.forEach((item) => {
|
||||
if (dicom[item]) {
|
||||
res[item] = data.string(dicom[item]) || data.uint16(dicom[item]) || data.intString(dicom[item]) || '';
|
||||
}
|
||||
if (item === 'SliceLocation') {
|
||||
res[item] = data.intString(dicom[item])
|
||||
}
|
||||
});
|
||||
} else if (name) {
|
||||
if (dicom[name]) {
|
||||
res[name] = data.string(dicom[name]) || data.uint16(dicom[name]) || data.intString(dicom[name]) || '';
|
||||
if (name === 'SliceLocation') {
|
||||
res[name] = data.intString(dicom[name])
|
||||
}
|
||||
} else {
|
||||
reject("name is inexistence");
|
||||
}
|
||||
} else {
|
||||
Object.keys(dicom).forEach((key) => {
|
||||
res[key] = data.string(dicom[key]) || data.uint16(dicom[key]) || data.intString(dicom[key]) || '';
|
||||
if (key === 'SliceLocation') {
|
||||
res[key] = data.intString(dicom[key])
|
||||
}
|
||||
});
|
||||
}
|
||||
pormatParseKey.forEach(key => {
|
||||
if (res[key]) {
|
||||
const Element = data.elements[dicom[key]];
|
||||
const Bytes = new Uint8Array(
|
||||
data.byteArray.buffer,
|
||||
Element ? Element.dataOffset : 0,
|
||||
Element ? Element.length : 0
|
||||
);
|
||||
res[key] = convertBytes(
|
||||
res.ParsingFormat,
|
||||
Bytes
|
||||
);
|
||||
}
|
||||
})
|
||||
defaultKey.forEach(key => {
|
||||
if (!res[key] && res.hasOwnProperty(key)) {
|
||||
res[key] = 0;
|
||||
}
|
||||
})
|
||||
resolve(res);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
reader.onerror = function (e) {
|
||||
reject(e);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
};
|
||||
// 获取缩略图
|
||||
export const getThumbnail = async (file, params, dicomInfo) => {
|
||||
try {
|
||||
if (dicomInfo.modality !== "SR") {
|
||||
let fileId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
|
||||
let blob = await dicomToPng(
|
||||
fileId,
|
||||
dicomInfo.Columns,
|
||||
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);
|
||||
if (seriesRes && seriesRes.url) {
|
||||
return Vue.prototype.$getObjectName(seriesRes.url);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
const canvasToBlob = (canvas) => {
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(blob);
|
||||
});
|
||||
});
|
||||
};
|
||||
const dicomToPng = (imageId, width, height) => {
|
||||
return new Promise((resolve) => {
|
||||
cornerstone.loadImage(imageId).then(async (image) => {
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
if (image) {
|
||||
cornerstone.renderToCanvas(canvas, image);
|
||||
// 将 Canvas 图像对象转换为 PNG 格式
|
||||
let blob = await canvasToBlob(canvas);
|
||||
resolve(blob);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}).catch((reason) => {
|
||||
reason();
|
||||
});
|
||||
};
|
|
@ -43,6 +43,7 @@
|
|||
prop="OrganizationName"
|
||||
>
|
||||
<el-input
|
||||
:disabled="user.IsZhiZhun"
|
||||
v-model="user.OrganizationName"
|
||||
:placeholder="$t('trials:trials-myinfo:form:organization')"
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
>
|
||||
<template v-for="item of $d.Criterion_Question_Type">
|
||||
<el-option
|
||||
v-if="item.value !== 'calculation' && item.value !== 'increment'"
|
||||
v-if="item.value !== 'calculation' && item.value !== 'increment'&& item.value !== 'table'&& item.value !== 'group'"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
|
|
|
@ -285,7 +285,7 @@ export default {
|
|||
handleSubjectVisitChange(val) {
|
||||
if (val) {
|
||||
const selectArr = this.subjectVisitOptions.filter(item => item.Id === val)
|
||||
this.form.VisitNum = selectArr[0].VisitNum + 0.1
|
||||
this.form.VisitNum = Math.ceil((selectArr[0].VisitNum + 0.1)*10)/10;
|
||||
}
|
||||
},
|
||||
handleIsFinalVisitChange(val) {
|
||||
|
|
|
@ -977,7 +977,15 @@ const PDFViewerApplication = {
|
|||
|
||||
await Promise.all(promises);
|
||||
},
|
||||
|
||||
decodeUtf8(bytes) {
|
||||
let str = bytes.split('?');
|
||||
let str2 = str[0].split('/');
|
||||
let name = str2[str2.length-1];
|
||||
name = encodeURIComponent(name);
|
||||
str.shift();
|
||||
str2.pop();
|
||||
return str2.join("/")+'/'+name+'?'+str.join("?");
|
||||
},
|
||||
async open(file, args) {
|
||||
if (this.pdfLoadingTask) {
|
||||
await this.close();
|
||||
|
@ -992,6 +1000,7 @@ const PDFViewerApplication = {
|
|||
const parameters = Object.create(null);
|
||||
|
||||
if (typeof file === "string") {
|
||||
file = this.decodeUtf8(file);
|
||||
this.setTitleUsingUrl(file, file);
|
||||
parameters.url = file;
|
||||
} else if (file && "byteLength" in file) {
|
||||
|
|
Loading…
Reference in New Issue