Compare commits

...

18 Commits
temp ... main

Author SHA1 Message Date
wangxiaoshuang 97a406d32a Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing Details
2026-08-07 14:08:00 +08:00
wangxiaoshuang f47db19702 下载优化 2026-08-07 14:07:50 +08:00
caiyiling f3610b685a Merge commit '5bbbd9334e9ad13c4dd2cf02b7c348278e777feb' into main
continuous-integration/drone/push Build is passing Details
2026-08-07 09:16:44 +08:00
wangxiaoshuang 3cf1cf5f91 下载优化
continuous-integration/drone/push Build is passing Details
2026-08-06 15:56:17 +08:00
wangxiaoshuang 8315bcf59f 【dicom分割】MPR横截面的普通标记绑定保存时会提示是否切换至二维视图
continuous-integration/drone/push Build is passing Details
2026-08-06 15:16:31 +08:00
wangxiaoshuang 653faaf4ba 【ivus&oct】靶段标注上传页面,非dicom鼠标悬浮的位置和高亮显示的行不一致
continuous-integration/drone/push Build is passing Details
2026-08-06 14:38:37 +08:00
wangxiaoshuang aa92776b31 【受试者】受试者创建时,受试者名称禁用特殊符号
continuous-integration/drone/push Build is passing Details
2026-08-06 09:52:03 +08:00
caiyiling 5bbbd9334e ivus & oct 导表后自动保存逻辑更改
continuous-integration/drone/push Build is passing Details
2026-08-05 16:48:41 +08:00
wangxiaoshuang fcd6efaa39 Merge branch 'uat'
continuous-integration/drone/push Build is pending Details
2026-08-05 14:11:15 +08:00
caiyiling b7de254b5f Merge commit 'c53890add7987e6753ded211b8675a0a1853f56a' into main
continuous-integration/drone/push Build is passing Details
2026-08-05 10:16:12 +08:00
caiyiling 9256970662 IVUS & OCT标准更改
continuous-integration/drone/push Build is passing Details
2026-08-05 09:08:46 +08:00
wangxiaoshuang c53890add7 质控dicom编辑检查类型报错修改
continuous-integration/drone/push Build is passing Details
2026-08-04 13:14:58 +08:00
caiyiling 23fb7be52b Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing Details
2026-08-03 14:44:37 +08:00
caiyiling 4e5c9aaf38 质控页面注册自定义metaDataProvider逻辑更改 2026-08-03 14:43:44 +08:00
wangxiaoshuang 52bb39d6c2 下载优化
continuous-integration/drone/push Build is passing Details
2026-08-03 11:49:09 +08:00
wangxiaoshuang cbd860a35e 先下载再上传的逻辑指定IVUS和OCT标准
continuous-integration/drone/push Build is passing Details
2026-07-31 15:01:02 +08:00
wangxiaoshuang 7e9eb1f3a5 待阅列表屏蔽上传下载按钮
continuous-integration/drone/push Build is passing Details
2026-07-31 11:00:34 +08:00
wangxiaoshuang 254e43ac3d 分割球体工具bug修复
continuous-integration/drone/push Build is passing Details
2026-07-30 10:47:35 +08:00
18 changed files with 325 additions and 192 deletions

View File

@ -80,7 +80,7 @@
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneMath from 'cornerstone-math'
import * as cornerstoneTools from 'cornerstone-tools'
import metaDataProvider from '@/utils/metaDataProvider'
import metaDataProvider, { registerImageSharedCacheKey, unregisterImageSharedCacheKeys } from '@/utils/metaDataProvider'
const scroll = cornerstoneTools.import('util/scrollToIndex')
import Hammer from 'hammerjs'
@ -211,6 +211,9 @@ export default {
this.stackScrollCallback
)
},
beforeDestroy() {
unregisterImageSharedCacheKeys(this.stack?.imageIds)
},
methods: {
loadImageStack(dicomSeries, text = '') {
@ -400,6 +403,10 @@ export default {
},
onNewImage(e) {
e.detail.enabledElement.options = {}
registerImageSharedCacheKey(
e.detail.image?.imageId,
e.detail.image?.sharedCacheKey
)
var data = e.detail.image.data
this.dicomInfo.hospital = data.string('x00080080')
// let instanceInfo = this.series.instanceInfoList.find(item => item.ImageId === e.detail.image.imageId)

View File

@ -477,7 +477,10 @@ export default {
let res = await getSubjectImageUploadList(params)
this.loading = false
if (res.IsSuccess) {
this.isDownloaded = res.OtherInfo.IsIRImageDownloaded
this.isDownloaded = this.Criterion && this.Criterion.CriterionType && (
this.Criterion.CriterionType == 19 ||
this.Criterion.CriterionType == 20
) ? res.OtherInfo.IsIRImageDownloaded : true
this.TrialModality = res.OtherInfo.TrialModality.split('|')
this.IsReadingTaskViewInOrder = res.OtherInfo.IsReadingTaskViewInOrder || res.OtherInfo.IsReadingTaskViewInOrder === 0 ? res.OtherInfo.IsReadingTaskViewInOrder : 2
this.StudyInstanceUidList = []

View File

@ -104,7 +104,7 @@
<span v-else>{{ scope.row.UploadedFileCount }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('common:action:action')" fixed="right" width="180">
<el-table-column :label="$t('common:action:action')" width="180">
<template slot-scope="scope">
<!--预览--->
<el-button circle :disabled="scope.row.UploadedFileCount <= 0" icon="el-icon-view"
@ -353,7 +353,10 @@ export default {
if (res.IsSuccess) {
this.list = res.Result
this.relationInfo = res.OtherInfo
this.isDownloaded = res.OtherInfo.IsIRImageDownloaded
this.isDownloaded = this.Criterion && this.Criterion.CriterionType && (
this.Criterion.CriterionType == 19 ||
this.Criterion.CriterionType == 20
) ? res.OtherInfo.IsIRImageDownloaded : true
this.faccept = []
this.relationInfo.ImageFormatList.forEach((item) => {
this.faccept.push(`.${item}`)

View File

@ -119,6 +119,25 @@ function parseImageId(imageId) {
return { scheme, url, frame };
}
const imageIdToSharedCacheKey = new Map();
export function registerImageSharedCacheKey(imageId, sharedCacheKey) {
if (!imageId || !sharedCacheKey) {
return;
}
imageIdToSharedCacheKey.set(imageId, sharedCacheKey);
}
export function unregisterImageSharedCacheKeys(imageIds) {
if (!Array.isArray(imageIds) || imageIds.length === 0) {
return;
}
imageIds.forEach((imageId) => {
if (imageId) {
imageIdToSharedCacheKey.delete(imageId);
}
});
}
function getNumberValues(dataSet, tag, minimumLength) {
const values = [];
const valueAsString = dataSet.string(tag);
@ -257,10 +276,20 @@ function getSpacingBetweenSlices(dataSet) {
}
function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId);
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
let dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
parsedImageId.url
);
if (!dataSet) {
const sharedCacheKey = imageIdToSharedCacheKey.get(imageId);
if (sharedCacheKey) {
dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
sharedCacheKey
);
}
}
if (!dataSet) {
return;
}
@ -421,7 +450,6 @@ function metaDataProvider(type, imageId) {
let columnPixelSpacing = null;
let rowPixelSpacing = null;
if (pixelSpacing) {
rowPixelSpacing = pixelSpacing[0];
columnPixelSpacing = pixelSpacing[1];

View File

@ -11,10 +11,11 @@ function zipFiles(zipName, files) {
store.dispatch('trials/setUnLock', true)
files = formatFiles(files)
// 创建压缩文件输出流
const zipFileOutputStream = streamSaver.createWriteStream(zipName);
let zipFileOutputStream = streamSaver.createWriteStream(zipName);
// 创建下载文件流
const fileIterator = files.values();
const readableZipStream = new ZIP({
let Url = [];
let readableZipStream = new ZIP({
async pull(ctrl) {
const fileInfo = fileIterator.next();
if (fileInfo.done) {//迭代终止
@ -23,6 +24,7 @@ function zipFiles(zipName, files) {
let { name, url, IsEncapsulated = false } = fileInfo.value;
if (IsEncapsulated) {
url = await downloadFileToUrl(url)
Url.push(url)
}
// url = decodeUtf8(url); // 待定,可能做过特殊处理
return fetch(url).then(res => {
@ -40,13 +42,31 @@ function zipFiles(zipName, files) {
.pipeTo(zipFileOutputStream)
.then(() => {
console.log("同步下载打包结束时间:" + new Date());
store.dispatch('trials/setUnLock', false)
resolve(true)
}).catch(err => {
console.log(err);
resolve(false)
}).finally(() => {
store.dispatch('trials/setUnLock', false)
readableZipStream = null;
zipFileOutputStream = null;
if (Url && Url.length > 0) {
Url.forEach(url => {
URL.revokeObjectURL(url)
})
Url = null
}
});
} else {
store.dispatch('trials/setUnLock', false)
readableZipStream = null;
zipFileOutputStream = null;
if (Url && Url.length > 0) {
Url.forEach(url => {
URL.revokeObjectURL(url)
})
Url = null
}
resolve(false)
}
} catch (err) {
@ -61,15 +81,17 @@ async function updateFile(file, name) {
return new Promise(async resolve => {
try {
store.dispatch('trials/setUnLock', true)
const fileOutputStream = streamSaver.createWriteStream(name);
let fileOutputStream = streamSaver.createWriteStream(name);
// file = decodeUtf8(file);
let res = await fetch(file);
res.body.pipeTo(fileOutputStream).then(() => {
store.dispatch('trials/setUnLock', true)
resolve(true)
}).catch(err => {
console.log(err)
resolve(false)
}).finally(() => {
store.dispatch('trials/setUnLock', false)
fileOutputStream = null;
});
} catch (err) {
console.log(err)
@ -110,13 +132,17 @@ function downloadFileToUrl(url) {
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
const arraybuffer = xhr.response;
let dataset = dcmjs.data.DicomMessage.readFile(arraybuffer)
let newDicomFile = dataset.write({ fragmentMultiframe: false, startOffset: false }) // fragmentMultiframe 原始数据是否进行分割
const bufferArray = new Uint8Array(newDicomFile)
const blob = new Blob([bufferArray], { type: 'application/octet-stream' })
const href = URL.createObjectURL(blob)
resolve(href)
try {
const arraybuffer = xhr.response;
let dataset = dcmjs.data.DicomMessage.readFile(arraybuffer)
let newDicomFile = dataset.write({ fragmentMultiframe: false, startOffset: false }) // fragmentMultiframe 原始数据是否进行分割
const bufferArray = new Uint8Array(newDicomFile)
const blob = new Blob([bufferArray], { type: 'application/octet-stream' })
const href = URL.createObjectURL(blob)
resolve(href)
} catch (err) {
reject(err);
}
};
xhr.send();
})

View File

@ -133,7 +133,7 @@ import { mapGetters } from 'vuex'
import Contextmenu from 'vue-contextmenujs'
Vue.use(Contextmenu)
import * as cornerstone from 'cornerstone-core'
import metaDataProvider from '@/utils/metaDataProvider'
import metaDataProvider, { registerImageSharedCacheKey, unregisterImageSharedCacheKeys } from '@/utils/metaDataProvider'
import * as cornerstoneMath from 'cornerstone-math'
import * as cornerstoneTools from 'cornerstone-tools'
@ -480,6 +480,7 @@ export default {
},
beforeDestroy() {
// Store.$off('getMeasureData')
unregisterImageSharedCacheKeys(this.stack?.imageIds)
DicomEvent.$off('setReadingState')
},
methods: {
@ -1279,6 +1280,7 @@ export default {
this.resetHideMeasureArr()
}
const { image } = e.detail
registerImageSharedCacheKey(image?.imageId, image?.sharedCacheKey)
const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager
const imageId = image.imageId
ToolStateManager.clearImageIdToolState(imageId)

View File

@ -189,11 +189,22 @@
</template>
<!-- 计算值 -->
<Questions ref="ecrf2" :question-form-change-state="true" :question-form-change-num="0" :is-qulity-issues="false"
:group-classify="4" style="margin-top:20px" />
<Questions
ref="ecrf2"
:question-form-change-state="true"
:question-form-change-num="0"
:is-qulity-issues="false"
:group-classify="4"
:question-type="12"
style="margin-top:20px" />
<!-- 评估结果 -->
<Questions ref="ecrf3" :question-form-change-state="true" :question-form-change-num="0" :is-qulity-issues="false"
<Questions
ref="ecrf3"
:question-form-change-state="true"
:question-form-change-num="0"
:is-qulity-issues="false"
:question-type="12"
:group-classify="5" />
<!-- 新增编辑弹窗 -->
<el-dialog v-if="addOrEdit.visible" :visible.sync="addOrEdit.visible" :close-on-click-modal="false"
@ -622,7 +633,7 @@ export default {
return v
}
},
async handleSave(index) {
async handleSave(index, options = {}) {
if (this.rowSaveLoadingMap[index]) return
const refName = `questions${index}`
this.$set(this.rowSaveLoadingMap, index, true)
@ -640,9 +651,13 @@ export default {
answers: answers
}
await saveTaskQuestion(12, params)
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (!options.silentSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
DicomEvent.$emit('getReportInfo', true)
this.refreshQuestions()
if (!options.skipRefresh) {
await this.refreshQuestions()
}
this.formChanged = false
} catch (e) {
console.log(e)
@ -858,9 +873,29 @@ export default {
}
},
async uploadDlgClose() {
this.refreshQuestions()
DicomEvent.$emit('getReportInfo', true)
this.upload.visible = false
try {
await this.refreshQuestions()
const saveTasks = []
for (let index = 0; index < this.questions.length; index++) {
const qs = this.questions[index]
if (qs.GroupClassify === 6) {
saveTasks.push(() => this.handleSave(index, { silentSuccess: true, skipRefresh: true }))
}
}
saveTasks.push(() => this.$refs['ecrf2'] && this.$refs['ecrf2'].handleSave({ silentSuccess: true, skipRefresh: true }))
saveTasks.push(() => this.$refs['ecrf3'] && this.$refs['ecrf3'].handleSave({ silentSuccess: true, skipRefresh: true }))
for (const saveTask of saveTasks) {
try {
await saveTask()
} catch (e) {
console.log(e)
}
}
} catch (e) {
console.log(e)
} finally {
this.upload.visible = false
}
},
renderHeader(h, { column, $index }) {
const span = document.createElement('span')

View File

@ -189,11 +189,22 @@
</template>
<!-- 计算值 -->
<Questions ref="ecrf2" :question-form-change-state="true" :question-form-change-num="0" :is-qulity-issues="false"
:group-classify="4" style="margin-top:20px" />
<Questions
ref="ecrf2"
:question-form-change-state="true"
:question-form-change-num="0"
:is-qulity-issues="false"
:group-classify="4"
:question-type="12"
style="margin-top:20px" />
<!-- 评估结果 -->
<Questions ref="ecrf3" :question-form-change-state="true" :question-form-change-num="0" :is-qulity-issues="false"
<Questions
ref="ecrf3"
:question-form-change-state="true"
:question-form-change-num="0"
:is-qulity-issues="false"
:question-type="12"
:group-classify="5" />
<el-dialog v-if="addOrEdit.visible" :visible.sync="addOrEdit.visible" :close-on-click-modal="false"
:title="addOrEdit.title" width="500px">
@ -617,7 +628,7 @@ export default {
return v
}
},
async handleSave(index) {
async handleSave(index, options = {}) {
if (this.rowSaveLoadingMap[index]) return
const refName = `questions${index}`
this.$set(this.rowSaveLoadingMap, index, true)
@ -635,9 +646,13 @@ export default {
answers: answers
}
await saveTaskQuestion(12, params)
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (!options.silentSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
DicomEvent.$emit('getReportInfo', true)
this.refreshQuestions()
if (!options.skipRefresh) {
await this.refreshQuestions()
}
this.formChanged = false
} catch (e) {
console.log(e)
@ -884,9 +899,29 @@ export default {
}
},
async uploadDlgClose() {
this.refreshQuestions()
DicomEvent.$emit('getReportInfo', true)
this.upload.visible = false
try {
await this.refreshQuestions()
const saveTasks = []
for (let index = 0; index < this.questions.length; index++) {
const qs = this.questions[index]
if (qs.GroupClassify === 6) {
saveTasks.push(() => this.handleSave(index, { silentSuccess: true, skipRefresh: true }))
}
}
saveTasks.push(() => this.$refs['ecrf2'] && this.$refs['ecrf2'].handleSave({ silentSuccess: true, skipRefresh: true }))
saveTasks.push(() => this.$refs['ecrf3'] && this.$refs['ecrf3'].handleSave({ silentSuccess: true, skipRefresh: true }))
for (const saveTask of saveTasks) {
try {
await saveTask()
} catch (e) {
console.log(e)
}
}
} catch (e) {
console.log(e)
} finally {
this.upload.visible = false
}
},
renderHeader(h, { column, $index }) {
const span = document.createElement('span')

View File

@ -279,84 +279,86 @@ export default {
})
},
handleSave() {
if (this.saveLoading) return
async handleSave(options = {}) {
if (this.saveLoading) return false
this.saveLoading = true
this.$refs['questions'].validate(async (valid) => {
if (!valid) {
this.saveLoading = false
return
}
// lugano
if ((this.criterionType === 2 || this.criterionType === 18) && this.groupClassify === 3) {
//
var existUnSave = this.checkAnnotationStatus(this.questions)
if (existUnSave) {
this.$alert(this.$t('trials:lugano:message:saveWarning'), this.$t('trials:lugano:fusionDialog:warning'))
this.saveLoading = false
return
}
var currentSpleenStatus = this.questionForm[this.spleenStatusId]
var currentSpleenLength = this.questionForm[this.spleenLengthId]
currentSpleenStatus = isNaN(parseInt(currentSpleenStatus)) ? null : parseInt(currentSpleenStatus)
var stIdx = this.measurements.findIndex(i => i.QuestionType === 60)
var slIdx = this.measurements.findIndex(i => i.QuestionType === 61)
if (currentSpleenLength && currentSpleenStatus === 5) {
// '!'
this.$alert(this.$t('trials:lugano:message:validSpleen1'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
} else if (currentSpleenStatus === 5 && ((stIdx > -1 && this.measurements[stIdx].MeasureData) || (slIdx > -1 && this.measurements[slIdx].MeasureData))) {
//
this.$alert(this.$t('trials:lugano:message:validSpleen1'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
} else if (((stIdx > -1 && this.measurements[stIdx].MeasureData) || (slIdx > -1 && this.measurements[slIdx].MeasureData)) && !currentSpleenLength) {
this.$alert(this.$t('trials:lugano:message:validSpleen2'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
} else {
this.saveQuestionsList()
}
} else if ((this.criterionType === 2 || this.criterionType === 18) && this.groupClassify === 1 && this.questionForm[this.imageQualityIssuesId] && parseInt(this.questionForm[this.imageQualityIssuesId]) === 6) {
// PET-CT
let res = null
if (this.criterionType === 18) {
res = await getCanChooseNotMergeNoPet({ visitTaskId: this.visitTaskId })
} else {
res = await getCanChooseNotMerge({ visitTaskId: this.visitTaskId })
}
if (res.IsSuccess && !res.Result.IsCanChooseNotMerge) {
// this.$alert(this.$t('trials:lugano:saveQuestions:warning1'), this.$t('trials:lugano:fusionDialog:warning'), {showCancelButton: false})
this.$alert(this.$t('trials:lugano:saveQuestions:warning1'), this.$t('trials:lugano:fusionDialog:warning'), {
showCancelButton: false,
callback: action => { }
})
this.saveLoading = false
return
} else {
// PET-CTFDG-PETNE
this.$confirm(this.$t('trials:lugano:saveQuestions:warning2'), {
type: 'warning',
distinguishCancelAndClose: true
})
.then(() => {
this.saveQuestionsList()
})
}
} else {
this.saveQuestionsList()
}
// this.saveQuestionsList()
const valid = await new Promise(resolve => {
this.$refs['questions'].validate(v => resolve(v))
})
if (!valid) {
this.saveLoading = false
return false
}
// lugano
if ((this.criterionType === 2 || this.criterionType === 18) && this.groupClassify === 3) {
//
var existUnSave = this.checkAnnotationStatus(this.questions)
if (existUnSave) {
await this.$alert(this.$t('trials:lugano:message:saveWarning'), this.$t('trials:lugano:fusionDialog:warning'))
this.saveLoading = false
return false
}
var currentSpleenStatus = this.questionForm[this.spleenStatusId]
var currentSpleenLength = this.questionForm[this.spleenLengthId]
currentSpleenStatus = isNaN(parseInt(currentSpleenStatus)) ? null : parseInt(currentSpleenStatus)
var stIdx = this.measurements.findIndex(i => i.QuestionType === 60)
var slIdx = this.measurements.findIndex(i => i.QuestionType === 61)
if (currentSpleenLength && currentSpleenStatus === 5) {
// '!'
await this.$alert(this.$t('trials:lugano:message:validSpleen1'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
return false
} else if (currentSpleenStatus === 5 && ((stIdx > -1 && this.measurements[stIdx].MeasureData) || (slIdx > -1 && this.measurements[slIdx].MeasureData))) {
//
await this.$alert(this.$t('trials:lugano:message:validSpleen1'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
return false
} else if (((stIdx > -1 && this.measurements[stIdx].MeasureData) || (slIdx > -1 && this.measurements[slIdx].MeasureData)) && !currentSpleenLength) {
await this.$alert(this.$t('trials:lugano:message:validSpleen2'), this.$t('trials:lugano:fusionDialog:warning'), {
callback: _ => { }
})
this.saveLoading = false
return false
}
return await this.saveQuestionsList(options)
} else if ((this.criterionType === 2 || this.criterionType === 18) && this.groupClassify === 1 && this.questionForm[this.imageQualityIssuesId] && parseInt(this.questionForm[this.imageQualityIssuesId]) === 6) {
// PET-CT
let res = null
if (this.criterionType === 18) {
res = await getCanChooseNotMergeNoPet({ visitTaskId: this.visitTaskId })
} else {
res = await getCanChooseNotMerge({ visitTaskId: this.visitTaskId })
}
if (res.IsSuccess && !res.Result.IsCanChooseNotMerge) {
await this.$alert(this.$t('trials:lugano:saveQuestions:warning1'), this.$t('trials:lugano:fusionDialog:warning'), {
showCancelButton: false,
callback: action => { }
})
this.saveLoading = false
return false
}
// PET-CTFDG-PETNE
const confirm = await this.$confirm(this.$t('trials:lugano:saveQuestions:warning2'), {
type: 'warning',
distinguishCancelAndClose: true
}).then(() => true).catch(() => false)
if (!confirm) {
this.saveLoading = false
return false
}
return await this.saveQuestionsList(options)
} else {
return await this.saveQuestionsList(options)
}
},
async saveQuestionsList() {
async saveQuestionsList(options = {}) {
this.loading = true
var answers = []
var imageQuality = null
@ -382,18 +384,22 @@ export default {
// })
// params.questionMarkInfoList = questionMarkInfoList
// }
saveTaskQuestion(this.questionType, params).then(async res => {
try {
const res = await saveTaskQuestion(this.questionType, params)
if (this.imageQualityIssuesId) {
store.dispatch('reading/setImageQualityIssues', this.questionForm[this.imageQualityIssuesId])
if (this.questionForm[this.imageQualityIssuesId] && parseInt(this.questionForm[this.imageQualityIssuesId]) === 6) {
DicomEvent.$emit('closePetct')
}
}
this.$message.success(this.$t('common:message:savedSuccessfully'))
var trialId = this.$route.query.trialId
await store.dispatch('reading/refreshDicomReadingQuestionAnswer', { trialId: trialId, visitTaskId: this.visitTaskId })
this.getQuestions(this.visitTaskId, true)
if (!options.silentSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
if (!options.skipRefresh) {
var trialId = this.$route.query.trialId
await store.dispatch('reading/refreshDicomReadingQuestionAnswer', { trialId: trialId, visitTaskId: this.visitTaskId })
this.getQuestions(this.visitTaskId, true)
}
this.loading = false
if (this.isQulityIssues) {
DicomEvent.$emit('questionFormChange', false)
@ -417,10 +423,12 @@ export default {
}
this.formChanged = false
this.saveLoading = false
}).catch(() => {
return res
} catch (e) {
this.loading = false
this.saveLoading = false
})
throw e
}
},
checkAnnotationStatus(obj) {
for (let i = 0; i < obj.length; i++) {

View File

@ -126,7 +126,7 @@ import { mapGetters } from 'vuex'
import Contextmenu from 'vue-contextmenujs'
Vue.use(Contextmenu)
import * as cornerstone from 'cornerstone-core'
import metaDataProvider from '@/utils/metaDataProvider'
import metaDataProvider, { registerImageSharedCacheKey, unregisterImageSharedCacheKeys } from '@/utils/metaDataProvider'
import * as cornerstoneMath from 'cornerstone-math'
import * as cornerstoneTools from 'cornerstone-tools'
@ -473,6 +473,7 @@ export default {
},
beforeDestroy() {
// Store.$off('getMeasureData')
unregisterImageSharedCacheKeys(this.stack?.imageIds)
DicomEvent.$off('setReadingState')
DicomEvent.$off('updateImage')
},
@ -1186,6 +1187,7 @@ export default {
this.resetHideMeasureArr()
}
const { image } = e.detail
registerImageSharedCacheKey(image?.imageId, image?.sharedCacheKey)
const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager
const imageId = image.imageId
ToolStateManager.clearImageIdToolState(imageId)

View File

@ -411,7 +411,7 @@
<WL v-if="activeName === '2'" @getWwcTpl="getWwcTpl" />
</el-tab-pane>
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="activeName === '3'" :imageToolType="1"/>
<Others v-if="activeName === '3'" :imageToolType="1" />
</el-tab-pane>
</el-tabs>
</el-dialog>
@ -448,10 +448,10 @@
/> -->
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :SubjectId="uploadSubjectId" :SubjectCode="uploadSubjectCode"
:Criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible" :VisitTaskId="taskId"
:isReadingTaskViewInOrder="isReadingTaskViewInOrder" />
:isReadingTaskViewInOrder="isReadingTaskViewInOrder" :isReading="true" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :SubjectId="uploadSubjectId"
:SubjectCode="uploadSubjectCode" :Criterion="uploadTrialCriterion" :TaskId="taskId"
:visible.sync="downloadImageVisible" />
:visible.sync="downloadImageVisible" :isReading="true"/>
</div>
</template>
<script>

View File

@ -147,7 +147,7 @@
</template>
</el-table-column>
</el-table>
<div style="text-align:right;margin-top: 10px;">
<div style="text-align:right;margin-top: 10px;" v-if="!IsViewStudyPart">
<!-- 确认 -->
<el-button type="primary" @click="handleConfirmBP">{{ $t('trials:reading:button:confirm')
}}</el-button>
@ -223,7 +223,8 @@ export default {
readingVersionEnum: null,
dialogBodyPartVisible: false,
isReadKeyFile: true,
isHaveKeyFile: false
isHaveKeyFile: false,
IsViewStudyPart: true
}
},
computed: {

View File

@ -3723,7 +3723,7 @@ export default {
})
this.setToolsPassive()
},
async activeSeries(obj) {
async activeSeries(obj, isSave = false) {
if (this.readingTool === 3) {
let res = await this.changeScreenSave()
if (!res) return false
@ -3735,6 +3735,7 @@ export default {
if (!confirm) return false
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].removeAllNoSaveAnnotation()
} else {
if (isSave) return false
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
}
@ -4022,7 +4023,7 @@ export default {
}
let res = await this.openMPRViewport(series)
if (!res) {
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex, true)
setTimeout(async () => {
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
@ -4534,7 +4535,7 @@ export default {
return new Promise(async (resolve, reject) => {
this.setToolsPassive()
if (this.isMPR) {
this.activeSeries(this.$refs[`viewport-MPR-0`][0].series)
this.activeSeries(this.$refs[`viewport-MPR-0`][0].series, true)
resolve(false)
if (!data) return resolve(false)
let viewportSeries = this.$refs[`viewport-MPR-0`][0].series

View File

@ -804,12 +804,16 @@ export default {
toolGroup.setToolPassive(this.activeTool)
}
this.$emit('update:activeTool', '')
} else if (this.activeTool === name) {
toolGroup.setToolPassive(this.ThresholdTools[1])
this.$emit('update:activeTool', '')
} else {
if (this.activeTool) {
if (toolName === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
} else {
toolGroup.setToolPassive(this.activeTool)
toolGroup.setToolPassive(this.ThresholdTools[1])
}
}
toolGroup.setToolActive(toolName, {

View File

@ -14,7 +14,7 @@
<div class="ps">
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<template slot="title">
<div v-if="!study.IsCriticalSequence" class="dicom-desc">
<div style="text-overflow: ellipsis;overflow: hidden;">
@ -24,7 +24,8 @@
</div>
<div>
<span class="study-meta-line" :title="study.Modalities">
<span v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder !== 0" class="study-code" :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder !== 0" class="study-code"
:title="study.StudyCode">{{ study.StudyCode }}</span>
<span class="study-modality">{{ `${study.Modalities}(${study.SeriesCount})` }}</span>
<span class="patient-info" v-if="['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities)">
<el-popover placement="right-start" trigger="hover" popper-class="patient-info-popper">
@ -81,8 +82,7 @@
style="position: absolute;right: 0;top: 0;">
<el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
<div class="frame_list">
<div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id"
class="frame_content"
<div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id" class="frame_content"
:class="{ 'frame_content_active': activeInstanceId === instance.Id }"
:style="{ 'margin-bottom': idx < series.InstanceInfoList.length - 1 ? '5px' : '0px' }"
@click.stop="showMultiFrames(index, series, i, instance)">
@ -130,7 +130,7 @@
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
@ -205,18 +205,19 @@ export default {
this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex
this.activeInstanceId = null
},
},
activeStudy(id) {
console.log('activeStudy')
if (this.activeNames.indexOf(id) > -1) return
this.activeNames.push(id)
},
setSeriesActive(studyIndex, seriesIndex) {
setSeriesActive(studyIndex, seriesIndex, isSave = false) {
this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex
const studyId = this.studyList[studyIndex].StudyId
if (!studyId) return
this.activeStudy(studyId)
if (isSave) return false
DicomEvent.$emit('activeSeries', this.studyList[studyIndex].SeriesList[this.activeSeriesIndex])
},
showMultiFrames(studyIndex, series, seriesIndex, instance) {
@ -482,25 +483,29 @@ export default {
<style lang="scss">
.study-wrapper {
.instance_frame_wrapper{
.instance_frame_wrapper {
min-width: 120px;
background-color: #2c2c2c;
border: 1px solid #2c2c2c;
padding: 5px;
}
.frame_list{
.frame_list {
max-height: 500px;
overflow-y: auto;
}
.instance_frame_wrapper ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.frame_content{
.frame_content {
height: 50px;
padding: 5px;
display: flex;
@ -509,6 +514,7 @@ export default {
font-size: 12px;
border: 1px solid #404040;
}
.frame_content:hover {
/* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@ -517,10 +523,10 @@ export default {
border-color: #213a54 !important;
background-color: #213a54;
}
.frame_content_active {
border-color: #213a54 !important;
background-color: #213a54;
}
}
</style>

View File

@ -88,21 +88,21 @@
: $t('trials:pendingReadingTasks:button:review')
" icon="el-icon-edit-outline" @click="handleReadImage(scope.row)" />
<!-- 上传 -->
<el-button v-if="
<!-- <el-button v-if="
item.ImageUploadEnum > 0 &&
item.IsReadingTaskViewInOrder > 1 &&
!scope.row.IsSubjectJudge
" v-hasPermi="['role:ir']" circle icon="el-icon-upload2"
:title="$t('trials:pendingReadingTasks:button:upload')"
@click="openUploadImage(scope.row, item, 'upload')" />
@click="openUploadImage(scope.row, item, 'upload')" /> -->
<!-- 下载 -->
<el-button v-if="
<!-- <el-button v-if="
item.ImageDownloadEnum > 0 &&
item.IsReadingTaskViewInOrder > 1 &&
!scope.row.IsSubjectJudge
" v-hasPermi="['role:ir']" circle icon="el-icon-download"
:title="$t('trials:pendingReadingTasks:button:download')"
@click="openUploadImage(scope.row, item, 'download')" />
@click="openUploadImage(scope.row, item, 'download')" /> -->
</template>
</el-table-column>
</el-table>

View File

@ -1,58 +1,35 @@
<template>
<el-form
ref="subjectForm"
v-loading="loading"
:model="form"
:rules="rules"
size="small"
label-width="200px"
>
<el-form ref="subjectForm" v-loading="loading" :model="form" :rules="rules" size="small" label-width="200px">
<div class="base-dialog-body">
<!-- Site -->
<el-form-item :label="$t('trials:subject:table:site')" prop="TrialSiteId">
<el-select
v-model="form.TrialSiteId"
clearable
filterable
style="width:100%"
:disabled="form.StudyCount>0 || isEdit"
>
<el-option
v-for="item of siteOptions"
:key="item.Id"
:label="item.TrialSiteCode"
:value="item.TrialSiteId"
/>
<el-select v-model="form.TrialSiteId" clearable filterable style="width:100%"
:disabled="form.StudyCount > 0 || isEdit">
<el-option v-for="item of siteOptions" :key="item.Id" :label="item.TrialSiteCode" :value="item.TrialSiteId" />
</el-select>
</el-form-item>
<!-- 受试者编号 -->
<el-form-item :label="$t('trials:subject:table:subjectId')" prop="Code">
<el-input v-model="form.Code" :disabled="form.StudyCount > 0 || isEdit" />
<span v-show="otherInfo.IsNoticeSubjectCodeRule" style="color:#F56C6C;font-size:12px;">{{ otherInfo.SubjectCodeRule }}</span>
<el-input v-model="form.Code" :disabled="form.StudyCount > 0 || isEdit" @input="
form.Code =
form.Code.replace(/[^0-9a-zA-Z\_\-]/g, '')
" />
<span v-show="otherInfo.IsNoticeSubjectCodeRule" style="color:#F56C6C;font-size:12px;">{{
otherInfo.SubjectCodeRule }}</span>
</el-form-item>
<!-- 受试者第二编号 -->
<el-form-item v-if="otherInfo.IsSubjectSecondCodeView" :label="$t('trials:subject:table:medicalNo')" prop="MedicalNo">
<el-form-item v-if="otherInfo.IsSubjectSecondCodeView" :label="$t('trials:subject:table:medicalNo')"
prop="MedicalNo">
<el-input v-model="form.MedicalNo" :disabled="form.StudyCount > 0 || isEdit" />
</el-form-item>
<!-- 访视基准日期 -->
<el-form-item
v-show="otherInfo.IsHaveFirstGiveMedicineDate"
:label="$t('trials:subject:table:firstGiveMedicineTime')"
>
<el-date-picker
v-model="form.FirstGiveMedicineTime"
type="date"
:picker-options="pickerOption"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
/>
<el-form-item v-show="otherInfo.IsHaveFirstGiveMedicineDate"
:label="$t('trials:subject:table:firstGiveMedicineTime')">
<el-date-picker v-model="form.FirstGiveMedicineTime" type="date" :picker-options="pickerOption"
value-format="yyyy-MM-dd" format="yyyy-MM-dd" />
</el-form-item>
<!-- Age -->
<el-form-item
v-if="otherInfo.IsHaveSubjectAge"
:label="$t('trials:subject:table:age')"
prop="Age"
>
<el-form-item v-if="otherInfo.IsHaveSubjectAge" :label="$t('trials:subject:table:age')" prop="Age">
<el-input-number v-model="form.Age" controls-position="right" :min="0" />
</el-form-item>
<!-- Gender -->
@ -73,12 +50,7 @@
<div class="base-dialog-footer" style="text-align:right;margin-top:10px;">
<el-form-item>
<!-- 取消 -->
<el-button
:disabled="btnLoading"
size="small"
type="primary"
@click="handleCancel"
>
<el-button :disabled="btnLoading" size="small" type="primary" @click="handleCancel">
{{ $t('common:button:cancel') }}
</el-button>
<!-- 保存 -->

View File

@ -644,7 +644,7 @@
<el-input v-model="studyForm.Modalities" disabled />
</el-form-item>
<!-- 检查类型 -->
<el-form-item v-else :label="$t('trials:audit:table:modality')" prop="Modalities" :rules="[
<el-form-item v-else :label="$t('trials:audit:table:modality')" prop="Modality" :rules="[
{
required: true,
message: $t('common:ruleMessage:specify'),