Merge branch 'main' into uat_us

# Conflicts:
#	src/views/trials/trials-layout/components/trialsNavbar.vue
uat_us
wangxiaoshuang 2026-04-01 16:18:38 +08:00
commit 0f19a612b7
108 changed files with 11867 additions and 11055 deletions

View File

@ -1,4 +1,39 @@
kind: pipeline
type: ssh
name: ssh-linux-uat-irc-publish
platform:
os: Linux
arch: 386
clone:
disable: true #禁用默认克隆
server:
host: 101.132.253.119
user: root
password:
from_secret: local_pwd
steps:
- name: publish-uat-irc-web
commands:
- bash /opt/1panel/xc-deploy-new/devops-center/build-vue-then-publish.sh IRC_Web uat --mode uat
- name: notify-wecom
commands:
- echo $DRONE_COMMIT_AUTHOR "$DRONE_COMMIT_MESSAGE"
- bash /opt/1panel/xc-deploy-new/devops-center/drone-notify-wecom.sh "$DRONE_BUILD_STATUS" "$DRONE_REPO_NAME" "$DRONE_BRANCH" "$DRONE_BUILD_NUMBER" "4355b98e-1e72-4678-8dfb-2fc6ad0bf449" "$DRONE_COMMIT_MESSAGE" "$DRONE_COMMIT_AUTHOR" "Uat_IRC_WEB" "irc.uat.extimaging.com"
when:
status:
- success
- failure
trigger:
branch:
- uat
---
kind: pipeline kind: pipeline
type: ssh type: ssh
name: ssh-linux-test-irc-publish name: ssh-linux-test-irc-publish
@ -30,9 +65,6 @@ steps:
- failure - failure
trigger: trigger:
branch: branch:
- main - main

10997
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,18 +16,17 @@
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.701.0", "@aws-sdk/client-s3": "^3.701.0",
"@cornerstonejs/adapters": "^2.19.7", "@cornerstonejs/adapters": "4.19.2",
"@cornerstonejs/calculate-suv": "^1.1.0", "@cornerstonejs/calculate-suv": "^1.1.0",
"@cornerstonejs/core": "^2.19.7", "@cornerstonejs/core": "^4.19.2",
"@cornerstonejs/dicom-image-loader": "^2.19.7", "@cornerstonejs/dicom-image-loader": "^4.19.2",
"@cornerstonejs/tools": "^2.19.7", "@cornerstonejs/tools": "^4.19.2",
"@fingerprintjs/fingerprintjs": "^4.6.2", "@fingerprintjs/fingerprintjs": "^4.6.2",
"@icr/polyseg-wasm": "^0.4.0", "@icr/polyseg-wasm": "^0.4.0",
"@microsoft/signalr": "^8.0.7", "@microsoft/signalr": "^8.0.7",
"@riophae/vue-treeselect": "^0.4.0", "@riophae/vue-treeselect": "^0.4.0",
"@vue-office/docx": "^1.6.2", "@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.11", "@vue-office/excel": "^1.7.11",
"@vue/composition-api": "^1.7.2",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^0.18.1", "axios": "^0.18.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
@ -61,11 +60,10 @@
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.5", "sortablejs": "^1.15.5",
"streamsaver": "^2.0.6", "streamsaver": "^2.0.6",
"svg-sprite-loader": "^4.1.3",
"svgo": "^1.2.2", "svgo": "^1.2.2",
"v-viewer": "^1.7.4", "v-viewer": "^1.7.4",
"vcrontab": "^0.3.5", "vcrontab": "^0.3.5",
"vue": "^2.6.14", "vue": "2.7.16",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-contextmenujs": "^1.4.11", "vue-contextmenujs": "^1.4.11",
"vue-count-to": "^1.0.13", "vue-count-to": "^1.0.13",
@ -99,9 +97,10 @@
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"process": "^0.11.10", "process": "^0.11.10",
"sass": "^1.63.2", "sass": "^1.63.2",
"sass-loader": "^10.4.1", "sass-loader": "^16.0.7",
"svg-sprite-loader": "^6.0.11",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"vue-template-compiler": "^2.6.14", "vue-template-compiler": "2.7.16",
"webpack": "^5.96.1", "webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2" "webpack-bundle-analyzer": "^4.10.2"
}, },
@ -126,4 +125,4 @@
"not dead", "not dead",
"not op_mini all" "not op_mini all"
] ]
} }

View File

@ -115,8 +115,7 @@ export default {
} }
}, },
mounted() { mounted() {
// this.show = process.env.VUE_APP_OSS_PATH === '/test/dist' this.show = process.env.VUE_APP_OSS_PATH === '/test/dist'
this.show = false
Vue.prototype.$openI18n = this.openI18n Vue.prototype.$openI18n = this.openI18n
}, },
// watch: { // watch: {

View File

@ -319,3 +319,92 @@ export function getReportsChartData(param) {
data: param data: param
}) })
} }
// 分割相关
// 获取分割组
export function getSegmentationList(param) {
return request({
url: `/Segmentation/getSegmentationList`,
method: 'post',
data: param
})
}
// 新增修改分割组
export function addOrUpdateSegmentation(param) {
return request({
url: `/Segmentation/addOrUpdateSegmentation`,
method: 'post',
data: param
})
}
// 删除分割组
export function deleteSegmentation(segmentationId) {
return request({
url: `/Segmentation/deleteSegmentation/${segmentationId}`,
method: 'delete'
})
}
// 获取分割
export function getSegmentList(param) {
return request({
url: `/Segmentation/getSegmentList`,
method: 'post',
data: param
})
}
// 新增修改分割
export function addOrUpdateSegment(param) {
return request({
url: `/Segmentation/addOrUpdateSegment`,
method: 'post',
data: param
})
}
// 删除分割
export function deleteSegment(segmentId) {
return request({
url: `/Segmentation/deleteSegment/${segmentId}`,
method: 'delete'
})
}
// 获取分割绑定关系
export function getSegmentBindingList(param) {
return request({
url: `/Segmentation/getSegmentBindingList`,
method: 'post',
data: param
})
}
// 保存分割绑定关系(可用作修改删除)
export function saveSegmentBindingAndAnswer(param) {
return request({
url: `/Segmentation/saveSegmentBindingAndAnswer`,
method: 'post',
data: param
})
}
// 获取表格问题配置
export function getReadingTableQuestionTrialById(params) {
return request({
url: `/ReadingQuestion/getReadingTableQuestionTrialById`,
method: 'post',
params
})
}
// 获取外层问题配置
export function getReadingQuestionTrialById(params) {
return request({
url: `/ReadingQuestion/getReadingQuestionTrialById`,
method: 'post',
params
})
}
// 修改分割组保存状态
export function changeSegmentationSavedStatus(data) {
return request({
url: `/Segmentation/changeSegmentationSavedStatus`,
method: 'post',
data
})
}

View File

@ -74,10 +74,11 @@ export default {
zzSessionStorage.setItem('lastWorkbench', `${v.path}${query ? '?' : ''}${query}`) zzSessionStorage.setItem('lastWorkbench', `${v.path}${query ? '?' : ''}${query}`)
var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0] var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0]
if (this.trialsTab === '/trials/trials-panel') { if (this.trialsTab === '/trials/trials-panel') {
this.$router.replace({ path: `${firstGoIn.path}${query ? '?' : ''}${query}` }) var targetPath = firstGoIn.path
if (firstGoIn.children && firstGoIn.children.length > 0) { if (firstGoIn.children && firstGoIn.children.length > 0) {
this.$router.replace({ path: `${firstGoIn.children[0].path}${query ? '?' : ''}${query}` }) targetPath = firstGoIn.children[0].path
} }
this.$router.replace({ path: `${targetPath}${query ? '?' : ''}${query}` })
} }
// document.cookie = 'TrialId=' + this.$route.query.trialId + ';path=/' // document.cookie = 'TrialId=' + this.$route.query.trialId + ';path=/'
}, },
@ -112,10 +113,11 @@ export default {
this.getTrialList() this.getTrialList()
var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0] var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0]
if (this.trialsTab === '/trials/trials-panel') { if (this.trialsTab === '/trials/trials-panel') {
this.$router.replace({ path: `${firstGoIn.path}${query ? '?' : ''}${query}` }) var targetPath = firstGoIn.path
if (firstGoIn.children && firstGoIn.children.length > 0) { if (firstGoIn.children && firstGoIn.children.length > 0) {
this.$router.replace({ path: `${firstGoIn.children[0].path}${query ? '?' : ''}${query}` }) targetPath = firstGoIn.children[0].path
} }
this.$router.replace({ path: `${targetPath}${query ? '?' : ''}${query}` })
} }
}, },
methods: { methods: {
@ -201,15 +203,19 @@ export default {
if (~url.indexOf('?')) { if (~url.indexOf('?')) {
query = url.split('?')[1] query = url.split('?')[1]
} }
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` }) var trialsPanelList = this.trialsRouter.children.find(item => { return item.name === 'TrialsPanel' }).children
var trialsPanelList = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children var currentRoute = trialsPanelList.find(c => { return c.path === v.name })
var isHasChild = trialsPanelList.find(c => { return c.path === v.name }) && trialsPanelList.find(c => { return c.path === v.name }).children
if (trialsPanelList.find(c => { return c.path === v.name }) && trialsPanelList.find(c => { return c.path === v.name }).tabHiddn) { if (currentRoute && currentRoute.tabHiddn) {
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` })
return return
} }
var isHasChild = currentRoute && currentRoute.children
if (isHasChild && isHasChild.length > 0) { if (isHasChild && isHasChild.length > 0) {
this.trialsTabChild = isHasChild[0].path this.trialsTabChild = isHasChild[0].path
this.$router.push({ path: `${this.trialsTabChild}${query ? '?' : ''}${query}` }) this.$router.push({ path: `${this.trialsTabChild}${query ? '?' : ''}${query}` })
} else {
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` })
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)

View File

@ -116,7 +116,7 @@ export default {
display: none !important; /* 隐藏原生 radio 输入,但仍然允许交互 */ display: none !important; /* 隐藏原生 radio 输入,但仍然允许交互 */
} }
::v-deep.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) ::v-deep .el-radio:focus:not(.is-focus):not(:active):not(.is-disabled)
.el-radio__inner { .el-radio__inner {
box-shadow: none !important; box-shadow: none !important;
} }

View File

@ -712,12 +712,12 @@ export default {
var studyUid = data.string('x0020000d') var studyUid = data.string('x0020000d')
if (!studyUid) return resolve() if (!studyUid) return resolve()
var pixelDataElement = data.elements.x7fe00010 var pixelDataElement = data.elements.x7fe00010
if (!pixelDataElement && modality !== 'SR') return resolve() if (!pixelDataElement && modality !== 'SR' && modality !== 'ECG') return resolve()
var studyIndex = 0 var studyIndex = 0
while ( while (
studyIndex < scope.uploadQueues.length && studyIndex < scope.uploadQueues.length &&
scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid && scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid &&
(pixelDataElement || modality === 'SR') && (pixelDataElement || modality === 'SR' || modality === 'ECG') &&
modality != '' modality != ''
) { ) {
++studyIndex ++studyIndex

View File

@ -86,16 +86,16 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep.el-tabs--left .el-tabs__header.is-left { ::v-deep .el-tabs--left .el-tabs__header.is-left {
margin-right: 0; margin-right: 0;
} }
.uploadDicomAndNonedicom { .uploadDicomAndNonedicom {
::v-deep.el-tabs--border-card>.el-tabs__header .el-tabs__item { ::v-deep .el-tabs--border-card>.el-tabs__header .el-tabs__item {
color: #909399; color: #909399;
} }
::v-deep.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active { ::v-deep .el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active {
color: #428bca; color: #428bca;
background-color: #fff; background-color: #fff;
border-right-color: #dcdfe6; border-right-color: #dcdfe6;

View File

@ -0,0 +1 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" class="h-6 w-6"><g id="tool-seg-brush" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><rect id="Rectangle" x="0" y="0" width="24" height="24"></rect><path d="M3.24640621,21.8286833 C3.09173375,21.7551472 2.99513748,21.5971513 3.00018895,21.4259625 C3.00524042,21.2547737 3.11098486,21.1027485 3.26972426,21.0384606 C5.3260304,20.2059201 4.66362518,17.8620247 5.27421252,16.6088957 C6.02197747,15.1026514 7.84114758,14.4766383 9.35746132,15.2037675 C13.9485253,17.4422999 8.48346644,24.3211232 3.24640621,21.8286833 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M21.5968442,2.302022 C21.1256527,1.8826075 20.4094461,1.90228142 19.9619901,2.34693083 L9.69255027,12.5887345 C11.0536437,13.0051578 12.1843437,13.9616637 12.8206229,15.2349008 L21.7410706,3.93687606 C22.1347378,3.44008272 22.0714081,2.72222021 21.5968442,2.302022 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

12
src/icons/svg/contour.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="h-6 w-6" color="#fff">
<path
d="M10.3945 12.6113C10.043 12.6113 9.78516 12.3945 9.78516 12.0371C9.78516 11.6855 10.043 11.4688 10.3945 11.4688H13.2012C13.5527 11.4688 13.8164 11.6855 13.8164 12.0371C13.8164 12.3945 13.5527 12.6113 13.2012 12.6113H10.3945Z"
fill="currentColor"></path>
<path opacity="0.5"
d="M18.312 6.84473C21.8473 8.88583 22.9225 13.6424 20.7134 17.4687C18.5042 21.2951 13.8473 22.7422 10.312 20.7011C9.51661 20.2419 8.84737 19.6439 8.31042 18.9512C11.3012 19.3598 14.4827 17.8251 16.1946 14.8598C17.9067 11.8945 17.6457 8.3712 15.7963 5.98538C16.6645 6.10409 17.5167 6.38556 18.312 6.84473Z"
fill="currentColor"></path>
<circle cx="13.7852" cy="13.4688" r="8" stroke="currentColor"></circle>
<path
d="M4.50586 8.22266C4.14844 8.22266 3.92578 7.9707 3.92578 7.60742V5.68555H2.10352C1.74609 5.68555 1.5 5.46875 1.5 5.11719C1.5 4.76562 1.74609 4.54297 2.10352 4.54297H3.92578V2.61523C3.92578 2.25195 4.14844 2 4.50586 2C4.85742 2 5.08008 2.25195 5.08008 2.61523V4.54297H6.89648C7.25391 4.54297 7.50586 4.76562 7.50586 5.11719C7.50586 5.46875 7.25391 5.68555 6.89648 5.68555H5.08008V7.60742C5.08008 7.9707 4.85742 8.22266 4.50586 8.22266Z"
fill="currentColor"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
src/icons/svg/fill.svg Normal file
View File

@ -0,0 +1 @@
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" fill="currentColor" x="2" y="2" width="14" height="14" rx="1"></rect></g></g></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@ -0,0 +1 @@
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-outline-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" stroke="currentColor" x="1.5" y="1.5" width="15" height="15" rx="1"></rect><rect id="Rectangle" fill="currentColor" x="3.5" y="3.5" width="11" height="11" rx="1"></rect></g></g></svg>

After

Width:  |  Height:  |  Size: 446 B

1
src/icons/svg/jumpto.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774331916694" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1627" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M198.4 960A134.4 134.4 0 0 1 64 825.6V198.4A134.4 134.4 0 0 1 198.4 64h268.8a44.8 44.8 0 0 1 44.8 44.8 44.8 44.8 0 0 1-44.8 44.8H198.4a44.8 44.8 0 0 0-44.8 44.8v627.2a44.8 44.8 0 0 0 44.8 44.8h627.2a44.8 44.8 0 0 0 44.8-44.8V556.8a44.8 44.8 0 0 1 44.8-44.8 44.8 44.8 0 0 1 44.8 44.8v268.8A134.4 134.4 0 0 1 825.6 960z m281.984-416.384a44.8 44.8 0 0 1 0-63.296L807.232 153.6H646.464a44.8 44.8 0 0 1-44.8-44.8 44.8 44.8 0 0 1 44.8-44.8h267.264a44.8 44.8 0 0 1 28.288 8.896l1.408 1.152 0.64 0.512 1.024 0.96a41.216 41.216 0 0 1 3.136 3.072l0.896 1.088 0.512 0.64 1.152 1.344a44.8 44.8 0 0 1 8.832 28.288V377.28a44.8 44.8 0 0 1-44.8 44.8 44.8 44.8 0 0 1-44.8-44.8V216.512l-326.4 326.4a44.8 44.8 0 0 1-32 13.248A44.8 44.8 0 0 1 480 543.616z" p-id="1628" fill="#2c2c2c"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-outline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" stroke="currentColor" x="1.5" y="1.5" width="15" height="15" rx="1"></rect></g></g></svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@ -0,0 +1 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" class="h-6 w-6"><g id="tool-seg-threshold" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><rect id="Rectangle" x="0" y="0" width="24" height="24"></rect><g id="Group" transform="translate(2.4184, 14.3673)" stroke="currentColor" stroke-width="1.5"><line x1="16.5816327" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><line x1="0.0816326531" y1="2.13265306" x2="12.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><circle id="Oval" cx="14.2244898" cy="2.09183673" r="2.09183673"></circle></g><g id="Group" transform="translate(11.7092, 7.4592) scale(-1, 1) translate(-11.7092, -7.4592)translate(1.9184, 5.3673)" stroke="currentColor" stroke-width="1.5"><line x1="16" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><line x1="0.0816326531" y1="2.13265306" x2="11" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><circle id="Oval" cx="13.2244898" cy="2.09183673" r="2.09183673"></circle></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -56,8 +56,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "~@/styles/mixin.scss"; @use "@/styles/mixin.scss" as *;
@import "~@/styles/variables.module.scss"; @use "@/styles/variables.module.scss" as *;
// @import "~@/styles/mixin.scss";
// @import "~@/styles/variables.module.scss";
.app-wrapper { .app-wrapper {
@include clearfix; @include clearfix;

View File

@ -156,12 +156,6 @@ export const constantRoutes = [
hidden: true, hidden: true,
component: () => import('@/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt') component: () => import('@/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt')
}, },
{
path: '/fusion',
name: 'fusion',
hidden: true,
component: () => import('@/views/trials/trials-panel/reading/dicoms/components/Fusion/demo/index')
},
{ {
path: '/historyScreenshot', path: '/historyScreenshot',

View File

@ -1,15 +1,16 @@
@import "./variables.module.scss"; @use "./variables.module.scss";
@import "./mixin.scss"; @use "./mixin.scss";
@import "./transition.scss"; @use "./transition.scss";
@import "./element-ui.scss"; @use "./element-ui.scss";
@import "./sidebar.scss"; @use "./sidebar.scss";
body { body {
height: 100%; height: 100%;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
// font-family:"Helvetica Neue", Helvetica, Arial, sans-serif; // font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
// font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", // font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
// "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, // "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial,
@ -205,133 +206,139 @@ body .el-table th.gutter {
.el-descriptions { .el-descriptions {
box-sizing: border-box; box-sizing: border-box;
font-size: 14px; font-size: 14px;
color: #303133 color: #303133;
} }
.el-descriptions__header { .el-descriptions__header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px margin-bottom: 20px;
} }
.el-descriptions__title { .el-descriptions__title {
font-size: 16px; font-size: 16px;
font-weight: 700 font-weight: 700;
} }
.el-descriptions__body { .el-descriptions__body {
color: #606266; color: #606266;
background-color: #fff background-color: #fff;
} }
.el-descriptions__body .el-descriptions__table { .el-descriptions__body .el-descriptions__table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
table-layout: fixed table-layout: fixed;
} }
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell { .el-descriptions__body .el-descriptions__table .el-descriptions-item__cell {
box-sizing: border-box; box-sizing: border-box;
text-align: left; text-align: left;
font-weight: 400; font-weight: 400;
line-height: 1.5 line-height: 1.5;
} }
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-left { .el-descriptions__body
text-align: left .el-descriptions__table
.el-descriptions-item__cell.is-left {
text-align: left;
} }
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-center { .el-descriptions__body
text-align: center .el-descriptions__table
.el-descriptions-item__cell.is-center {
text-align: center;
} }
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-right { .el-descriptions__body
text-align: right .el-descriptions__table
.el-descriptions-item__cell.is-right {
text-align: right;
} }
.el-descriptions .is-bordered { .el-descriptions .is-bordered {
table-layout: auto table-layout: auto;
} }
.el-descriptions .is-bordered .el-descriptions-item__cell { .el-descriptions .is-bordered .el-descriptions-item__cell {
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
padding: 12px 10px padding: 12px 10px;
} }
.el-descriptions :not(.is-bordered) .el-descriptions-item__cell { .el-descriptions :not(.is-bordered) .el-descriptions-item__cell {
padding-bottom: 12px padding-bottom: 12px;
} }
.el-descriptions--medium.is-bordered .el-descriptions-item__cell { .el-descriptions--medium.is-bordered .el-descriptions-item__cell {
padding: 10px padding: 10px;
} }
.el-descriptions--medium:not(.is-bordered) .el-descriptions-item__cell { .el-descriptions--medium:not(.is-bordered) .el-descriptions-item__cell {
padding-bottom: 10px padding-bottom: 10px;
} }
.el-descriptions--small { .el-descriptions--small {
font-size: 12px font-size: 12px;
} }
.el-descriptions--small.is-bordered .el-descriptions-item__cell { .el-descriptions--small.is-bordered .el-descriptions-item__cell {
padding: 8px 10px padding: 8px 10px;
} }
.el-descriptions--small:not(.is-bordered) .el-descriptions-item__cell { .el-descriptions--small:not(.is-bordered) .el-descriptions-item__cell {
padding-bottom: 8px padding-bottom: 8px;
} }
.el-descriptions--mini { .el-descriptions--mini {
font-size: 12px font-size: 12px;
} }
.el-descriptions--mini.is-bordered .el-descriptions-item__cell { .el-descriptions--mini.is-bordered .el-descriptions-item__cell {
padding: 6px 10px padding: 6px 10px;
} }
.el-descriptions--mini:not(.is-bordered) .el-descriptions-item__cell { .el-descriptions--mini:not(.is-bordered) .el-descriptions-item__cell {
padding-bottom: 6px padding-bottom: 6px;
} }
.el-descriptions-item { .el-descriptions-item {
vertical-align: top vertical-align: top;
} }
.el-descriptions-item__container { .el-descriptions-item__container {
display: flex display: flex;
} }
.el-descriptions-item__container .el-descriptions-item__content, .el-descriptions-item__container .el-descriptions-item__content,
.el-descriptions-item__container .el-descriptions-item__label { .el-descriptions-item__container .el-descriptions-item__label {
display: inline-flex; display: inline-flex;
align-items: baseline align-items: baseline;
} }
.el-descriptions-item__container .el-descriptions-item__content { .el-descriptions-item__container .el-descriptions-item__content {
flex: 1 flex: 1;
} }
.el-descriptions-item__label.has-colon:after { .el-descriptions-item__label.has-colon:after {
content: ":"; content: ":";
position: relative; position: relative;
top: -.5px top: -0.5px;
} }
.el-descriptions-item__label.is-bordered-label { .el-descriptions-item__label.is-bordered-label {
font-weight: 700; font-weight: 700;
color: #909399; color: #909399;
background: #fafafa background: #fafafa;
} }
.el-descriptions-item__label:not(.is-bordered-label) { .el-descriptions-item__label:not(.is-bordered-label) {
margin-right: 10px margin-right: 10px;
} }
.el-descriptions-item__content { .el-descriptions-item__content {
word-break: break-word; word-break: break-word;
overflow-wrap: break-word overflow-wrap: break-word;
} }
.feedBack-box { .feedBack-box {
@ -350,7 +357,7 @@ body .el-table th.gutter {
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: .5; opacity: 0.5;
background: #000; background: #000;
z-index: 3999; z-index: 3999;
} }
@ -371,4 +378,4 @@ body .el-table th.gutter {
height: 20px !important; height: 20px !important;
vertical-align: -0.4em !important; vertical-align: -0.4em !important;
cursor: pointer; cursor: pointer;
} }

View File

@ -8366,7 +8366,7 @@
for (var i = 0; i < valarr.length; i++) { for (var i = 0; i < valarr.length; i++) {
var checkValue = valarr[i], var checkValue = valarr[i],
checklen = lengths[i], checklen = lengths[i] || 0,
isString = false, isString = false,
displaylen = checklen; displaylen = checklen;

View File

@ -0,0 +1,343 @@
import getNumberValues from "./getNumberValues";
import isNMReconstructable from "./isNMReconstructable";
/**
* Get a subpart of Image Type dicom tag defined by index
* @param {*} dataSet
* @param {*} index 0 based index of the subtype
*/
function getImageTypeSubItemFromDataset(dataSet, index) {
const imageType = dataSet.string("x00080008");
if (imageType) {
const subTypes = imageType.split("\\");
if (subTypes.length > index) {
return subTypes[index];
}
}
return undefined;
}
// DICOM 标准中针对 Enhanced CT/MR/PET 等“增强型多帧”Enhanced Multi-frame影像 强制要求为每一帧显式记录真实的独立空间坐标 。
// - 它们的数据通常存放在: Per-frame Functional Groups Sequence (5200,9230) -> 对应的 Frame Item -> Plane Position Sequence (0020,9113) -> ImagePositionPatient (0020,0032) 。
function getSharedFunctionalGroupsDataSet(dataSet) {
const seq = dataSet?.elements?.x52009229;
if (!seq?.items?.length) {
return;
}
return seq.items[0].dataSet;
}
function getPerFrameFunctionalGroupsDataSet(dataSet, frameIndex) {
const seq = dataSet?.elements?.x52009230;
if (!seq?.items?.length) {
return;
}
if (
!Number.isInteger(frameIndex) ||
frameIndex < 0 ||
frameIndex >= seq.items.length
) {
return;
}
return seq.items[frameIndex].dataSet;
}
function getOrientationFromPlaneOrientationSequence(dataSet) {
const seq = dataSet?.elements?.x00209116;
if (!seq?.items?.length) {
return;
}
return getNumberValues(seq.items[0].dataSet, "x00200037", 6);
}
function getPositionFromPlanePositionSequence(dataSet) {
const seq = dataSet?.elements?.x00209113;
if (!seq?.items?.length) {
return;
}
return getNumberValues(seq.items[0].dataSet, "x00200032", 3);
}
function getPixelSpacingFromPixelMeasuresSequence(dataSet) {
const seq = dataSet?.elements?.x00289110;
if (!seq?.items?.length) {
return;
}
return getNumberValues(seq.items[0].dataSet, "x00280030", 2);
}
function getSliceThicknessFromPixelMeasuresSequence(dataSet) {
const seq = dataSet?.elements?.x00289110;
if (!seq?.items?.length) {
return;
}
if (!seq.items[0]?.dataSet?.elements?.x00180050) {
return;
}
return seq.items[0].dataSet.floatString("x00180050");
}
/**
* Extracts the orientation from NM multiframe dataset, if image type
* equal to RECON TOMO or RECON GATED TOMO
* @param {*} dataSet
* @returns
*/
function extractOrientationFromNMMultiframeDataset(dataSet) {
let imageOrientationPatient;
const modality = dataSet.string("x00080060");
if (modality?.includes("NM")) {
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
if (imageSubType && isNMReconstructable(imageSubType)) {
if (dataSet.elements.x00540022) {
imageOrientationPatient = getNumberValues(
dataSet.elements.x00540022.items[0].dataSet,
"x00200037",
6
);
}
}
}
return imageOrientationPatient;
}
/**
* Extracts the position from NM multiframe dataset, if image type
* equal to RECON TOMO or RECON GATED TOMO
* @param {*} dataSet
* @returns
*/
function extractPositionFromNMMultiframeDataset(dataSet) {
let imagePositionPatient;
const modality = dataSet.string("x00080060");
if (modality?.includes("NM")) {
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
if (imageSubType && isNMReconstructable(imageSubType)) {
if (dataSet.elements.x00540022) {
imagePositionPatient = getNumberValues(
dataSet.elements.x00540022.items[0].dataSet,
"x00200032",
3
);
}
}
}
return imagePositionPatient;
}
function isMultiFrame(dataSet) {
const numberOfFrames =
dataSet.string("x00280008") || dataSet.uint16("x00280008");
return numberOfFrames !== undefined && Number(numberOfFrames) > 1;
}
/**
* Extract orientation information from a dataset. It tries to get the orientation
* from the Detector Information Sequence (for NM images) if image type equal
* to RECON TOMO or RECON GATED TOMO
* @param {*} dataSet
* @returns
*/
function extractOrientationFromDataset(dataSet) {
// let imageOrientationPatient;
// if (isMultiFrame(dataSet)) {
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
// dataSet,
// frameIndex
// );
// imageOrientationPatient =
// getOrientationFromPlaneOrientationSequence(perFrameDataSet);
// if (!imageOrientationPatient) {
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
// imageOrientationPatient =
// getOrientationFromPlaneOrientationSequence(sharedDataSet);
// }
// }
// if (!imageOrientationPatient) {
// imageOrientationPatient = getNumberValues(dataSet, "x00200037", 6);
// }
let imageOrientationPatient = getNumberValues(dataSet, "x00200037", 6)
// Trying to get the orientation from the Plane Orientation Sequence
if (!imageOrientationPatient && dataSet.elements.x00209116) {
imageOrientationPatient = getNumberValues(
dataSet.elements.x00209116.items[0].dataSet,
"x00200037",
6
);
}
// If orientation not valid to this point, trying to get the orientation
// from the Detector Information Sequence (for NM images) with image type
// equal to RECON TOMO or RECON GATED TOMO
if (!imageOrientationPatient) {
imageOrientationPatient =
extractOrientationFromNMMultiframeDataset(dataSet);
}
return imageOrientationPatient;
}
/**
* Extract position information from a dataset. It tries to get the position
* from the Detector Information Sequence (for NM images) if image type equal
* to RECON TOMO or RECON GATED TOMO
* @param {*} dataSet
* @returns
*/
function extractPositionFromDataset(dataSet) {
// let imagePositionPatient;
// if (isMultiFrame(dataSet)) {
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
// dataSet,
// frameIndex
// );
// imagePositionPatient =
// getPositionFromPlanePositionSequence(perFrameDataSet);
// if (!imagePositionPatient) {
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
// imagePositionPatient =
// getPositionFromPlanePositionSequence(sharedDataSet);
// }
// }
// if (!imagePositionPatient) {
// imagePositionPatient = getNumberValues(dataSet, "x00200032", 3);
// }
let imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
// Trying to get the position from the Plane Position Sequence
if (!imagePositionPatient && dataSet.elements.x00209113) {
imagePositionPatient = getNumberValues(
dataSet.elements.x00209113.items[0].dataSet,
"x00200032",
3
);
}
// If position not valid to this point, trying to get the position
// from the Detector Information Sequence (for NM images)
if (!imagePositionPatient) {
imagePositionPatient = extractPositionFromNMMultiframeDataset(dataSet);
}
return imagePositionPatient;
}
/**
* Extract the pixelSpacing information. If exists, extracts this information
* from Pixel Measures Sequence
* @param {*} dataSet
* @returns
*/
function extractSpacingFromDataset(dataSet) {
// let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
// // If pixelSpacing not valid to this point, trying to get the spacing
// // from the Pixel Measures Sequence
// if (!pixelSpacing && dataSet.elements.x00289110) {
// pixelSpacing = getNumberValues(
// dataSet.elements.x00289110.items[0].dataSet,
// 'x00280030',
// 2
// );
// }
// return pixelSpacing;
let rowPixelSpacing = null;
let columnPixelSpacing = null;
// let pixelSpacing;
// if (isMultiFrame(dataSet)) {
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
// dataSet,
// frameIndex
// );
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
// pixelSpacing =
// getPixelSpacingFromPixelMeasuresSequence(perFrameDataSet) ||
// getPixelSpacingFromPixelMeasuresSequence(sharedDataSet);
// }
// if (!pixelSpacing) {
// pixelSpacing =
// getNumberValues(dataSet, "x00280030", 2) ||
// getPixelSpacingFromPixelMeasuresSequence(dataSet);
// }
let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
const estimatedRadiographicMagnificationFactor = getNumberValues(
dataSet,
"x00181114",
2
);
if (pixelSpacing) {
rowPixelSpacing = pixelSpacing[0];
columnPixelSpacing = pixelSpacing[1];
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
rowPixelSpacing =
imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
columnPixelSpacing =
imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
rowPixelSpacing = imagePixelSpacing[0];
columnPixelSpacing = imagePixelSpacing[1];
}
return rowPixelSpacing !== null
? [rowPixelSpacing, columnPixelSpacing]
: undefined;
}
/**
* Extract the sliceThickness information. If exists, extracts this information
* from Pixel Measures Sequence
* @param {*} dataSet
* @returns
*/
function extractSliceThicknessFromDataset(dataSet) {
let sliceThickness;
if (dataSet.elements.x00180050) {
sliceThickness = dataSet.floatString('x00180050');
}
else if (dataSet.elements.x00289110 &&
dataSet.elements.x00289110.items.length &&
dataSet.elements.x00289110.items[0].dataSet.elements.x00180050) {
sliceThickness =
dataSet.elements.x00289110.items[0].dataSet.floatString('x00180050');
}
// if (sliceThickness === undefined && isMultiFrame(dataSet)) {
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
// dataSet,
// frameIndex
// );
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
// sliceThickness =
// getSliceThicknessFromPixelMeasuresSequence(perFrameDataSet) ||
// getSliceThicknessFromPixelMeasuresSequence(sharedDataSet);
// }
return sliceThickness;
}
export {
getImageTypeSubItemFromDataset,
extractOrientationFromDataset,
extractPositionFromDataset,
extractSpacingFromDataset,
extractSliceThicknessFromDataset,
};

View File

@ -0,0 +1,24 @@
function getNumberValues(
dataSet,
tag,
minimumLength
){
const values = [];
const valueAsString = dataSet.string(tag);
if (!valueAsString) {
return;
}
const split = valueAsString.split('\\');
if (minimumLength && split.length < minimumLength) {
return;
}
for (let i = 0; i < split.length; i++) {
values.push(parseFloat(split[i]));
}
return values;
}
export default getNumberValues;

View File

@ -0,0 +1,3 @@
export default function isNMReconstructable(imageSubType) {
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO';
}

View File

@ -1,4 +1,11 @@
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader' import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import {
getImageTypeSubItemFromDataset,
extractOrientationFromDataset,
extractPositionFromDataset,
extractSpacingFromDataset,
extractSliceThicknessFromDataset,
} from './extractPositioningFromDataset';
function parseImageId(imageId) { function parseImageId(imageId) {
// build a url by parsing out the url scheme and frame index from the imageId // build a url by parsing out the url scheme and frame index from the imageId
const firstColonIndex = imageId.indexOf(':'); const firstColonIndex = imageId.indexOf(':');
@ -141,6 +148,19 @@ function populatePaletteColorLut(dataSet, imagePixelModule) {
imagePixelModule.bluePaletteColorLookupTableDescriptor imagePixelModule.bluePaletteColorLookupTableDescriptor
); );
} }
function getSpacingBetweenSlices(dataSet) {
if (dataSet?.elements?.x00180088) {
return dataSet.floatString('x00180088');
}
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
if (
pixelMeasuresSequence?.items?.length &&
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
) {
return pixelMeasuresSequence.items[0].dataSet.floatString('x00180088');
}
}
function metaDataProvider(type, imageId) { function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId); const parsedImageId = parseImageId(imageId);
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url); const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
@ -149,10 +169,47 @@ function metaDataProvider(type, imageId) {
return; return;
} }
if (type === 'imagePlaneModule') { if (type === 'imagePlaneModule') {
const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6); // const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3); // const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2); // const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2); const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
const modality = dataSet.string("x00080060");
if (modality && modality.includes('NM') && parsedImageId.frame !== undefined && parsedImageId.frame > 1) {
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
const step = spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
if (imageOrientationPatient && imagePositionPatient && step !== undefined && frameIndex !== undefined) {
const rowCosines = [
parseFloat(imageOrientationPatient[0]),
parseFloat(imageOrientationPatient[1]),
parseFloat(imageOrientationPatient[2]),
];
const columnCosines = [
parseFloat(imageOrientationPatient[3]),
parseFloat(imageOrientationPatient[4]),
parseFloat(imageOrientationPatient[5]),
];
const normal = [
rowCosines[1] * columnCosines[2] - rowCosines[2] * columnCosines[1],
rowCosines[2] * columnCosines[0] - rowCosines[0] * columnCosines[2],
rowCosines[0] * columnCosines[1] - rowCosines[1] * columnCosines[0],
];
const offset = frameIndex * parseFloat(step);
imagePositionPatient = [
parseFloat(imagePositionPatient[0]) + normal[0] * offset,
parseFloat(imagePositionPatient[1]) + normal[1] * offset,
parseFloat(imagePositionPatient[2]) + normal[2] * offset,
];
}
}
const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2); const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
let columnPixelSpacing = null; let columnPixelSpacing = null;
@ -194,7 +251,7 @@ function metaDataProvider(type, imageId) {
rowCosines, rowCosines,
columnCosines, columnCosines,
imagePositionPatient, imagePositionPatient,
sliceThickness: dataSet.floatString('x00180050'), sliceThickness,
sliceLocation: dataSet.floatString('x00201041'), sliceLocation: dataSet.floatString('x00201041'),
pixelSpacing, pixelSpacing,
rowPixelSpacing, rowPixelSpacing,
@ -224,6 +281,11 @@ function metaDataProvider(type, imageId) {
} }
return imagePixelModule; return imagePixelModule;
} }
// if (type === "multiframeModule") {
// return {
// NumberOfFrames: dataSet.uint16("x00280008") || dataSet.string("x00280008")
// };
// }
// if (type === 'imagePixelModule') { // if (type === 'imagePixelModule') {
// return { // return {
// samplesPerPixel: dataSet.uint16('x00280002'), // samplesPerPixel: dataSet.uint16('x00280002'),
@ -249,4 +311,4 @@ function metaDataProvider(type, imageId) {
// } // }
// } // }
} }
export default metaDataProvider; export default metaDataProvider;

View File

@ -965,8 +965,8 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss">
.imageBox { .viewerContainer .imageBox {
position: relative; position: relative;
i { i {

View File

@ -158,108 +158,105 @@
<div class="viewerSidethumbinner"> <div class="viewerSidethumbinner">
<el-collapse v-model="relationActiveName" @change="handelRelationActiveChange"> <el-collapse v-model="relationActiveName" @change="handelRelationActiveChange">
<template v-for="item in relationStudyListByVisitName"> <div v-for="item in relationStudyListByVisitName" :key="`${item.VisitName}`">
<div :key="`${item.VisitName}`"> <div v-show="item.VisitName" class="text-desc" style="background-color: #1f1f1f;">
<div v-show="item.VisitName" class="text-desc" style="background-color: #1f1f1f;"> {{ item.VisitName }}
{{ item.VisitName }} </div>
</div> <template v-for="(study, studyIndex) in relationStudyList">
<template v-for="(study, studyIndex) in relationStudyList"> <el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`"
<el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`" v-if="study.VisitName === item.VisitName">
v-if="study.VisitName === item.VisitName"> <template slot="title">
<template slot="title">
<div class="text-desc"> <div class="text-desc">
{{ study.StudyCode }} {{ study.StudyCode }}
</div>
<!-- <div v-show="study.Description" class="text-desc">
{{ study.Description }}
</div> -->
<div v-show="study.SeriesCount" class="text-desc">
{{ study.Modalities }} : {{ study.SeriesCount }} Series
</div>
</template>
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
{{ study.Description }}
</div> </div>
<div class="viewerSidethumbs ps" style="position: relative;"> <!-- <div v-show="study.Description" class="text-desc">
<div class="viewerSidethumbinner"> {{ study.Description }}
<div v-for="(seriesItem, index) in study.seriesList" :key="seriesItem.seriesId"> </div> -->
<div class="viewernavigatorwrapper"
style="position: relative;border:1px solid #434343;" series-type="relation"
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
<div class="imageBox" style="width: 72px;height:72px;">
<img class="image-preview" :src="seriesItem.previewImageUrl"
crossorigin="anonymous" alt="" style="width: 72px;height:72px;" fit="fill" />
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
@click.stop="refreshImage(item)"></i>
</div>
<div class="viewernavitextwrapper"> <div v-show="study.SeriesCount" class="text-desc">
<div {{ study.Modalities }} : {{ study.SeriesCount }} Series
style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;"> </div>
<div v-if="seriesItem.keySeries" style="color:red"> </template>
Key Images <div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
</div> {{ study.Description }}
<div v-else>#{{ seriesItem.seriesNumber }}</div> </div>
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1"> <div class="viewerSidethumbs ps" style="position: relative;">
<el-popover placement="right-start" trigger="click" <div class="viewerSidethumbinner">
popper-class="instance_frame_wrapper"> <div v-for="(seriesItem, index) in study.seriesList" :key="seriesItem.seriesId">
<div class="frame_list"> <div class="viewernavigatorwrapper"
<div v-for="(instance, idx) in seriesItem.instanceInfoList" style="position: relative;border:1px solid #434343;" series-type="relation"
:key="instance.Id" class="frame_content" @click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }" <div class="imageBox" style="width: 72px;height:72px;">
@click="showMultiFrames(studyIndex, seriesItem, index, instance)"> <img class="image-preview" :src="seriesItem.previewImageUrl"
<div> crossorigin="anonymous" alt="" style="width: 72px;height:72px;" fit="fill" />
<div>{{ instance.InstanceNumber }}</div> <i class="el-icon-refresh" :title="$t('tip:refreshImage')"
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1} @click.stop="refreshImage(item)"></i>
frame` </div>
}}</div>
</div>
<div class="viewernavitextwrapper">
<div
style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="seriesItem.keySeries" style="color:red">
Key Images
</div>
<div v-else>#{{ seriesItem.seriesNumber }}</div>
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
<el-popover placement="right-start" trigger="click"
popper-class="instance_frame_wrapper">
<div class="frame_list">
<div v-for="(instance, idx) in seriesItem.instanceInfoList"
:key="instance.Id" class="frame_content"
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(studyIndex, seriesItem, index, instance)">
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1}
frame`
}}</div>
</div> </div>
</div>
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;" />
</el-popover>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;" />
</el-popover>
</div> </div>
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
</div>
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
T: {{ seriesItem.sliceThickness }}
</div>
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
:content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ seriesItem.description }}
</div>
</el-tooltip>
</div> </div>
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
</div>
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
T: {{ seriesItem.sliceThickness }}
</div>
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
:content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ seriesItem.description }}
</div>
</el-tooltip>
</div> </div>
<!-- <el-image </div>
class="image-preview" <!-- <el-image
style="height:72px;width:72px;" class="image-preview"
:src="seriesItem.previewImageUrl" style="height:72px;width:72px;"
fit="fill" :src="seriesItem.previewImageUrl"
/> --> fit="fill"
<div /> -->
v-if="seriesItem.prefetchInstanceCount > 0 && seriesItem.prefetchInstanceCount < seriesItem.instanceCount * 100"> <div
<el-progress v-if="seriesItem.prefetchInstanceCount > 0 && seriesItem.prefetchInstanceCount < seriesItem.instanceCount * 100">
:percentage="parseInt((seriesItem.prefetchInstanceCount / seriesItem.instanceCount).toFixed(2))" /> <el-progress
</div> :percentage="parseInt((seriesItem.prefetchInstanceCount / seriesItem.instanceCount).toFixed(2))" />
</div> </div>
</div> </div>
</div> </div>
</el-collapse-item> </div>
</template> </el-collapse-item>
</template>
</div>
</template>
</div>
</el-collapse> </el-collapse>
</div> </div>
@ -1012,7 +1009,7 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.imageBox { .viewerContainer .imageBox {
position: relative; position: relative;
i { i {

View File

@ -37,7 +37,7 @@ export default {
// console.log(type, No); // console.log(type, No);
if (type !== "Chrome" && type !== "Edge") { if (type !== "Chrome" && type !== "Edge") {
this.tip = this.$t("browser:tip:changeBorwser"); this.tip = this.$t("browser:tip:changeBorwser");
this.getSystemInfo() // this.getSystemInfo()
return (this.visible = true); return (this.visible = true);
} }
let res = await this.getInfo(); let res = await this.getInfo();
@ -53,10 +53,10 @@ export default {
this.tip += "、"; this.tip += "、";
} }
}); });
this.getSystemInfo() // this.getSystemInfo()
return (this.visible = true); return (this.visible = true);
} }
this.getSystemInfo() // this.getSystemInfo()
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }

View File

@ -278,7 +278,7 @@ export default {
padding: 5px 20px; padding: 5px 20px;
margin-top: 5px; margin-top: 5px;
background: #fff; background: #fff;
::v-deep.el-form-item { ::v-deep .el-form-item {
padding-bottom: 20px; padding-bottom: 20px;
} }
@ -292,10 +292,10 @@ export default {
} }
} }
.form-label-width{ .form-label-width{
::v-deep.el-form-item__label{ ::v-deep .el-form-item__label{
width: 140px; width: 140px;
} }
::v-deep.el-form-item__content{ ::v-deep .el-form-item__content{
margin-left: 140px; margin-left: 140px;
} }
} }
@ -304,7 +304,7 @@ export default {
text-align: center; text-align: center;
} }
::v-deep.el-form-item { ::v-deep .el-form-item {
margin-bottom: 0px; margin-bottom: 0px;
padding-top: 5px; padding-top: 5px;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@ -156,7 +156,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.equipment_form_content{ .equipment_form_content{
padding: 0 10px; padding: 0 10px;
::v-deep.el-form-item { ::v-deep .el-form-item {
margin-bottom: 0px; margin-bottom: 0px;
padding: 5px 0 20px 0; padding: 5px 0 20px 0;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@ -295,7 +295,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.participants_form_content{ .participants_form_content{
padding: 0 10px; padding: 0 10px;
::v-deep.el-form-item { ::v-deep .el-form-item {
margin-bottom: 0px; margin-bottom: 0px;
padding: 5px 0 20px 0; padding: 5px 0 20px 0;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@ -309,7 +309,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.participants_form_content{ .participants_form_content{
padding: 0 10px; padding: 0 10px;
::v-deep.el-form-item { ::v-deep .el-form-item {
margin-bottom: 0px; margin-bottom: 0px;
padding: 5px 0 20px 0; padding: 5px 0 20px 0;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@ -381,7 +381,7 @@ export default {
padding: 5px 20px; padding: 5px 20px;
margin-top: 5px; margin-top: 5px;
background: #fff; background: #fff;
::v-deep.el-form-item { ::v-deep .el-form-item {
padding-bottom: 20px; padding-bottom: 20px;
} }
} }
@ -398,7 +398,7 @@ export default {
text-align: center; text-align: center;
} }
::v-deep.el-form-item { ::v-deep .el-form-item {
margin-bottom: 0px; margin-bottom: 0px;
padding-top: 5px; padding-top: 5px;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@ -357,7 +357,7 @@ export default {
overflow-y: auto; overflow-y: auto;
} }
::v-deep.el-card__body { ::v-deep .el-card__body {
padding: 10px; padding: 10px;
} }
@ -365,13 +365,13 @@ export default {
// padding: 10px 20px 20px 20px; // padding: 10px 20px 20px 20px;
// } // }
::v-deep.full-dialog-container { ::v-deep .full-dialog-container {
.el-dialog__body { .el-dialog__body {
height: calc(100% - 80px); height: calc(100% - 80px);
} }
} }
::v-deep.dialog-container { ::v-deep .dialog-container {
// margin-top: 50px !important; // margin-top: 50px !important;
width: 75%; width: 75%;
height: 80%; height: 80%;

View File

@ -44,7 +44,7 @@ export default {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
::v-deep.el-card__body { ::v-deep .el-card__body {
padding: 0px; padding: 0px;
} }
.trials-tab{ .trials-tab{

View File

@ -28,8 +28,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "~@/styles/mixin.scss"; // @import "~@/styles/mixin.scss";
@import "~@/styles/variables.module.scss"; // @import "~@/styles/variables.module.scss";
@use "@/styles/mixin.scss" as *;
@use "@/styles/variables.module.scss" as *;
.trials-wrapper { .trials-wrapper {
@include clearfix; @include clearfix;

View File

@ -495,17 +495,18 @@
<h2 v-else style="color:#ddd"> <h2 v-else style="color:#ddd">
Developing... Developing...
</h2> </h2>
<div v-if="iseCRFShowInDicomReading && currentReadingTaskState < 2" v-show="listShow" class="form-footer">
<el-button type="primary" size="small" @click="skipTask">
<!-- 跳过 -->
{{ $t('trials:readingReport:button:skip') }}
</el-button>
<el-button type="primary" size="small" @click="submit">
<!-- 提交 -->
{{ $t('common:button:submit') }}
</el-button>
</div>
</div> </div>
<div v-if="iseCRFShowInDicomReading && currentReadingTaskState < 2" v-show="listShow" class="form-footer">
<el-button type="primary" size="small" @click="skipTask">
<!-- 跳过 -->
{{ $t('trials:readingReport:button:skip') }}
</el-button>
<el-button type="primary" size="small" @click="submit">
<!-- 提交 -->
{{ $t('common:button:submit') }}
</el-button>
</div>
</transition> </transition>
@ -704,6 +705,7 @@ import { getAutoCutNextTask } from '@/api/user'
import const_ from '@/const/sign-code' import const_ from '@/const/sign-code'
import { changeURLStatic } from '@/utils/history.js' import { changeURLStatic } from '@/utils/history.js'
import SystemInfo from "@/utils/systemInfo"; import SystemInfo from "@/utils/systemInfo";
import md5 from 'js-md5'
export default { export default {
name: 'DicomViewer', name: 'DicomViewer',
components: { components: {
@ -987,6 +989,7 @@ export default {
}, },
mounted() { mounted() {
console.log(this.iseCRFShowInDicomReading, this.currentReadingTaskState, this.listShow)
this.getHotKeys() this.getHotKeys()
this.getWwcTpl() this.getWwcTpl()
this.getTrialCriterion() this.getTrialCriterion()
@ -1138,6 +1141,7 @@ export default {
let windowHeight = document.documentElement.clientHeight; let windowHeight = document.documentElement.clientHeight;
this.AspectRatio = windowWidth / windowHeight this.AspectRatio = windowWidth / windowHeight
}; };
this.getSystemInfoReading()
}, },
beforeDestroy() { beforeDestroy() {
DicomEvent.$off('updateImage') DicomEvent.$off('updateImage')
@ -2501,6 +2505,30 @@ export default {
}) })
}, },
async getSystemInfoReading() {
return new Promise(async resolve => {
let whitelisting = localStorage.getItem('whitelisting') ? JSON.parse(localStorage.getItem('whitelisting')) : []
let user = md5(sessionStorage.getItem('identityUserId'))
let r = whitelisting.some(item => item === user)
if (r) return resolve(true)
const systemInfo = new SystemInfo();
const allInfo = systemInfo.getAllInfo();
let deviceMemory = allInfo.hardware.deviceMemory; //
let { width, height } = allInfo.screen; //
// let discrete = allInfo.webgl.gpuType.discrete; //
// let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; //
// parseFloat(deviceMemory) < 16 ||
if (width < 1920 || height < 1080) {
let res = await this.$confirm(this.$t('browser:tip:Configuration'))
whitelisting.push(user)
localStorage.setItem('whitelisting', JSON.stringify(whitelisting))
resolve(res)
} else {
resolve(true)
}
})
},
} }
} }
</script> </script>
@ -2786,7 +2814,7 @@ export default {
.form-footer { .form-footer {
background: #000; background: #000;
padding: 10px 0; padding: 5px 0;
text-align: center; text-align: center;
} }
@ -2900,12 +2928,12 @@ export default {
} }
.series-table { .series-table {
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #dfdfdf; border-bottom: 1px solid #dfdfdf;
} }
@ -2916,14 +2944,14 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -2934,7 +2962,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }
} }

View File

@ -319,23 +319,23 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.series-table{ .series-table{
::v-deep.el-table{ ::v-deep .el-table{
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
::v-deep.el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf{ ::v-deep .el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf{
border-bottom: 1px solid #dfdfdf; border-bottom: 1px solid #dfdfdf;
} }
.el-table--border::after, .el-table--group::after, .el-table::before{ .el-table--border::after, .el-table--group::after, .el-table::before{
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper{ ::v-deep .el-table__header-wrapper{
th{ th{
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
} }
::v-deep.el-table__body-wrapper{ ::v-deep .el-table__body-wrapper{
tr{ tr{
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -344,7 +344,7 @@ export default {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }
} }
::v-deep.el-table__empty-block{ ::v-deep .el-table__empty-block{
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }
} }

View File

@ -140,46 +140,8 @@
</div> </div>
</el-tooltip> </el-tooltip>
<!-- 伪彩 --> <!-- 伪彩 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:lugano:button:colormap')" placement="bottom"> <colorMap v-show="isFusion" ref="colorMap" :unit="fusionOverlayModality === 'NM' ? 'counts' : 'g/ml'"
<div class="colorBar" style="display:flex;justify-content: flex-start;align-items: center;position: relative;" :modality="fusionOverlayModality" @setColorMap="setColorMap" @voiChange="voiChange" />
@mouseleave="isSlideMoving = false">
<div class="tool-wrapper" style="margin-right:0px" @click.stop="showColorBarPanel($event)"
@mouseleave="handleColorBarMouseout">
<div>
<!-- <canvas id="colorBarCanvas" /> -->
<div class="dropdown">
<div id="colorBar" class="icon" style="display: flex;align-items: center;width:266px">
<canvas id="colorBarCanvas" />
</div>
<!-- 伪彩 -->
<div class="text">{{ $t('trials:lugano:button:colormap') }}</div>
<div class="dropdown-content" style="width:266px">
<ul>
<li v-for="(colorMap, index) in colorMaps" :key="colorMap"
style="display: flex;align-items: center;margin-bottom:5px;padding:0 5px;justify-content: space-between;"
:class="{ activeLi: rgbPresetName === colorMap }" @click="setColorMap(colorMap)">
<canvas :id="`colorBarCanvas${index}`" />
<span style="margin-left:5px;font-size: 10px;">{{ colorMap }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div style="margin-left:-1px;border: 1px solid #424242;">
<el-input v-model="range" size="mini" style="width:120px" maxlength="3"
oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}" @change="upperRangeChange">
<template slot="append">g/ml</template>
</el-input>
</div>
<div id="slider" style="position: absolute;left: 6px;top:5px;cursor: pointer;">
<div id="sliderBox" class="slider" style="height: 17px;width: 10px;background-color: #909399;" />
<div id="slider-position" style="color:#ddd;font-size: 12px;">0</div>
</div>
</div>
</el-tooltip>
<!-- 截屏 --> <!-- 截屏 -->
<!-- <el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:screenShot')}`" placement="bottom"> <!-- <el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:screenShot')}`" placement="bottom">
@ -217,15 +179,14 @@
<Viewport ref="CT_AXIAL" :index="1" :active-index="activeIndex" <Viewport ref="CT_AXIAL" :index="1" :active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="ctSeries" :is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="ctSeries"
:rendering-engine-id="renderingEngineId" viewport-id="CT_AXIAL" :volume="ctVolume" :rendering-engine-id="renderingEngineId" viewport-id="CT_AXIAL" :volume="ctVolume"
:measure-datas="measureDatas" :style="1 === activeIndex ? viewportStyle : {}" :measure-datas="measureDatas" :style="1 === activeIndex ? viewportStyle : {}" />
@upperRangeChange="upperRangeChange" />
<Viewport ref="PT_AXIAL" :index="2" :active-index="activeIndex" <Viewport ref="PT_AXIAL" :index="2" :active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries" :is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries"
:rendering-engine-id="renderingEngineId" viewport-id="PT_AXIAL" :volume="ptVolume" :rendering-engine-id="renderingEngineId" viewport-id="PT_AXIAL" :volume="ptVolume"
:measure-datas="measureDatas" :style="2 === activeIndex ? viewportStyle : {}" :measure-datas="measureDatas" :style="2 === activeIndex ? viewportStyle : {}"
@upperRangeChange="upperRangeChange" /> @upperRangeChange="upperRangeChange" />
<Viewport ref="FUSION_AXIAL" :index="3" :active-index="activeIndex" <Viewport ref="FUSION_AXIAL" :index="3" :active-index="activeIndex"
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries" :is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries" :ct-series-info="ctSeries"
:rendering-engine-id="renderingEngineId" viewport-id="FUSION_AXIAL" :volume="ptVolume" :rendering-engine-id="renderingEngineId" viewport-id="FUSION_AXIAL" :volume="ptVolume"
:measure-datas="measureDatas" :rgb-preset-name="rgbPresetName" :style="3 === activeIndex ? viewportStyle : {}" :measure-datas="measureDatas" :rgb-preset-name="rgbPresetName" :style="3 === activeIndex ? viewportStyle : {}"
@upperRangeChange="upperRangeChange" /> @upperRangeChange="upperRangeChange" />
@ -306,6 +267,7 @@ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunc
import { mat4, vec3 } from 'gl-matrix' import { mat4, vec3 } from 'gl-matrix'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import readingChart from '@/components/readingChart' import readingChart from '@/components/readingChart'
import colorMap from '@/views/trials/trials-panel/reading/dicoms3D/components/colorMap.vue'
// import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction' // import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'
// import vtkMath from '@kitware/vtk.js/Common/Core/Math' // import vtkMath from '@kitware/vtk.js/Common/Core/Math'
// import CircleROITool from './tools/CircleROITool' // import CircleROITool from './tools/CircleROITool'
@ -395,7 +357,8 @@ export default {
TableQuestions, TableQuestions,
CustomWwwcForm, CustomWwwcForm,
FusionForm, FusionForm,
readingChart readingChart,
colorMap
}, },
data() { data() {
return { return {
@ -441,6 +404,8 @@ export default {
upper: 6, upper: 6,
isSlideMoving: false, isSlideMoving: false,
viewportStyle: {}, viewportStyle: {},
isFusion: true,
fusionOverlayModality: 'PT',
defaultWwwc: [ defaultWwwc: [
{ label: this.$t('trials:reading:button:wwwcDefault'), val: -1, ww: null, wc: null }, // { label: this.$t('trials:reading:button:wwwcDefault'), val: -1, ww: null, wc: null }, //
{ label: this.$t('trials:reading:button:wwwcCustom'), val: 0, ww: null, wc: null }, // { label: this.$t('trials:reading:button:wwwcCustom'), val: 0, ww: null, wc: null }, //
@ -458,7 +423,9 @@ export default {
activeCanvasWW: null, activeCanvasWW: null,
activeCanvasWC: null, activeCanvasWC: null,
fusion: { visible: false }, // fusion: { visible: false }, //
screenshotWindow: null screenshotWindow: null,
hasVoiChanged: false,
lastUpper: null
// initFirstAnnotation:false // initFirstAnnotation:false
} }
}, },
@ -512,13 +479,13 @@ export default {
}) })
this.colorMaps = getColormapNames() this.colorMaps = getColormapNames()
this.colorMaps.unshift('hsv') this.colorMaps.unshift('hsv')
this.$nextTick(() => { this.$nextTick(() => {
this.renderColorMaps() // this.renderColorMaps()
this.handleElementsClick() this.handleElementsClick()
this.initPage() this.initPage()
this.upperRangeChange(this.range) // this.upperRangeChange(this.range)
this.initSlider() // this.initSlider()
}) })
FusionEvent.$on('getAnnotations', () => { FusionEvent.$on('getAnnotations', () => {
console.log('getAnnotations') console.log('getAnnotations')
@ -744,6 +711,7 @@ export default {
return new Promise(resolve => { return new Promise(resolve => {
getDicomSeriesInfo({ seriesId }).then(res => { getDicomSeriesInfo({ seriesId }).then(res => {
var series = res.Result var series = res.Result
this.fusionOverlayModality = series.Modality
var imageIds = [] var imageIds = []
var instanceList = [] var instanceList = []
series.InstanceInfoList.forEach(instance => { series.InstanceInfoList.forEach(instance => {
@ -1057,7 +1025,7 @@ export default {
// volume to use for the WindowLevelTool for the fusion viewports // volume to use for the WindowLevelTool for the fusion viewports
fusionToolGroup.addTool(WindowLevelTool.toolName, { fusionToolGroup.addTool(WindowLevelTool.toolName, {
volumeId: ptVolumeId volumeId: ptVolumeId2
}); });
[ctToolGroup, ptToolGroup, fusionToolGroup].forEach((toolGroup) => { [ctToolGroup, ptToolGroup, fusionToolGroup].forEach((toolGroup) => {
@ -1627,148 +1595,141 @@ export default {
var slider = document.getElementById('slider') var slider = document.getElementById('slider')
var sliderBox = document.getElementById('sliderBox') var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas') var container = document.getElementById('colorBarCanvas')
slider.addEventListener('mousedown', () => {
if (!slider || !sliderBox || !container) return
slider.addEventListener('mousedown', (e) => {
this.isSlideMoving = true this.isSlideMoving = true
e.stopPropagation()
// document.onselectstart = function() { return false }// // document.onselectstart = function() { return false }//
// document.ondragstart = function() { return false } // document.ondragstart = function() { return false }
}) })
document.addEventListener('mousemove', (e) => { document.addEventListener('mousemove', (e) => {
if (this.isSlideMoving) { if (this.isSlideMoving) {
var containerWidth = container.clientWidth var colorBarContainer = document.getElementById('colorBar')
if (!colorBarContainer) return
var containerWidth = colorBarContainer.clientWidth
var sliderWidth = sliderBox.clientWidth var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth var maxLeft = containerWidth - sliderWidth
var left = e.clientX - container.getBoundingClientRect().left
var position = null //
position = left var rect = colorBarContainer.getBoundingClientRect()
var left = e.clientX - rect.left - (sliderWidth / 2)
if (left < 0) { if (left < 0) {
left = 6 left = 0
position = 0
} else if (left > maxLeft) { } else if (left > maxLeft) {
left = maxLeft + 6 left = maxLeft
position = maxLeft
} }
slider.style.left = left + 'px' slider.style.left = left + 'px'
var positionValue = document.getElementById('slider-position') var positionValue = document.getElementById('slider-position')
var upper = this.range var upper = this.range
position = parseInt((position / maxLeft) * upper) var position = parseInt((left / maxLeft) * upper)
positionValue.textContent = position
if (positionValue) {
positionValue.textContent = position
}
this.upper = position this.upper = position
this.voiChange(position)
if (this.sliderTimeout) {
cancelAnimationFrame(this.sliderTimeout)
}
this.sliderTimeout = requestAnimationFrame(() => {
this.voiChange(position)
})
} }
}) })
document.addEventListener('mouseup', () => { document.addEventListener('mouseup', (e) => {
this.isSlideMoving = false if (this.isSlideMoving) {
this.isSlideMoving = false
if (this.sliderTimeout) {
cancelAnimationFrame(this.sliderTimeout)
}
this.voiChange(this.upper)
}
// document.onselectstart = null // document.onselectstart = null
// document.ondragstart = null // document.ondragstart = null
}) })
}, },
upperRangeChange(v) {
if (v === 0 || v < this.upper) {
return
}
var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas')
var containerWidth = container.clientWidth
var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth
var left = (this.upper / this.range) * maxLeft
if (left < 0) {
left = 6
} else if (left >= maxLeft) {
left = maxLeft + 6
}
var slider = document.getElementById('slider')
slider.style.left = left + 'px'
var positionValue = document.getElementById('slider-position')
positionValue.textContent = this.upper
},
renderColorMaps() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
this.colorMaps.forEach((e, index) => {
this.createColorBar(e, `colorBarCanvas${index}`, 180, 15)
})
},
getVOIRange(viewportId, volumeId) {
const defaultImageRange = { lower: -1000, upper: 1000 }
const viewport = (
renderingEngine.getViewport(viewportId)
)
const volumeActor = volumeId
? viewport.getActor(volumeId)
: viewport.getDefaultActor()
if (!volumeActor || !csUtils.isImageActor(volumeActor)) {
return defaultImageRange
}
const voiRange = (volumeActor.actor)
.getProperty()
.getRGBTransferFunction(0)
.getRange()
return voiRange[0] === 0 && voiRange[1] === 0
? defaultImageRange
: { lower: voiRange[0], upper: voiRange[1] }
},
voiChange(v) { voiChange(v) {
// console.log(this.lastUpper, this.hasVoiChanged, v)
if (this.lastUpper === v && this.hasVoiChanged) return;
this.lastUpper = v;
this.hasVoiChanged = true;
let viewportIds = ['FUSION_AXIAL', 'PT_AXIAL', 'PET_MIP_CORONAL'] let viewportIds = ['FUSION_AXIAL', 'PT_AXIAL', 'PET_MIP_CORONAL']
viewportIds.map(viewportId => { viewportIds.map(viewportId => {
// const volumeId = viewportId === 'viewportId' ? ptVolumeId : ctVolumeId // const volumeId = viewportId === 'viewportId' ? ptVolumeId : ctVolumeId
const volumeId = ptVolumeId const volumeId = viewportId === 'FUSION_AXIAL' ? ptVolumeId2 : ptVolumeId
const voiRange = { lower: 0, upper: v } const voiRange = { lower: 0, upper: v }
const viewport = ( const viewport = (
renderingEngine.getViewport(viewportId) renderingEngine.getViewport(viewportId)
) )
if (!viewport) return if (!viewport) return
// const volumeId = viewport.getVolumeId()
// const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
// volumeId,
// viewport.renderingEngineId
// )
viewport.setProperties({ voiRange }, volumeId) viewport.setProperties({ voiRange }, volumeId)
viewport.render() viewport.render()
this.$refs[viewport.id].setWwWc() this.$refs[viewport.id] && this.$refs[viewport.id].setWwWc()
// viewportsContainingVolumeUID.forEach((vp) => {
// vp.render()
// this.$refs[vp.id].setWwWc()
// console.log(vp.id)
// })
}) })
// colorMap
if (this.$refs.colorMap) {
this.$refs.colorMap.changeVoi(v)
}
}, },
// async setColorMap(rgbPresetName) {
// this.rgbPresetName = rgbPresetName
// if (this.$refs.FUSION_AXIAL) {
// this.$refs.FUSION_AXIAL.setPreset(this.rgbPresetName)
// this.$refs.FUSION_AXIAL.renderColorBar(this.rgbPresetName)
// }
// const viewport = getRenderingEngine(renderingEngineId)?.getViewport('FUSION_AXIAL')
// if (!viewport) return
// const actorEntry = viewport.getActors()?.find(e => String(e.uid || '').includes('PT_VOLUME_ID2'))
// if (!actorEntry?.actor) return
// const preset = rgbPresetName === 'hsv' ? vtkColorMaps.getPresetByName(rgbPresetName) : getColormap(rgbPresetName)
// const cfun = vtkColorTransferFunction.newInstance()
// const rgbPoints = preset?.RGBPoints
// if (Array.isArray(rgbPoints)) {
// for (let i = 0; i < rgbPoints.length; i += 4) {
// cfun.addRGBPoint(rgbPoints[i], rgbPoints[i + 1], rgbPoints[i + 2], rgbPoints[i + 3])
// }
// }
// cfun.setMappingRange(0, 5)
// actorEntry.actor.getProperty().setRGBTransferFunction(0, cfun)
// const ofun = vtkPiecewiseFunction.newInstance()
// ofun.addPoint(0, 0.0)
// ofun.addPoint(0.1, 0.9)
// ofun.addPoint(5, 1.0)
// actorEntry.actor.getProperty().setScalarOpacity(0, ofun)
// viewport.render()
// },
async setColorMap(rgbPresetName) { async setColorMap(rgbPresetName) {
this.rgbPresetName = rgbPresetName this.rgbPresetName = rgbPresetName
let viewports = ['FUSION_AXIAL', 'PT_AXIAL', 'PET_MIP_CORONAL'] let viewports = ['FUSION_AXIAL']
viewports.map(v => { viewports.map(v => {
this.$refs[v].setPreset(this.rgbPresetName) this.$refs[v] && this.$refs[v].setPreset(this.rgbPresetName)
this.$refs[v].renderColorBar(this.rgbPresetName) this.$refs[v] && this.$refs[v].setColorMap(rgbPresetName)
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = (
renderingEngine.getViewport(v)
)
viewport.setProperties({ colormap: { name: rgbPresetName } }, ptVolumeId)
viewport.render()
}) })
// this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName) },
// this.$refs['FUSION_AXIAL'].renderColorBar(this.rgbPresetName) upperRangeChange(v) {
// this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15) if (this.upper === v) return
// const renderingEngine = getRenderingEngine(renderingEngineId) this.upper = v;
// const viewport = ( if (v === 0) {
// renderingEngine.getViewport(viewportIds.FUSION.AXIAL) return
// ) }
this.voiChange(v)
// viewport.setProperties({ colormap: { name: rgbPresetName } }, ptVolumeId) // colorMap
// // viewport.setProperties({ colormap: { name: rgbPresetName }}, ctVolumeId) if (this.$refs.colorMap) {
// viewport.render() this.$refs.colorMap.changeVoi(v)
// document.onselectstart = function() { return false }// }
// document.ondragstart = function() { return false }
}, },
setPetColorMap(volumeInfo) { setPetColorMap(volumeInfo) {
const { volumeActor } = volumeInfo const { volumeActor } = volumeInfo
@ -1979,17 +1940,24 @@ export default {
// document.onselectstart = function() { return false }// // document.onselectstart = function() { return false }//
// document.ondragstart = function() { return false } // document.ondragstart = function() { return false }
}, },
setWwwc(v) { // setWwwc(v) {
// this.changeMapperRange(v.wc, v.ww) // // this.changeMapperRange(v.wc, v.ww)
const renderingEngine = getRenderingEngine(renderingEngineId) // const renderingEngine = getRenderingEngine(renderingEngineId)
let viewportId = this.activeIndex === 1 ? 'CT_AXIAL' : this.activeIndex === 2 ? 'PT_AXIAL' : this.activeIndex === 3 ? 'FUSION_AXIAL' : 'PET_MIP_CORONAL' // let viewportId = this.activeIndex === 1 ? 'CT_AXIAL' : this.activeIndex === 2 ? 'PT_AXIAL' : this.activeIndex === 3 ? 'FUSION_AXIAL' : 'PET_MIP_CORONAL'
const viewport = renderingEngine.getViewport(viewportId) // const viewport = renderingEngine.getViewport(viewportId)
const lower = v.wc - v.ww / 2 // const lower = v.wc - v.ww / 2
const upper = v.wc + v.ww / 2 - 1 // const upper = v.wc + v.ww / 2 - 1
viewport.setProperties({ voiRange: { upper: upper, lower: lower } }) // viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
viewport.render() // viewport.render()
this.customWwc.visible = false // if (viewportId !== 'CT_AXIAL') {
}, // const nextUpper = Math.round(upper)
// this.upper = nextUpper
// if (this.$refs.colorMap) {
// this.$refs.colorMap.changeVoi(nextUpper)
// }
// }
// this.customWwc.visible = false
// },
changeMapperRange(wc, ww) { changeMapperRange(wc, ww) {
var lower = wc - ww / 2.0 var lower = wc - ww / 2.0
var upper = wc + ww / 2.0 var upper = wc + ww / 2.0
@ -2262,7 +2230,7 @@ export default {
background-color: #000; background-color: #000;
padding: 5px 2px; padding: 5px 2px;
::v-deep.el-dialog { ::v-deep .el-dialog {
background: #1e1e1e; background: #1e1e1e;
border: 1px solid #ddd; border: 1px solid #ddd;
color: #ddd; color: #ddd;

View File

@ -19,6 +19,8 @@
</div> </div>
<div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageMaxLength}` }}</div> <div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageMaxLength}` }}</div>
<div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div> <div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div>
<div v-if="index === 3">{{ ctSeriesInfo ? ctSeriesInfo.modality + ' / ' + seriesInfo.modality : seriesInfo.modality }}</div>
<div v-if="index === 4">MIP</div>
</div> </div>
<!-- 描述信息 --> <!-- 描述信息 -->
<div <div
@ -135,6 +137,12 @@ export default {
return {} return {}
} }
}, },
ctSeriesInfo: {
type: Object,
default() {
return null
}
},
renderingEngineId: { renderingEngineId: {
type: String, type: String,
required: true required: true
@ -200,7 +208,6 @@ export default {
mounted() { mounted() {
const digitPlaces = parseInt(this.$route.query.digitPlaces) const digitPlaces = parseInt(this.$route.query.digitPlaces)
this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces
console.log(toolsUtilities)
this.subjectCode = this.$route.query.subjectCode this.subjectCode = this.$route.query.subjectCode
var element = document.getElementById(`viewport${this.index}`) var element = document.getElementById(`viewport${this.index}`)
element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage) element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage)
@ -328,7 +335,7 @@ export default {
let properties = viewport.getProperties() let properties = viewport.getProperties()
if (this.index === 3) { if (this.index === 3) {
// const volumeId = viewport.getVolumeId() // const volumeId = viewport.getVolumeId()
const volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID` const volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID2`
properties = viewport.getProperties(volumeId) properties = viewport.getProperties(volumeId)
} }
if (properties && properties.voiRange) { if (properties && properties.voiRange) {
@ -338,6 +345,10 @@ export default {
upper upper
) )
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}` this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
// PTMIP (CT)
if (this.index !== 1) {
this.$emit('upperRangeChange', Math.round(upper))
}
} }
}, },
handleMouseMove(e) { handleMouseMove(e) {
@ -567,10 +578,17 @@ export default {
ctx.fillStyle = gradient ctx.fillStyle = gradient
ctx.fillRect(0, 0, rectWidth, rectHeight) ctx.fillRect(0, 0, rectWidth, rectHeight)
}, },
setColorMap(presetName) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID`
viewport.setProperties({ colormap: { name: presetName } }, volumeId)
viewport.render()
},
setWwWc() { setWwWc() {
let properties = viewport.getProperties() let properties = viewport.getProperties()
if (this.index === 3) { if (this.index === 3) {
properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID`) properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID2`)
} }
if (properties && properties.voiRange) { if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange var { lower, upper } = properties.voiRange
@ -582,7 +600,10 @@ export default {
upper upper
) )
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}` this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
this.$emit('upperRangeChange', Math.round(windowWidth)) // PTMIP (CT)
if (this.index !== 1) {
this.$emit('upperRangeChange', Math.round(upper))
}
} }
}, },
async getScreenshots() { async getScreenshots() {

View File

@ -1,106 +0,0 @@
<template>
<div v-loading="loading" class="ecrf-wrapper">
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
评估说明
</h4>
<el-form
ref="questions"
size="small"
:model="questionForm"
>
<el-form-item
label="访视点备注"
prop="note"
:rules="[
{ required: true,
message:$t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
]"
>
<el-input
v-model="questionForm.note"
:disabled="readingTaskState >= 2"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
maxlength="500"
/>
</el-form-item>
<el-form-item v-if="readingTaskState < 2">
<div style="text-align:right">
<el-button size="mini" @click="handleSave">{{ $t('common:button:save') }}</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ECRF',
data() {
return {
loading: false,
questionForm: {
note: ''
},
readingTaskState: 1,
isBaseLineTask: false,
visitTaskId: ''
}
},
mounted() {
this.trialId = this.$route.query.trialId
this.visitTaskId = this.$route.query.visitTaskId
},
beforeDestroy() {
},
methods: {
handleSave() {
this.$refs['questions'].validate((valid) => {
if (!valid) return
})
},
}
}
</script>
<style lang="scss" scoped>
.ecrf-wrapper{
::v-deep .el-form-item__label{
color: #c3c3c3;
text-align: left;
}
::v-deep .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-textarea__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-form-item{
display: flex;
flex-direction: row;
justify-content: flex-start;
flex-wrap: wrap;
}
::v-deep .el-form-item__content{
flex: 1;
}
::v-deep .el-button--mini, .el-button--mini.is-round {
padding: 7px 10px;
}
.el-form-item__content
.el-select{
width: 100%;
}
}
</style>

View File

@ -1,148 +0,0 @@
<template>
<div v-loading="loading" class="ecrf-wrapper">
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
影像质量
</h4>
<el-form
ref="questions"
size="small"
:model="questionForm"
>
<el-form-item
label="影像质量"
prop="imageQuality"
:rules="[
{ required: true,
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
]"
>
<el-radio-group v-model="questionForm.imageQuality">
<el-radio
v-for="item of $d.ImageQualityEvaluation"
:key="item.id"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="questionForm.imageQuality === 2"
label="影像质量问题"
prop="imageQualityIssues"
:rules="[
{ required: true,
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
]"
>
<el-select
v-model="questionForm.imageQualityIssues"
:disabled="readingTaskState >= 2"
clearable
>
<el-option
v-for="item of $d.ImageQualityIssues"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="questionForm.imageQualityIssues === 5"
label="影像质量备注"
prop="note"
:rules="[
{ required: true,
message:$t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
]"
>
<el-input
v-model="questionForm.note"
:disabled="readingTaskState >= 2"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
maxlength="500"
/>
</el-form-item>
<el-form-item v-if="readingTaskState < 2">
<div style="text-align:right">
<el-button size="mini" @click="handleSave">{{ $t('common:button:save') }}</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ECRF',
data() {
return {
loading: false,
questionForm: {
imageQuality: null,
imageQualityIssues: null,
note: ''
},
readingTaskState: 1,
isBaseLineTask: false,
visitTaskId: ''
}
},
mounted() {
this.trialId = this.$route.query.trialId
},
beforeDestroy() {
},
methods: {
handleSave() {
this.$refs['questions'].validate((valid) => {
if (!valid) return
})
},
}
}
</script>
<style lang="scss" scoped>
.ecrf-wrapper{
::v-deep .el-form-item__label{
color: #c3c3c3;
text-align: left;
}
::v-deep .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-textarea__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-form-item{
display: flex;
flex-direction: row;
justify-content: flex-start;
flex-wrap: wrap;
}
::v-deep .el-form-item__content{
flex: 1;
}
::v-deep .el-button--mini, .el-button--mini.is-round {
padding: 7px 10px;
}
.el-form-item__content
.el-select{
width: 100%;
}
}
</style>

View File

@ -1,354 +0,0 @@
<template>
<div class="measurement-wrapper">
<div class="container">
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
核医学评估
</h4>
<div class="lesion_list">
<div class="flex-row" style="margin:3px 0;">
<div class="title">盆腔淋巴结</div>
</div>
<el-table
:data="questions"
style="width: 100%"
row-key="rowIndex"
:expand-row-keys="expands"
@expand-change="expandSelect"
size="mini"
>
<el-table-column type="expand">
<template slot-scope="props">
<el-form :ref="props.row.rowIndex" size="small" :model="props.row" label-width="80px" style="padding-right: 10px">
<el-form-item label="部位">
<el-input
v-model="props.row.part"
disabled
/>
</el-form-item>
<el-form-item
label="转移灶"
prop="distraction"
:rules="[
{ required: true,
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
]"
>
<el-select v-model="props.row.distraction" style="width:100%;">
<el-option label="阴性" :value="1"></el-option>
<el-option label="阳性" :value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="SUVmax">
<el-input
v-model="props.row.suvMax"
disabled
/>
</el-form-item>
<el-form-item >
<div style="text-align:right">
<el-button v-if="props.row.annotation" size="mini" @click="clearAnnotation(props.row)">{{ $t('trials:reading:button:removeMark') }}</el-button>
<el-button size="mini" @click="handleSave(props.row)">{{ $t('common:button:save') }}</el-button>
</div>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
label="部位"
prop="part">
</el-table-column>
<el-table-column
label="转移灶"
prop="distraction">
<template slot-scope="scope">
{{scope.row.distraction === 1 ? '阴性' : scope.row.distraction === 2 ? '阳性' : ''}}
</template>
</el-table-column>
<el-table-column
label="SUVmax"
prop="suvMax">
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import FusionEvent from './../FusionEvent'
export default {
name: 'TableQuestionList',
components: {
},
data() {
return {
height: window.innerHeight - 140,
questions: [
{
rowIndex: '001',
part: '髂内',
distraction: '',
suvMax: null,
saveTypeEnum: 1,
annotation: null
},
{
rowIndex: '002',
part: '髂外',
distraction: '',
suvMax: null,
saveTypeEnum: 1,
annotation: null
},
{
rowIndex: '003',
part: '髂总',
distraction: '',
suvMax: null,
saveTypeEnum: 1,
annotation: null
},
{
rowIndex: '004',
part: '闭孔',
distraction: '',
suvMax: null,
saveTypeEnum: 1,
annotation: null
}
],
visitTaskId: '',
isCurrentTask: false,
loading: false,
readingTaskState: 1,
isBaseLineTask: false,
taskBlindName: '',
expands:[]
}
},
mounted() {
},
beforeDestroy() {
},
methods: {
expandSelect(row, expandedRows) {
this.expands = []
if (expandedRows.length > 0) {
row ? this.expands.push(row.rowIndex) : ''
}
if (this.expands.length > 0 && row.annotation) {
FusionEvent.$emit('imageLocation', { annotation: row.annotation })
}
},
async clearAnnotation(row) {
//
const confirm = await this.$confirm(
this.$t('trials:reading:warnning:msg47'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
let i = this.questions.findIndex(i=>i.rowIndex === row.rowIndex)
FusionEvent.$emit('removeAnnotation', { annotation: this.questions[i].annotation })
this.questions[i].annotation = null
this.questions[i].suvMax = null
},
async handleSave(row) {
let loading = null
try {
let valid = await this.$refs[row.rowIndex].validate()
if (!valid) return
loading = this.$loading({ fullscreen: true })
setTimeout(() => {
//
loading.close()
this.$message.success(this.$t('common:message:savedSuccessfully'))
//
}, 2000);
} catch(e) {
if (loading) {
loading.close()
}
}
},
setActiveCollapse(annotationUID) {
for (let i = 0; i < this.questions.length; i++) {
let obj = this.questions[i]
if (obj.annotation && obj.annotation.data && obj.annotation.data.annotationUID === annotationUID) {
this.expands = [this.questions[i].rowIndex]
break
}
}
},
isCanActiveTool(toolName) {
if (this.expands.length > 0) {
let isExist = this.isExistMeasureData(this.expands[0])
return { isCanActiveTool: isExist, reason: '' }
} else {
return { isCanActiveTool: false, reason: '' }
}
},
isExistMeasureData(rowIndex) {
let i = this.questions.findIndex(i=>i.rowIndex === rowIndex)
if (this.questions[i].annotation) {
return false
} else {
return true
}
},
// modifyMeasuredData(measureObj) {
// if (measureObj.questionInfo) {
// this.activeItem.activeCollapseId = measureObj.questionInfo.QuestionId
// this.activeItem.activeRowIndex = String(measureObj.questionInfo.RowIndex)
// this.activeName = `${this.activeItem.activeCollapseId}_${this.activeItem.activeRowIndex}`
// const refName = `${this.activeItem.activeCollapseId}_${this.activeItem.activeRowIndex}`
// if (this.$refs[refName]) {
// this.$refs[refName][0].setMeasureData(measureObj.measureData)
// }
// }
// },
//
setMeasuredData(measureData) {
if (measureData.data && measureData.data.data && measureData.data.data.remark ) {
//
//
let remark = measureData.data.data.remark
let i = this.questions.findIndex(i=>i.part === remark)
this.questions[i].suvMax = measureData.suvMax
this.questions[i].annotation = measureData
this.expands = [this.questions[i].rowIndex]
} else {
if (this.expands.length > 0) {
let i = this.questions.findIndex(i=>i.rowIndex === this.expands[0])
if (measureData.data && measureData.data.data) {
measureData.data.data.remark = this.questions[i].part
}
this.questions[i].annotation = measureData
}
}
},
}
}
</script>
<style lang="scss" scoped>
.measurement-wrapper{
// .container{
// padding: 10px;
// }
.title{
padding: 5px;
font-weight: bold;
color: #ddd;
font-size: 15px;
}
.add-icon{
padding: 5px;
font-weight: bold;
color: #ddd;
font-size: 15px;
border: 1px solid #938b8b;
margin-bottom: 2px;
cursor: pointer;
}
.add-icon:hover{
background-color: #607d8b;
}
.flex-row{
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: #424242;
}
.lesion_list{
position: relative;
::v-deep .el-table, .el-table__expanded-cell {
background-color: #000;
color: #fff;
border-color:#444444;
.el-form-item__label {
color: #fff;
}
.el-input__inner {
background-color: rgba(0, 0, 0, .1);
border-color: #606266;
color: #fff;
}
.el-input.is-disabled .el-input__inner {
background-color: #303133;
border-color: #444444;
color: #c0c4cc;
cursor: not-allowed;
}
}
::v-deep .el-table th, .el-table tr {
background-color: #000;
color: #fff;
border-color:#444444;
}
::v-deep .el-table__body tr > td{
background-color:#000 !important;
color: #fff;
border-color:#444444;
}
// ::v-deep .el-table__body tr:hover > td{
// background-color:#858282 !important;
// color: #fff;
// border-color:#444444;
// }
::v-deep .el-table--border th.gutter:last-of-type{
border: none;
}
::v-deep .el-card__header{
border: none;
padding: 10px;
}
}
.el-collapse{
border-bottom:none;
border-top:none;
::v-deep .el-collapse-item{
background-color: #000!important;
color: #ddd;
}
::v-deep .el-collapse-item__header{
background-color: #000!important;
color: #ddd;
border-bottom-color:#5a5a5a;
padding-left: 5px;
height: 35px;
line-height: 35px;
}
::v-deep .el-collapse-item__wrap{
background-color: #000!important;
color: #ddd;
}
::v-deep .el-collapse-item__content{
width:260px;
position: absolute;
top: 0px;
right: 0px;
// border: 1px solid #ffeb3b;
border: 1px solid #fff;
z-index: 1;
color: #ddd;
padding: 5px;
background-color:#1e1e1e;
}
}
}
</style>

View File

@ -1,690 +0,0 @@
<template>
<div
class="viewport_container"
:class="['item',activeIndex === index?'item_active':'']"
@mouseup="sliderMouseup"
>
<div :id="`viewport${index}`" ref="viewportCanvas" style="height: 100%;width:100%" />
<!-- 序列信息 -->
<div
v-if="seriesInfo.taskBlindName"
class="seriesInfo_wrapper"
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
>
<h2 v-if="isReadingShowSubjectInfo" class="taskInfo_container">
{{ subjectCode }} {{ seriesInfo.taskBlindName }}
</h2>
<div v-if="index === 1 || index === 2">
Series: #{{ seriesInfo.seriesNumber }}
</div>
<div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageMaxLength}` }}</div>
<div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div>
</div>
<!-- 描述信息 -->
<div
v-if="seriesInfo.description && (index === 1 || index === 2)"
class="descInfo_wrapper"
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
>
<div>{{ seriesInfo.description }}</div>
</div>
<!-- 图像信息 -->
<div
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
class="imageInfo_wrapper_l"
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
>
<div v-show="mousePosition.index.length > 0">
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
</div>
<div v-if="(seriesInfo.modality === 'CT' || seriesInfo.modality === 'DR' || seriesInfo.modality === 'CR') && mousePosition.value">
HU: {{ mousePosition.value }}
</div>
<div v-else-if="(seriesInfo.modality === 'PT' && mousePosition.value)">
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.value.toFixed(3) :mousePosition.value.toFixed(digitPlaces) }}
</div>
<div v-else-if="mousePosition.value">
Density: {{ mousePosition.value }}
</div>
<div v-if="index === 1 || index === 2">
W*H: {{ imageInfo.size }}
</div>
<!-- <div v-if="index === 1 || index === 2">Zoom: {{ imageInfo.zoom }}</div> -->
</div>
<div
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
class="imageInfo_wrapper_r"
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
>
<div v-show="imageInfo.location && index !== 3 ">Location: {{ `${imageInfo.location} mm` }}</div>
<div v-show="seriesInfo.sliceThickness && index !== 3">Slice Thickness: {{ `${Number(seriesInfo.sliceThickness).toFixed(2)} mm` }}</div>
<div v-show="imageInfo.wwwc ">WW/WL: {{ imageInfo.wwwc }}</div>
</div>
<div v-if="index !== 4" ref="sliderBox" class="slider_box" :style="{background: index === 2?'#ddd':'#333'}" @click.stop="goViewer($event)">
<div :style="{top: sliderBoxHeight + '%'}" class="box" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
</div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }}
</div>
<div style="position: absolute;top: 50%;right: 15px;color: #f44336;transform: translateY(-50%);">
{{ markers.right }}
</div>
<div style="position: absolute;left: 50%;bottom: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.bottom }}
</div>
<div style="position: absolute;top: 50%;left: 15px;color: #f44336;transform: translateY(-50%);">
{{ markers.left }}
</div>
<div v-if="presetName" class="color_bar">
<canvas id="colorBar_Canvas" />
</div>
<div v-if="index === 4" id="rotateBar" ref="rotateBar" class="rotate_slider_box" @click.stop="clickRotate($event)">
<div id="rotateSlider" :style="{left: rotateBarLeft + 'px'}" class="box" @click.stop.prevent="() => {return}" @mousedown.stop="rotateBarMousedown($event)" />
</div>
</div>
</template>
<script>
import {
getRenderingEngine,
metaData,
utilities,
// getOrCreateCanvas,
Enums } from '@cornerstonejs/core'
import {
utilities as toolsUtilities,
annotation,
Enums as toolsEnums
// cursors
} from '@cornerstonejs/tools'
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
import { vec3, mat4 } from 'gl-matrix'
import html2canvas from 'html2canvas'
const { getColormap } = utilities.colormap
const {
VOLUME_NEW_IMAGE
// VOLUME_LOADED,
// IMAGE_VOLUME_MODIFIED,
// VOLUME_VIEWPORT_NEW_VOLUME
} = Enums.Events
var renderingEngine
var viewport
export default {
name: 'Viewport',
props: {
activeIndex: {
type: Number,
default: 0
},
index: {
// 1:ct 2:pet 3:fusion 4:mip
type: Number,
required: true
},
isReadingShowSubjectInfo: {
type: Boolean,
default: false
},
seriesInfo: {
type: Object,
default() {
return {}
}
},
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
volume: {
type: Object,
default() {
return {}
}
},
measureDatas: {
type: Array,
default() {
return []
}
}
},
data() {
return {
subjectCode: '',
mousePosition: { index: [], value: null, modalityUnit: '', world: [], ctVal: null, ptVal: null },
digitPlaces: -1,
imageInfo: {
zoom: null,
size: null,
location: null,
sliceThickness: null,
wwwc: null
},
sliderBoxHeight: 0,
sliderInfo: {
oldB: null,
oldM: null,
isMove: false
},
rotateAngle: 0,
rotateBarLeft: 0,
rotateBarInfo: {
initX: null,
initLeft: null,
isMove: false
},
isFirstRender: true,
defaultWindowLevel: {},
presetName: '',
orientationMarkers: [],
originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' }
}
},
watch: {
activeIndex: {
handler(v) {
console.log('activeIndex ', v)
}
}
},
mounted() {
const digitPlaces = parseInt(this.$route.query.digitPlaces)
this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces
// console.log(toolsUtilities)
this.subjectCode = this.$route.query.subjectCode
var element = document.getElementById(`viewport${this.index}`)
element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage)
element.addEventListener(Enums.Events.CAMERA_MODIFIED, this.handleCameraModified)
element.addEventListener(Enums.Events.VOI_MODIFIED, this.handleVOIModified)
element.addEventListener(toolsEnums.Events.MOUSE_WHEEL, this.handletoolsMouseWheel)
element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.handleMouseMove)
element.addEventListener('mouseleave', this.handleMouseLeave)
document.addEventListener('mouseup', () => {
this.sliderMouseup()
if (this.index === 4) {
this.rotateBarMouseup()
}
})
document.addEventListener('mousemove', (e) => {
this.sliderMousemove(e)
if (this.index === 4) {
this.rotateBarMousemove(e)
}
})
},
methods: {
handleVolumeNewImage(e) {
const { imageIndex } = e.detail
if (this.viewportId === e.detail.viewportId && this.index !== 4) {
this.seriesInfo.imageIdIndex = imageIndex
}
renderingEngine = getRenderingEngine(this.renderingEngineId)
viewport = renderingEngine.getViewport(this.viewportId)
if (viewport) {
var zoom = viewport.getZoom()
if (zoom) {
this.imageInfo.zoom = zoom.toFixed(4)
}
var imageId = viewport.getCurrentImageId()
if (imageId) {
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}`
this.imageInfo.location = imagePlaneModule.sliceLocation
this.getOrientationMarker()
this.sliderBoxHeight = imageIndex * 100 / (this.seriesInfo.imageMaxLength - 1)
}
var properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const windowWidth = upper - lower
const windowCenter = (upper + lower) / 2
this.defaultWindowLevel.windowWidth = windowWidth
this.defaultWindowLevel.windowCenter = windowCenter
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
if (this.presetName) {
this.renderColorBar(this.presetName)
}
}
},
getOrientationMarker() {
// eslint-disable-next-line no-unused-vars
const { viewUp, viewPlaneNormal } = viewport.getCamera()
const viewRight = vec3.create()
vec3.cross(viewRight, viewUp, viewPlaneNormal)
const columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]]
const rowCosines = viewRight
const rowString = toolsUtilities.orientation.getOrientationStringLPS(rowCosines)
const columnString = toolsUtilities.orientation.getOrientationStringLPS(columnCosines)
const oppositeRowString = toolsUtilities.orientation.invertOrientationStringLPS(rowString)
const oppositeColumnString = toolsUtilities.orientation.invertOrientationStringLPS(columnString)
this.markers.top = oppositeColumnString
this.markers.right = rowString
this.markers.bottom = columnString
this.markers.left = oppositeRowString
this.orientationMarkers = [oppositeColumnString, rowString, columnString, oppositeRowString]
if (this.originalMarkers.length === 0) {
this.originalMarkers = [...this.orientationMarkers]
}
},
rotateMarkers(type) {
var markers = [...this.orientationMarkers]
if (type === 4) {
// 90
this.orientationMarkers = markers.slice(1, 4).concat(markers[0])
} else if (type === 5) {
// 90
this.orientationMarkers = [markers[3]].concat(markers.slice(0, 3))
}
this.setMarkers()
},
resetMarks() {
this.orientationMarkers = [...this.originalMarkers]
this.setMarkers()
},
setMarkers() {
var markers = [...this.orientationMarkers]
for (const key in this.markers) {
var v = markers.shift(0)
this.markers[key] = v
}
},
handleCameraModified(e) {
renderingEngine = getRenderingEngine(this.renderingEngineId)
viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
var zoom = viewport.getZoom()
if (!zoom) return
this.imageInfo.zoom = zoom.toFixed(4)
// console.log(e)
},
handleVOIModified(e) {
renderingEngine = getRenderingEngine(this.renderingEngineId)
viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
var properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
lower,
upper
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
},
handleMouseMove(e) {
if (this.index !== 4) {
const { currentPoints } = e.detail
const worldPoint = currentPoints.world
const { imageData } = this.volume
const index = imageData.worldToIndex(worldPoint)
index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2])
this.mousePosition.index = index
this.mousePosition.value = this.getValue(this.volume, worldPoint)
}
},
getValue(volume, worldPos) {
const { dimensions, scalarData, imageData } = volume
const index = imageData.worldToIndex(worldPos)
index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2])
if (!utilities.indexWithinDimensions(index, dimensions)) {
return
}
const yMultiple = dimensions[0]
const zMultiple = dimensions[0] * dimensions[1]
const value = scalarData[index[2] * zMultiple + index[1] * yMultiple + index[0]]
return value
},
handleMouseLeave(e) {
this.mousePosition.index = []
this.mousePosition.value = null
},
goViewer(e) {
var height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
this.sliderBoxHeight = height
var index = Math.trunc(this.seriesInfo.imageMaxLength * this.sliderBoxHeight / 100)
if (this.seriesInfo.imageIdIndex !== index) {
this.scroll(index)
}
},
sliderMousedown(e) {
var boxHeight = this.$refs['sliderBox'].clientHeight
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
this.sliderInfo.oldM = e.clientY
this.sliderInfo.isMove = true
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
sliderMousemove(e) {
if (!this.sliderInfo.isMove) return
var PX = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
var boxHeight = this.$refs['sliderBox'].clientHeight
//
if (PX < 0) return
if (PX > boxHeight) return
const height = PX * 100 / boxHeight
var index = Math.trunc(this.seriesInfo.imageMaxLength * height / 100)
index = index > this.seriesInfo.imageMaxLength ? this.seriesInfo.imageMaxLength : index < 0 ? 0 : index
this.sliderBoxHeight = height
if (this.seriesInfo.imageIdIndex !== index) {
this.scroll(index)
}
},
handletoolsMouseWheel(e) {
const { viewportId, wheel } = e.detail
if (viewportId === 'PET_MIP_CORONAL') {
const container = document.getElementById('rotateBar')
const slider = document.getElementById('rotateSlider')
const containerWidth = container.offsetWidth
const sliderWidth = slider.offsetWidth
const maxX = containerWidth - sliderWidth
const { direction } = wheel
var x = Math.trunc(30 * ((containerWidth - sliderWidth) / 360))
if (direction > 0 && (this.rotateBarLeft + x) > maxX) {
this.rotateBarLeft = x - (containerWidth - sliderWidth - this.rotateBarLeft)
} else if (direction < 0 && (this.rotateBarLeft < x)) {
this.rotateBarLeft = containerWidth - (x - this.rotateBarLeft + sliderWidth)
} else {
this.rotateBarLeft = x * direction + this.rotateBarLeft
}
}
},
rotateBarMousemove(e) {
//
if (!this.rotateBarInfo.isMove) return
const container = document.getElementById('rotateBar')
const slider = document.getElementById('rotateSlider')
const containerWidth = container.offsetWidth
const sliderWidth = slider.offsetWidth
let x = Math.trunc(e.clientX - this.rotateBarInfo.initX + this.rotateBarInfo.initLeft)
if (x < 0) x = 0
if (x > containerWidth - sliderWidth) x = containerWidth - sliderWidth
const deltaX = x - this.rotateBarLeft
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
this.rotate(angle)
this.rotateBarLeft = x
},
rotateBarMousedown(e) {
console.log('rotateBarMousedown')
this.rotateBarInfo.initLeft = e.srcElement.offsetLeft
this.rotateBarInfo.initX = e.clientX
this.rotateBarInfo.isMove = true
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
rotate(angle) {
renderingEngine = getRenderingEngine(this.renderingEngineId)
viewport = renderingEngine.getViewport(this.viewportId)
const camera = viewport.getCamera()
const { viewUp, position, focalPoint } = camera
const [cx, cy, cz] = focalPoint
const [ax, ay, az] = [0, 0, 1]
const newPosition = [0, 0, 0]
const newFocalPoint = [0, 0, 0]
const newViewUp = [0, 0, 0]
const transform = mat4.identity(new Float32Array(16))
mat4.translate(transform, transform, [cx, cy, cz])
mat4.rotate(transform, transform, angle, [ax, ay, az])
mat4.translate(transform, transform, [-cx, -cy, -cz])
vec3.transformMat4(newPosition, position, transform)
vec3.transformMat4(newFocalPoint, focalPoint, transform)
mat4.identity(transform)
mat4.rotate(transform, transform, angle, [ax, ay, az])
vec3.transformMat4(newViewUp, viewUp, transform)
viewport.setCamera({
position: newPosition,
viewUp: newViewUp,
focalPoint: newFocalPoint
})
viewport.render()
},
clickRotate(e) {
// console.log('clickRotate')
const container = document.getElementById('rotateBar')
const containerWidth = container.offsetWidth
const slider = document.getElementById('rotateSlider')
const sliderWidth = slider.offsetWidth
const x = Math.trunc(e.offsetX)
const deltaX = x - this.rotateBarLeft
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
this.rotate(angle)
this.rotateBarLeft = x
},
async scroll(index) {
renderingEngine = getRenderingEngine(this.renderingEngineId)
viewport = renderingEngine.getViewport(this.viewportId)
const actorEntries = viewport.getActors()
if (!actorEntries) {
return
}
// const delta = index - this.seriesInfo.imageIdIndex
// toolsUtilities.scroll(viewport, {
// delta,
// volumeId: actorEntries.uid
// })
await utilities.jumpToSlice(viewport.element, {
imageIndex: index,
});
renderingEngine.render()
},
rotateBarMouseup(e) {
this.rotateBarInfo.isMove = false
},
sliderMouseup(e) {
this.sliderInfo.isMove = false
},
setAnnotation(imageId, element) {
this.measureDatas.forEach(item => {
if (item.OtherMeasureData) {
var { metadata, annotationUID } = item.OtherMeasureData
var { referencedImageId } = metadata
console.log(annotationUID, annotation.state.getAnnotation(annotationUID))
if (!annotation.state.getAnnotation(annotationUID) && referencedImageId === imageId) {
annotation.state.addAnnotation(item.OtherMeasureData, element)
}
}
})
},
setPreset(presetName) {
this.presetName = presetName
},
renderColorBar(presetName) {
var colorMap = null
if (presetName === 'hsv') {
colorMap = vtkColorMaps.getPresetByName(presetName)
} else {
colorMap = getColormap(presetName)
}
const rgbPoints = colorMap.RGBPoints
const canvas = document.getElementById('colorBar_Canvas')
const ctx = canvas.getContext('2d')
const canvasWidth = 160
const canvasHeight = 5
const rectWidth = 160
const rectHeight = canvasHeight
canvas.width = canvasWidth
canvas.height = canvasHeight
const gradient = ctx.createLinearGradient(0, 0, rectWidth, 0)
for (let i = 0; i < rgbPoints.length; i += 4) {
let position = 0
if (rgbPoints[0] === -1) {
position = (rgbPoints[i] + 1) / 2
} else {
position = rgbPoints[i]
}
const color = `rgb(${parseInt(rgbPoints[i + 1] * 255)}, ${parseInt(rgbPoints[i + 2] * 255)}, ${parseInt(rgbPoints[i + 3] * 255)})`
gradient.addColorStop(position, color)
}
ctx.fillStyle = gradient
ctx.fillRect(0, 0, rectWidth, rectHeight)
},
setWwWc() {
var properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const windowWidth = upper - lower
const windowCenter = (upper + lower) / 2
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
},
async getScreenshots() {
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="FUSION_AXIAL"]`
)
// const divForDownloadViewport = document.querySelector(
// '.viewport_container'
// )
var canvas = await html2canvas(divForDownloadViewport)
var pictureBaseStr = canvas.toDataURL('image/png', 1)
return pictureBaseStr
}
}
}
</script>
<style lang="scss" scoped>
.viewport_container{
width:100%;
height: 100%;
.seriesInfo_wrapper {
position: absolute;
left: 10px;
top: 10px;
text-align: left;
font-size: 12px;
z-index: 1;
.taskInfo_container{
color:#f44336;
padding: 5px 0px;
margin: 0;
}
}
.descInfo_wrapper{
position: absolute;
right: 20px;
top: 10px;
text-align: right;
font-size: 12px;
z-index: 1;
}
.imageInfo_wrapper_l{
position: absolute;
left: 10px;
bottom: 5px;
text-align: left;
font-size: 12px;
z-index: 1;
}
.imageInfo_wrapper_r{
position: absolute;
right: 40px;
bottom: 5px;
text-align: right;
font-size: 12px;
z-index: 1;
}
.slider_box{
position: absolute;
right: 1px;
height: calc(100% - 120px);
transform: translateY(-50%);
top: calc(50% - 30px);
width: 10px;
background: #333;
cursor: pointer;
.box{
z-index:10;
background: #9e9e9e;
height: 20px;
width: 100%;
position: absolute;
top: 0;
cursor: move
}
}
.color_bar{
position: absolute;
// transform:translateY(-50%);
transform: rotate(-90deg);
transform-origin: right;
left: -150px;
top: 30%;
// transform-origin: top left;
z-index: 1;
// background: #f44336;
}
.rotate_slider_box{
position: absolute;
width: 380px;
height: 10px;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
background: #333;
cursor: pointer;
.box{
z-index:10;
background: #9e9e9e;
height: 100%;
width: 20px;
position: absolute;
top: 0;
cursor: move
}
}
// .my_slider_box:after{
// content: '';
// position: absolute;
// bottom: -20px;
// left: 0;
// height: 20px;
// width: 100%;
// background: #333;
// }
}
</style>

View File

@ -1,10 +1,31 @@
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader' import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
import {
getImageTypeSubItemFromDataset,
extractOrientationFromDataset,
extractPositionFromDataset,
extractSpacingFromDataset,
extractSliceThicknessFromDataset,
} from "@/utils/extractPositioningFromDataset";
import getNumberValues from "@/utils/getNumberValues";
import isNMReconstructable from "@/utils/isNMReconstructable";
function getSpacingBetweenSlices(dataSet) {
if (dataSet?.elements?.x00180088) {
return dataSet.floatString("x00180088");
}
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
if (
pixelMeasuresSequence?.items?.length &&
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
) {
return pixelMeasuresSequence.items[0].dataSet.floatString("x00180088");
}
}
function parseImageId(imageId) { function parseImageId(imageId) {
// build a url by parsing out the url scheme and frame index from the imageId // build a url by parsing out the url scheme and frame index from the imageId
const firstColonIndex = imageId.indexOf(':'); const firstColonIndex = imageId.indexOf(":");
let url = imageId.substring(firstColonIndex + 1); let url = imageId.substring(firstColonIndex + 1);
const frameIndex = url.indexOf('frame='); const frameIndex = url.indexOf("frame=");
let frame; let frame;
@ -21,127 +42,112 @@ function parseImageId(imageId) {
frame, frame,
}; };
} }
function getNumberValues(dataSet, tag, minimumLength) {
const values = [];
const valueAsString = dataSet.string(tag);
if (!valueAsString) { function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId);
const dataSet = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.get(
parsedImageId.url
);
if (!dataSet) {
return; return;
} }
const split = valueAsString.split('\\'); if (type === "imagePlaneModule") {
const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
// const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
// let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
// const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
// const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
const imageOrientationPatient = extractOrientationFromDataset(dataSet);
let imagePositionPatient = extractPositionFromDataset(dataSet);
const pixelSpacing = extractSpacingFromDataset(dataSet);
const sliceThickness = extractSliceThicknessFromDataset(dataSet);
const modality = dataSet.string("x00080060");
if (
modality &&
modality.includes("NM") &&
parsedImageId.frame !== undefined &&
parsedImageId.frame > 1
) {
// 坐标堆叠 (位置相同) NM 多帧 DICOM 的空间位置 ( imagePositionPatient ) 通常只存在于 Detector Information Sequence 序列的第一个 Item 中。原来的代码在提取位置时所有帧都被赋予了第一帧的位置。因为所有帧都挤在一个坐标上3D 引擎无法根据厚度和法向量空间展开它们,导致间距 (Spacing) 为 0
// 当识别到 NM 多帧图像且属于多帧切片时,通过 RowCosines 与 ColumnCosines 的叉乘计算出切片的 法向量(Normal) ,并结合 帧索引 (frameIndex) 以及 Spacing Between Slices (0018,0088)(层间距) 或者 Slice Thickness (0018,0050)(层厚) ,自动推算补全后续每一帧的 imagePositionPatient 真实三维坐标
//
if (
imageOrientationPatient &&
imagePositionPatient &&
sliceThickness !== undefined
) {
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
const step =
spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
const rowCosines = [
parseFloat(imageOrientationPatient[0]),
parseFloat(imageOrientationPatient[1]),
parseFloat(imageOrientationPatient[2]),
];
const columnCosines = [
parseFloat(imageOrientationPatient[3]),
parseFloat(imageOrientationPatient[4]),
parseFloat(imageOrientationPatient[5]),
];
const normal = [
rowCosines[1] * columnCosines[2] - rowCosines[2] * columnCosines[1],
rowCosines[2] * columnCosines[0] - rowCosines[0] * columnCosines[2],
rowCosines[0] * columnCosines[1] - rowCosines[1] * columnCosines[0],
];
if (minimumLength && split.length < minimumLength) { const offset = frameIndex * parseFloat(step);
return;
}
for (let i = 0; i < split.length; i++) {
values.push(parseFloat(split[i]));
}
return values; imagePositionPatient = [
} parseFloat(imagePositionPatient[0]) + normal[0] * offset,
function getImageTypeSubItemFromDataset(dataSet, index) { parseFloat(imagePositionPatient[1]) + normal[1] * offset,
const imageType = dataSet.string('x00080008'); parseFloat(imagePositionPatient[2]) + normal[2] * offset,
if (imageType) { ];
const subTypes = imageType.split('\\');
if (subTypes.length > index) {
return subTypes[index];
} }
} }
return undefined;
} // const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
function isNMReconstructable(imageSubType) { // const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO'; // const pixelSpacing = extractSpacingFromDataset(dataSet);
} const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
function extractOrientationFromNMMultiframeDataset(dataSet) {
let imageOrientationPatient; // let rowCosines = null;
const modality = dataSet.string('x00080060');
if (modality?.includes('NM')) { // let columnCosines = null;
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
if (imageSubType && isNMReconstructable(imageSubType)) { // if (imageOrientationPatient) {
if (dataSet.elements.x00540022) { // rowCosines = [
imageOrientationPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200037', 6); // parseFloat(imageOrientationPatient[0]),
} // parseFloat(imageOrientationPatient[1]),
} // parseFloat(imageOrientationPatient[2]),
} // ];
return imageOrientationPatient; // columnCosines = [
} // parseFloat(imageOrientationPatient[3]),
function extractOrientationFromDataset(dataSet) { // parseFloat(imageOrientationPatient[4]),
let imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6); // parseFloat(imageOrientationPatient[5]),
if (!imageOrientationPatient && dataSet.elements.x00209116) { // ];
imageOrientationPatient = getNumberValues(dataSet.elements.x00209116.items[0].dataSet, 'x00200037', 6); // }
} const estimatedRadiographicMagnificationFactor = getNumberValues(
if (!imageOrientationPatient) { dataSet,
imageOrientationPatient = "x00181114",
extractOrientationFromNMMultiframeDataset(dataSet); 2
} );
return imageOrientationPatient; let columnPixelSpacing = null;
}
function extractPositionFromNMMultiframeDataset(dataSet) { let rowPixelSpacing = null;
let imagePositionPatient;
const modality = dataSet.string('x00080060'); if (pixelSpacing) {
if (modality?.includes('NM')) { rowPixelSpacing = pixelSpacing[0];
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2); columnPixelSpacing = pixelSpacing[1];
if (imageSubType && isNMReconstructable(imageSubType)) {
if (dataSet.elements.x00540022) {
imagePositionPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200032', 3);
}
}
}
return imagePositionPatient;
}
function extractPositionFromDataset(dataSet) {
let imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
if (!imagePositionPatient && dataSet.elements.x00209113) {
imagePositionPatient = getNumberValues(dataSet.elements.x00209113.items[0].dataSet, 'x00200032', 3);
}
if (!imagePositionPatient) {
imagePositionPatient = extractPositionFromNMMultiframeDataset(dataSet);
}
return imagePositionPatient;
}
function extractSliceThicknessFromDataset(dataSet) {
let sliceThickness;
if (dataSet.elements.x00180050) {
sliceThickness = dataSet.floatString('x00180050');
}
else if (dataSet.elements.x00289110 &&
dataSet.elements.x00289110.items.length &&
dataSet.elements.x00289110.items[0].dataSet.elements.x00180050) {
sliceThickness =
dataSet.elements.x00289110.items[0].dataSet.floatString('x00180050');
}
return sliceThickness;
}
function extractSpacingFromDataset(dataSet) {
let rowPixelSpacing = null
let columnPixelSpacing = null
let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
if (pixelSpacing) {
rowPixelSpacing = pixelSpacing[0]
columnPixelSpacing = pixelSpacing[1]
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) { } else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
rowPixelSpacing = imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0]; rowPixelSpacing =
columnPixelSpacing = imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1]; imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
columnPixelSpacing =
imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) { } else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
rowPixelSpacing = imagePixelSpacing[0]; rowPixelSpacing = imagePixelSpacing[0];
columnPixelSpacing = imagePixelSpacing[1]; columnPixelSpacing = imagePixelSpacing[1];
} }
return rowPixelSpacing !== null ? [rowPixelSpacing, columnPixelSpacing] : undefined
}
function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId);
const dataSet = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url)
if (!dataSet) {
return;
}
if (type === 'imagePlaneModule') {
const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
const pixelSpacing = extractSpacingFromDataset(dataSet);
let rowCosines = null; let rowCosines = null;
@ -160,34 +166,35 @@ function metaDataProvider(type, imageId) {
]; ];
} }
return { return {
frameOfReferenceUID: dataSet.string('x00200052'), frameOfReferenceUID: dataSet.string("x00200052"),
rows: dataSet.uint16('x00280010'), rows: dataSet.uint16("x00280010"),
columns: dataSet.uint16('x00280011'), columns: dataSet.uint16("x00280011"),
imageOrientationPatient, imageOrientationPatient,
rowCosines, rowCosines,
columnCosines, columnCosines,
imagePositionPatient, imagePositionPatient,
sliceThickness: dataSet.floatString('x00180050'), sliceThickness,
sliceLocation: dataSet.floatString('x00201041'), sliceLocation: dataSet.floatString("x00201041"),
pixelSpacing: pixelSpacing, pixelSpacing,
rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : null, rowPixelSpacing,
columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : null, columnPixelSpacing,
}; };
} }
if (type === 'nmMultiframeGeometryModule') { if (type === "nmMultiframeGeometryModule") {
const modality = dataSet.string('x00080060'); const modality = dataSet.string("x00080060");
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2); const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
return { return {
modality, modality,
imageType: dataSet.string('x00080008'), imageType: dataSet.string("x00080008"),
imageSubType, imageSubType,
imageOrientationPatient: extractOrientationFromDataset(dataSet), imageOrientationPatient: extractOrientationFromDataset(dataSet),
imagePositionPatient: extractPositionFromDataset(dataSet), imagePositionPatient: extractPositionFromDataset(dataSet),
sliceThickness: extractSliceThicknessFromDataset(dataSet), sliceThickness: extractSliceThicknessFromDataset(dataSet),
pixelSpacing: extractSpacingFromDataset(dataSet), pixelSpacing: extractSpacingFromDataset(dataSet),
numberOfFrames: dataSet.uint16('x00280008'), numberOfFrames: dataSet.uint16("x00280008"),
isNMReconstructable: isNMReconstructable(imageSubType) && modality.includes('NM'), isNMReconstructable:
}; isNMReconstructable(imageSubType) && modality.includes("NM"),
};
} }
} }
export default metaDataProvider; export default metaDataProvider;

View File

@ -1319,12 +1319,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -1335,7 +1335,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1343,7 +1343,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1354,7 +1354,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -112,9 +112,8 @@
</el-input> </el-input>
<el-input <el-input
v-else v-else
type="number"
@change="(val) => { formItemChange(val, item) }" @change="(val) => { formItemChange(val, item) }"
@input="(val) => questionForm[item.Id] = val.replace(/[^\d.]/g, '')" @input="numberInput(item.Id)"
@blur="handleBlur(questionForm[item.Id], questionForm, item)" @blur="handleBlur(questionForm[item.Id], questionForm, item)"
v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2"> v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2">
<template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template> <template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template>
@ -369,7 +368,7 @@ export default {
const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation'] const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation']
questions.forEach(item => { questions.forEach(item => {
if (type.includes(item.Type)) { if (type.includes(item.Type)) {
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Type === 'number' || item.Type === 'calculation' ? isNaN(parseFloat(item.Answer)) ? null : item.Answer : item.Answer const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Answer
this.$set(this.questionForm, item.Id, answer) this.$set(this.questionForm, item.Id, answer)
} }
if (item.QuestionType === 1013) { if (item.QuestionType === 1013) {
@ -498,7 +497,6 @@ export default {
}, },
formItemChange(val, item) { formItemChange(val, item) {
console.log('formItemChange: ', item.QuestionName, val)
this.formChanged = true this.formChanged = true
// if (item.Type === 'number') { // if (item.Type === 'number') {
// this.limitBlur(item.Id, item.ValueType) // this.limitBlur(item.Id, item.ValueType)
@ -546,6 +544,7 @@ export default {
} }
}, },
numberOrNEInput(id) { numberOrNEInput(id) {
this.formChanged = true
// this.questionForm[id] = this.questionForm[id].toUpperCase(); // this.questionForm[id] = this.questionForm[id].toUpperCase();
if (!this.questionForm[id]) { if (!this.questionForm[id]) {
return return
@ -564,6 +563,11 @@ export default {
this.questionForm[id] = value this.questionForm[id] = value
this.$set(this.questionForm, id, value) this.$set(this.questionForm, id, value)
}, },
numberInput(id) {
this.formChanged = true
let value = this.questionForm[id].replace(/[^\d.]/g, '')
this.$set(this.questionForm, id, value)
},
handleNumberOrNEBlur(item) { handleNumberOrNEBlur(item) {
if (this.questionForm[item.Id] && !isNaN(parseFloat(this.questionForm[item.Id]))) { if (this.questionForm[item.Id] && !isNaN(parseFloat(this.questionForm[item.Id]))) {
if (item.ValueType === 3) { if (item.ValueType === 3) {
@ -802,7 +806,7 @@ export default {
uploadTpl(lesionType, TableName) { uploadTpl(lesionType, TableName) {
this.upload.lesionType = lesionType this.upload.lesionType = lesionType
this.upload.TableName = TableName this.upload.TableName = TableName
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} ` this.upload.title = `${this.$fd('LesionType', lesionType)}`
this.upload.visible = true this.upload.visible = true
}, },
async downloadTpl() { async downloadTpl() {
@ -834,9 +838,8 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.measurement-wrapper { .measurement-wrapper {
height: 100%; height: calc(100% - 50px);
overflow-y: auto; overflow-y: auto;
.container { .container {
padding: 10px; padding: 10px;
@ -986,21 +989,21 @@ export default {
padding: 5px 0; padding: 5px 0;
} }
::v-deep.el-table__fixed-right-patch { ::v-deep .el-table__fixed-right-patch {
background-color: #000 !important; background-color: #000 !important;
border-color: #444444; border-color: #444444;
} }
::v-deep.el-table__fixed-body-wrapper tr:hover>td { ::v-deep .el-table__fixed-body-wrapper tr:hover>td {
background-color: #000 !important; background-color: #000 !important;
} }
::v-deep.el-table--scrollable-x .el-table__body-wrapper { ::v-deep .el-table--scrollable-x .el-table__body-wrapper {
z-index: 2; z-index: 2;
} }
} }
::v-deep.el-tag.el-tag--info { ::v-deep .el-tag.el-tag--info {
color: #000; color: #000;
} }
} }

View File

@ -56,11 +56,11 @@ export default {
var data = new FormData() var data = new FormData()
data.append('file', param.file) data.append('file', param.file)
data.append('visitTaskId', this.visitTaskId) data.append('visitTaskId', this.visitTaskId)
data.append('readingImportType', 0) data.append('readingImportType', 3)
data.append('TableName', this.TableName) data.append('TableName', this.TableName)
await readingImport(data) await readingImport(data)
this.$emit('close') this.$emit('close')
this.$message.success('导入成功!') this.$message.success(this.$t('common:message:savedSuccessfully'))
loading.close() loading.close()
} catch (e) { } catch (e) {
loading.close() loading.close()

View File

@ -1436,12 +1436,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -1452,7 +1452,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1460,7 +1460,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1471,7 +1471,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -1436,12 +1436,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -1452,7 +1452,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1460,7 +1460,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1471,7 +1471,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -67,13 +67,13 @@
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="readingTaskState < 2" :label="$t('common:action:action')" width="90px" <el-table-column v-if="readingTaskState < 2 && item.LesionType === 104" :label="$t('common:action:action')" width="90px"
fixed="right"> fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" @click="handleAddOrEdit('edit', item, scope.$index)"> <el-button type="text" size="mini" @click="handleAddOrEdit('edit', item, scope.$index)">
{{ $t('common:button:edit') }} {{ $t('common:button:edit') }}
</el-button> </el-button>
<el-button v-if="item.LesionType === 112 || item.LesionType === 111" type="text" size="mini" <el-button type="text" size="mini"
@click="handleDelete(item, scope.$index)"> @click="handleDelete(item, scope.$index)">
{{ $t('common:button:delete') }} {{ $t('common:button:delete') }}
</el-button> </el-button>
@ -120,9 +120,8 @@
</el-input> </el-input>
<el-input <el-input
v-else v-else
type="number"
@change="(val) => { formItemChange(val, item) }" @change="(val) => { formItemChange(val, item) }"
@input="(val) => questionForm[item.Id] = val.replace(/[^\d.]/g, '')" @input="numberInput(item.Id)"
@blur="handleBlur(questionForm[item.Id], questionForm, item)" @blur="handleBlur(questionForm[item.Id], questionForm, item)"
v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2"> v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2">
<template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template> <template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template>
@ -369,7 +368,7 @@ export default {
const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation'] const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation']
questions.forEach(item => { questions.forEach(item => {
if (type.includes(item.Type)) { if (type.includes(item.Type)) {
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Type === 'number' || item.Type === 'calculation' ? isNaN(parseFloat(item.Answer)) ? null : item.Answer : item.Answer const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Answer
this.$set(this.questionForm, item.Id, answer) this.$set(this.questionForm, item.Id, answer)
} }
if (item.QuestionType === 1013) { if (item.QuestionType === 1013) {
@ -541,6 +540,7 @@ export default {
} }
}, },
numberOrNEInput(id) { numberOrNEInput(id) {
this.formChanged = true
// this.questionForm[id] = this.questionForm[id].toUpperCase(); // this.questionForm[id] = this.questionForm[id].toUpperCase();
if (!this.questionForm[id]) { if (!this.questionForm[id]) {
return return
@ -559,6 +559,11 @@ export default {
this.questionForm[id] = value this.questionForm[id] = value
this.$set(this.questionForm, id, value) this.$set(this.questionForm, id, value)
}, },
numberInput(id) {
this.formChanged = true
let value = this.questionForm[id].replace(/[^\d.]/g, '')
this.$set(this.questionForm, id, value)
},
handleNumberOrNEBlur(item) { handleNumberOrNEBlur(item) {
if (this.questionForm[item.Id] && !this.questionForm[item.Id].startsWith('N')) { if (this.questionForm[item.Id] && !this.questionForm[item.Id].startsWith('N')) {
if (item.ValueType === 3) { if (item.ValueType === 3) {
@ -682,11 +687,27 @@ export default {
const m1 = this.qsForm[this.m1Id] const m1 = this.qsForm[this.m1Id]
const m2 = this.qsForm[this.m2Id] const m2 = this.qsForm[this.m2Id]
const m3 = this.qsForm[this.m3Id] const m3 = this.qsForm[this.m3Id]
if (isNaN(parseFloat(m1)) || isNaN(parseFloat(m2)) || isNaN(parseFloat(m3))) { // if (isNaN(parseFloat(m1)) || isNaN(parseFloat(m2)) || isNaN(parseFloat(m3))) {
this.$set(this.qsForm, this.avgId, null) // this.$set(this.qsForm, this.avgId, null)
} else { // } else {
const avg = (parseFloat(m1) + parseFloat(m2) + parseFloat(m3)) / 3 // const avg = (parseFloat(m1) + parseFloat(m2) + parseFloat(m3)) / 3
// this.$set(this.qsForm, this.avgId, this.numberToFixed(avg))
// }
//
const validNumbers = []
if (!isNaN(parseFloat(m1))) validNumbers.push(parseFloat(m1))
if (!isNaN(parseFloat(m2))) validNumbers.push(parseFloat(m2))
if (!isNaN(parseFloat(m3))) validNumbers.push(parseFloat(m3))
//
if (validNumbers.length > 0) {
// /
const sum = validNumbers.reduce((a, b) => a + b, 0)
const avg = sum / validNumbers.length
this.$set(this.qsForm, this.avgId, this.numberToFixed(avg)) this.$set(this.qsForm, this.avgId, this.numberToFixed(avg))
} else {
//
this.$set(this.qsForm, this.avgId, null)
} }
} }
}, },
@ -805,7 +826,7 @@ export default {
uploadTpl(lesionType, TableName) { uploadTpl(lesionType, TableName) {
this.upload.lesionType = lesionType this.upload.lesionType = lesionType
this.upload.TableName = TableName this.upload.TableName = TableName
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} ` this.upload.title = `${this.$fd('LesionType', lesionType)}`
this.upload.visible = true this.upload.visible = true
}, },
async downloadTpl(lesionType) { async downloadTpl(lesionType) {
@ -813,7 +834,7 @@ export default {
const params = { const params = {
visitTaskId: this.visitTaskId visitTaskId: this.visitTaskId
} }
if (lesionType === 112) { if (lesionType === 104) {
await getOCTFCTTemplate(params) await getOCTFCTTemplate(params)
} else { } else {
await getOCTLipidAngleTemplate(params) await getOCTLipidAngleTemplate(params)
@ -841,7 +862,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.measurement-wrapper { .measurement-wrapper {
height: 100%; height: calc(100% - 50px);
overflow-y: auto; overflow-y: auto;
// overflow: hidden; // overflow: hidden;
@ -994,20 +1015,20 @@ export default {
padding: 5px 0; padding: 5px 0;
} }
::v-deep.el-table__fixed-right-patch { ::v-deep .el-table__fixed-right-patch {
background-color: #000 !important; background-color: #000 !important;
border-color: #444444; border-color: #444444;
} }
::v-deep.el-table__fixed-body-wrapper tr:hover>td { ::v-deep .el-table__fixed-body-wrapper tr:hover>td {
background-color: #000 !important; background-color: #000 !important;
} }
::v-deep.el-table--scrollable-x .el-table__body-wrapper { ::v-deep .el-table--scrollable-x .el-table__body-wrapper {
z-index: 2; z-index: 2;
} }
} }
::v-deep.el-tag.el-tag--info { ::v-deep .el-tag.el-tag--info {
color: #000; color: #000;
} }
} }

View File

@ -72,15 +72,17 @@ export default {
data.append('file', param.file) data.append('file', param.file)
data.append('visitTaskId', this.visitTaskId) data.append('visitTaskId', this.visitTaskId)
data.append('TableName', this.TableName) data.append('TableName', this.TableName)
if (this.lesionType === 112) { data.append('readingImportType', 3)
data.append('readingImportType', 1) await readingImport(data)
await readingImport(data) // if (this.lesionType === 112) {
} else { // data.append('readingImportType', 1)
data.append('readingImportType', 2) // await readingImport(data)
await readingImport(data) // } else {
} // data.append('readingImportType', 2)
// await readingImport(data)
// }
this.$emit('close') this.$emit('close')
this.$message.success('导入成功!') this.$message.success(this.$t('common:message:savedSuccessfully'))
loading.close() loading.close()
} catch (e) { } catch (e) {
loading.close() loading.close()

View File

@ -91,7 +91,7 @@
@change="((val) => { formItemChange(val, question) })" /> @change="((val) => { formItemChange(val, question) })" />
<!-- 下拉框 --> <!-- 下拉框 -->
<el-select v-else-if="question.Type === 'select'" v-model="questionForm[question.Id]" <el-select v-else-if="question.Type === 'select'" v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode) || isFirstChangeTask || question.QuestionType === 50 || question.QuestionType === 55" :disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode) || isFirstChangeTask || question.QuestionType === 50 || question.QuestionType === 55 || question.QuestionType === 1026"
clearable :multiple="question.OptionTypeEnum === 1" @change="((val) => { formItemChange(val, question) })"> clearable :multiple="question.OptionTypeEnum === 1" @change="((val) => { formItemChange(val, question) })">
<template v-if="question.TableQuestionType === 1"> <template v-if="question.TableQuestionType === 1">
<el-option v-for="item in organList" :key="item.Id" :label="item[question.DataTableColumn]" <el-option v-for="item in organList" :key="item.Id" :label="item[question.DataTableColumn]"

View File

@ -234,7 +234,10 @@ export default {
obj.forEach(i => { obj.forEach(i => {
i.IsBaseLineTask = this.isBaseLineTask i.IsBaseLineTask = this.isBaseLineTask
if (i.Type !== 'group' && i.Type !== 'summary' && i.Id) { if (i.Type !== 'group' && i.Type !== 'summary' && i.Id) {
const answer = i.Type === 'select' && i.OptionTypeEnum === 1 && i.Answer ? JSON.parse(i.Answer) : i.Answer let answer = i.Type === 'select' && i.OptionTypeEnum === 1 && i.Answer ? JSON.parse(i.Answer) : i.Answer
if (i.DictionaryCode && i.Type === 'calculation') {
answer = this.$fd(i.DictionaryCode, parseInt(i.Answer))
}
this.$set(this.questionForm, i.Id, answer ? answer : null) this.$set(this.questionForm, i.Id, answer ? answer : null)
if (i.QuestionType === 44) { if (i.QuestionType === 44) {
// //

View File

@ -614,7 +614,7 @@ export default {
} }
::v-deep.el-tabs { ::v-deep .el-tabs {
box-sizing: border-box; box-sizing: border-box;
padding: 0 5px; padding: 0 5px;
height: 100%; height: 100%;
@ -665,7 +665,7 @@ export default {
border: 1px solid #607d8b !important; border: 1px solid #607d8b !important;
} }
::v-deep.el-progress__text { ::v-deep .el-progress__text {
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }

View File

@ -1203,12 +1203,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -1219,7 +1219,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1227,7 +1227,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1238,7 +1238,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -1130,12 +1130,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -1146,7 +1146,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1154,7 +1154,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -1165,7 +1165,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -952,7 +952,7 @@ export default {
background-color: #607d8b!important; background-color: #607d8b!important;
border: 1px solid #607d8b!important; border: 1px solid #607d8b!important;
} }
::v-deep.el-progress__text{ ::v-deep .el-progress__text{
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
@ -1022,7 +1022,7 @@ export default {
} }
} }
::v-deep.el-collapse{ ::v-deep .el-collapse{
border: none; border: none;
.el-collapse-item{ .el-collapse-item{
background-color: #000!important; background-color: #000!important;
@ -1043,7 +1043,7 @@ export default {
} }
} }
.sr-wrapper{ .sr-wrapper{
::v-deep.el-dialog{ ::v-deep .el-dialog{
background: #fff !important; background: #fff !important;
border: 1px solid #ddd; border: 1px solid #ddd;
// color: #ddd; // color: #ddd;
@ -1051,12 +1051,12 @@ export default {
color:#fff; color:#fff;
} }
} }
::v-deep.sr-dialog-container{ ::v-deep .sr-dialog-container{
margin-top: 50px !important; margin-top: 50px !important;
width:75%; width:75%;
height:80%; height:80%;
} }
::v-deep.el-dialog__body{ ::v-deep .el-dialog__body{
padding: 10px; padding: 10px;
height: calc(100% - 50px); height: calc(100% - 50px);
} }
@ -1064,7 +1064,7 @@ export default {
position: relative; position: relative;
} }
.sr-full-dialog-container{ .sr-full-dialog-container{
::v-deep.is-fullscreen .el-dialog__body{ ::v-deep .is-fullscreen .el-dialog__body{
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }

View File

@ -887,7 +887,7 @@ export default {
background-color: #607d8b!important; background-color: #607d8b!important;
border: 1px solid #607d8b!important; border: 1px solid #607d8b!important;
} }
::v-deep.el-progress__text{ ::v-deep .el-progress__text{
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
@ -956,7 +956,7 @@ export default {
} }
} }
} }
::v-deep.el-collapse{ ::v-deep .el-collapse{
border: none; border: none;
.el-collapse-item{ .el-collapse-item{
background-color: #000!important; background-color: #000!important;
@ -977,7 +977,7 @@ export default {
} }
} }
.sr-wrapper{ .sr-wrapper{
::v-deep.el-dialog{ ::v-deep .el-dialog{
background: #fff !important; background: #fff !important;
border: 1px solid #ddd; border: 1px solid #ddd;
// color: #ddd; // color: #ddd;
@ -985,12 +985,12 @@ export default {
color:#fff; color:#fff;
} }
} }
::v-deep.sr-dialog-container{ ::v-deep .sr-dialog-container{
margin-top: 50px !important; margin-top: 50px !important;
width:75%; width:75%;
height:80%; height:80%;
} }
::v-deep.el-dialog__body{ ::v-deep .el-dialog__body{
padding: 10px; padding: 10px;
height: calc(100% - 50px); height: calc(100% - 50px);
} }
@ -998,7 +998,7 @@ export default {
position: relative; position: relative;
} }
.sr-full-dialog-container{ .sr-full-dialog-container{
::v-deep.is-fullscreen .el-dialog__body{ ::v-deep .is-fullscreen .el-dialog__body{
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }

View File

@ -529,7 +529,7 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep.el-message-box__headerbtn { ::v-deep .el-message-box__headerbtn {
display: none; display: none;
} }
@ -541,7 +541,7 @@ export default {
box-sizing: border-box; box-sizing: border-box;
background-color: #000; background-color: #000;
::v-deep.el-tabs { ::v-deep .el-tabs {
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
display: flex; display: flex;
@ -573,11 +573,11 @@ export default {
} }
::v-deep.hot-keys-label { ::v-deep .hot-keys-label {
color: #dfdfdf !important; color: #dfdfdf !important;
} }
::v-deep.shortcut-key-input span { ::v-deep .shortcut-key-input span {
color: #dfdfdf !important; color: #dfdfdf !important;
} }
@ -588,7 +588,7 @@ export default {
// } // }
// } // }
::v-deep.el-dialog { ::v-deep .el-dialog {
background: #1e1e1e; background: #1e1e1e;
border: 1px solid #ddd; border: 1px solid #ddd;
color: #ddd; color: #ddd;
@ -614,13 +614,13 @@ export default {
} }
::v-deep.dialog-container { ::v-deep .dialog-container {
margin-top: 50px !important; margin-top: 50px !important;
width: 75%; width: 75%;
height: 80%; height: 80%;
} }
::v-deep.el-dialog__body { ::v-deep .el-dialog__body {
padding: 20px; padding: 20px;
height: calc(100% - 70px); height: calc(100% - 70px);
} }
@ -636,7 +636,7 @@ export default {
} }
.full-dialog-container { .full-dialog-container {
::v-deep.is-fullscreen .el-dialog__body { ::v-deep .is-fullscreen .el-dialog__body {
height: calc(100% - 70px); height: calc(100% - 70px);
} }
} }

View File

@ -142,7 +142,7 @@ export default {
getStudyList(obj) { getStudyList(obj) {
if (obj) { if (obj) {
var studyList = obj.StudyList || [] var studyList = obj.StudyList || []
studyList = studyList.filter(i => !i.IsCriticalSequence && (i.Modalities.indexOf('CT') !== -1 || i.Modalities.indexOf('MR') !== -1) && i.Modalities.indexOf('PT') !== -1) studyList = studyList.filter(i => !i.IsCriticalSequence && (i.Modalities.indexOf('CT') !== -1 || i.Modalities.indexOf('MR') !== -1) && (i.Modalities.indexOf('PT') !== -1 || i.Modalities.indexOf('NM') !== -1))
if (studyList.length === 0) return if (studyList.length === 0) return
this.studyList = studyList this.studyList = studyList
} }
@ -200,7 +200,7 @@ export default {
var series = seriesList.filter(series => series.Modality === 'CT' || series.Modality === 'MR') var series = seriesList.filter(series => series.Modality === 'CT' || series.Modality === 'MR')
this.ctSeries = series.sort((a, b) => b.instanceCount - a.instanceCount) this.ctSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
series = seriesList.filter(series => series.Modality === 'PT') series = seriesList.filter(series => series.Modality === 'PT' || series.Modality === 'NM')
this.petSeries = series.sort((a, b) => b.instanceCount - a.instanceCount) this.petSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
} }
}, },
@ -235,12 +235,12 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.series-table { .series-table {
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #dfdfdf; border-bottom: 1px solid #dfdfdf;
} }
@ -251,14 +251,14 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -269,7 +269,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }
} }

View File

@ -0,0 +1,868 @@
<template>
<div ref="viewport-volume" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
@mouseleave="sliderMouseleave" :style="{ color: '#ddd' }">
<div v-if="series && taskInfo" class="left-top-text">
<div v-if="taskInfo.IsExistsClinicalData" class="cd-info" :title="$t('trials:reading:button:clinicalData')">
<svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon"
@click.stop="viewCD(series.TaskInfo.VisitTaskId)" />
</div>
<h2 v-if="taskInfo.IsReadingShowSubjectInfo && series.TaskInfo" class="subject-info">
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
</h2>
<div>Series: #{{ series.SeriesNumber }}</div>
<div v-if="imageInfo.total">Image: #{{ `${series.SliceIndex + 1}/${imageInfo.total}` }}</div>
<div>{{ series.Modality }}</div>
</div>
<div v-if="series" class="right-top-text">
<div>{{ series.Description }}</div>
</div>
<div v-if="series" class="left-bottom-text">
<div v-show="mousePosition.index.length > 0">
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
</div>
<div
v-if="(series.Modality === 'CT' || series.Modality === 'DR' || series.Modality === 'CR') && mousePosition.value">
HU: {{ mousePosition.value }}
</div>
<div v-else-if="(series.Modality === 'PT' && mousePosition.value)">
SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.value.toFixed(3) :
mousePosition.value.toFixed(digitPlaces)
}}
</div>
<div v-else-if="mousePosition.value">
Density: {{ mousePosition.value }}
</div>
<div v-show="imageInfo.size">
W*H: {{ imageInfo.size }}
</div>
</div>
<div v-if="series" class="right-bottom-text">
<div v-show="imageInfo.location">Location: {{
`${Number(imageInfo.location).toFixed(digitPlaces)} mm`
}}</div>
<div v-show="imageInfo.sliceThickness">Slice Thickness: {{
`${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm`
}}</div>
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
</div>
<div class="orientation-top">
{{ markers.top }}
</div>
<div class="orientation-right">
{{ markers.right }}
</div>
<div class="orientation-bottom">
{{ markers.bottom }}
</div>
<div class="orientation-left">
{{ markers.left }}
</div>
<div ref="sliderBox" class="right-slider-box" @click.stop="clickSlider($event)">
<div :style="{ top: sliderInfo.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
@mousedown.stop="sliderMousedown($event)" />
</div>
</div>
</template>
<script>
import {
metaData,
getRenderingEngine,
utilities as csUtils,
cache
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
import { vec3, mat4 } from 'gl-matrix'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
export default {
name: 'MPRViewport',
props: {
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
viewportIndex: {
type: Number,
required: true
},
MPRInfo: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
element: '',
series: {},
ctSeries: {},
petSeries: {},
taskInfo: null,
sliderInfo: {
oldB: null,
oldM: null,
isMove: false,
height: 0
},
mousePosition: {
index: [],
value: null,
modalityUnit: '',
world: []
},
imageInfo: {
zoom: null,
size: null,
location: null,
sliceThickness: null,
wwwc: null,
total: 0,
},
digitPlaces: 2,
orientationMarkers: [],
originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' },
playClipState: false,
wwwcIdx: 2,
presetName: '',
volumeId: null,
defaultWindowLevel: {},
rotateAngle: 0,
rotateBarLeft: 0,
loading: false,
}
},
mounted() {
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.$nextTick(() => {
this.initViewport()
})
},
watch: {
MPRInfo: {
handler() {
if (!this.series.orientation) return false
switch (this.series.orientation) {
case 'AXIAL':
this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.CORONAL.imageNum}`
break;
case 'CORONAL':
this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}`
break;
case 'SAGITTAL':
this.imageInfo.size = `${this.MPRInfo.CORONAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}`
break;
}
},
deep: true
}
},
methods: {
initViewport() {
this.element = this.$refs['viewport-volume']
const resizeObserver = new ResizeObserver(() => {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
if (renderingEngine) {
renderingEngine.resize(true, false)
}
})
this.element.oncontextmenu = (e) => e.preventDefault()
// resizeObserver.observe(this.element)
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
this.element.addEventListener('wheel', (e) => {
// console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const currentImageIdIndex = viewport.getCurrentImageIdIndex()
const totalImages = this.imageInfo.total;
//
if (currentImageIdIndex >= totalImages - 1 && e.wheelDeltaY < 0) {
//
csUtils.jumpToSlice(viewport.element, { imageIndex: 0 });
} else if (currentImageIdIndex <= 0 && e.wheelDeltaY > 0) {
//
csUtils.jumpToSlice(viewport.element, { imageIndex: totalImages - 1 });
}
});
this.element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.cornerstoneToolsMouseMove)
// this.element.addEventListener(cornerstoneTools.Enums.Events.MOUSE_WHEEL, this.handletoolsMouseWheel)
this.element.addEventListener('mouseleave', () => {
this.mousePosition.index = []
this.mousePosition.value = null
})
document.addEventListener('mouseup', () => {
this.sliderMouseup()
})
document.addEventListener('mousemove', (e) => {
this.sliderMousemove(e)
})
},
determineImagePlane(imageOrientationPatient) {
// imageOrientationPatient [rowX, rowY, rowZ, colX, colY, colZ]
// (rowX, rowY, rowZ)
const [rowX, rowY, rowZ] = imageOrientationPatient;
// X, Y, Z
const dotX = Math.abs(rowX);
const dotY = Math.abs(rowY);
const dotZ = Math.abs(rowZ);
//
const maxDot = Math.max(dotX, dotY, dotZ);
//
if (maxDot === dotX) {
// X 线 Y Z
// 线
//
// X-Y (rowZ ) Axial
// X-Z (rowY ) Sagittal
// 线线
// 线 = ×
const [colX, colY, colZ] = imageOrientationPatient.slice(3);
const normalX = rowY * colZ - rowZ * colY;
const normalY = rowZ * colX - rowX * colZ;
const normalZ = rowX * colY - rowY * colX;
const absNormalX = Math.abs(normalX);
const absNormalY = Math.abs(normalY);
const absNormalZ = Math.abs(normalZ);
const maxNormal = Math.max(absNormalX, absNormalY, absNormalZ);
if (maxNormal === absNormalZ) {
return 'AXIAL';
} else if (maxNormal === absNormalY) {
return 'SAGITTAL';
} else if (maxNormal === absNormalX) {
return 'CORONAL';
}
} else if (maxDot === dotY) {
// Y Coronal
return 'SAGITTAL';
} else if (maxDot === dotZ) {
// Z Sagittal
return 'CORONAL';
}
return 'unknown';
},
stackNewImage(e) {
const { detail } = e
this.series.SliceIndex = detail.imageIndex
this.sliderInfo.height = detail.imageIndex * 100 / detail.numberOfSlices
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const zoom = viewport.getZoom()
this.imageInfo.zoom = zoom.toFixed(4)
let imageIds = viewport.getImageIds(this.volumeId)
let imageId = imageIds[0]
let volume = cache.getVolume(this.volumeId)
let spacing = volume ? volume.spacing : []
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
if (imageId && volume) {
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient)
this.imageInfo.location = type === this.series.orientation ? imagePlaneModule.sliceLocation : ''
this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0]
this.imageInfo.total = detail.numberOfSlices
this.getOrientationMarker()
let properties = viewport.getProperties(this.volumeId)
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const windowWidth = upper - lower
const windowCenter = (upper + lower) / 2
this.defaultWindowLevel.windowWidth = windowWidth
this.defaultWindowLevel.windowCenter = windowCenter
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
}
if (volume) {
const toolGroupId = 'share-viewport-volume'
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled('ScaleOverlay')
}
// const toolGroupId = this.viewportId
},
setFullScreen(index) {
this.series.SliceIndex = index
setTimeout(() => {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(
this.viewportId
)
csUtils.jumpToSlice(viewport.element, { imageIndex: index })
viewport.render()
})
},
voiModified(e) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
lower,
upper
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
this.$emit('upperRangeChange', Math.round(windowWidth))
}
},
getOrientationMarker() {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const { viewUp, viewPlaneNormal } = viewport.getCamera()
const viewRight = vec3.create()
vec3.cross(viewRight, viewUp, viewPlaneNormal)
const columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]]
const rowCosines = viewRight
const rowString = cornerstoneTools.utilities.orientation.getOrientationStringLPS(rowCosines)
const columnString = cornerstoneTools.utilities.orientation.getOrientationStringLPS(columnCosines)
const oppositeRowString = cornerstoneTools.utilities.orientation.invertOrientationStringLPS(rowString)
const oppositeColumnString = cornerstoneTools.utilities.orientation.invertOrientationStringLPS(columnString)
this.markers.top = oppositeColumnString
this.markers.right = rowString
this.markers.bottom = columnString
this.markers.left = oppositeRowString
this.orientationMarkers = [oppositeColumnString, rowString, columnString, oppositeRowString]
if (this.originalMarkers.length === 0) {
this.originalMarkers = [...this.orientationMarkers]
}
},
setMarkers() {
const markers = [...this.orientationMarkers]
for (const key in this.markers) {
const v = markers.shift(0)
this.markers[key] = v
}
},
resetOrientationMarkers() {
if (this.originalMarkers.length > 0) {
this.orientationMarkers = [...this.originalMarkers]
this.setMarkers()
}
},
rotateOrientationMarkers(type) {
if (this.orientationMarkers.length > 0) {
if (type === 1) {
this.resetOrientationMarkers()
return
}
const markers = [...this.orientationMarkers]
if (type === 2) {
//
this.orientationMarkers[0] = markers[2]
this.orientationMarkers[2] = markers[0]
} else if (type === 3) {
//
this.orientationMarkers[1] = markers[3]
this.orientationMarkers[3] = markers[1]
} else if (type === 4) {
// 90
this.orientationMarkers = markers.slice(1, 4).concat(markers[0])
} else if (type === 5) {
// 90
this.orientationMarkers = [markers[3]].concat(markers.slice(0, 3))
}
this.setMarkers()
}
},
toggleClipPlay(isPlay, framesPerSecond) {
this.playClipState = isPlay
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (isPlay) {
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
} else {
cornerstoneTools.utilities.cine.stopClip(viewport.element)
}
},
scrollPage(type) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const currentImageIdIndex = viewport.getCurrentImageIdIndex()
const numImages = viewport.getImageIds().length
let newImageIdIndex = null
if (type === 0) {
newImageIdIndex = 0
} else if (type === -1) {
newImageIdIndex = currentImageIdIndex === 0 ? currentImageIdIndex : currentImageIdIndex - 1
} else if (type === 1) {
newImageIdIndex = currentImageIdIndex === numImages - 1 ? currentImageIdIndex : currentImageIdIndex + 1
} else if (type === 99999) {
newImageIdIndex = numImages - 1
}
// viewport.setImageIdIndex(newImageIdIndex)
csUtils.jumpToSlice(viewport.element, { imageIndex: newImageIdIndex })
cornerstoneTools.utilities.cine.stopClip(viewport.element)
},
setZoom(ratio) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const zoom = viewport.getZoom()
if (ratio > 0) {
viewport.setZoom(zoom * 1.05)
} else {
viewport.setZoom(zoom / 1.05)
}
viewport.render()
},
resize(forceFitToWindow) {
console.log('resize: ', forceFitToWindow)
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (!forceFitToWindow) {
viewport.setZoom(0.5)
viewport.render()
} else {
viewport.setZoom(1)
viewport.render()
}
},
voiChange(v) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const voiRange = { lower: 0, upper: v }
const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
let volumeId = this.volumeId
const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
volumeId,
viewport.renderingEngineId
)
viewport.setProperties({ voiRange }, volumeId)
viewportsContainingVolumeUID.forEach((vp) => {
vp.render()
// this.$refs[vp.id].voiModified()
this.voiModified()
})
},
setPreset(presetName) {
this.presetName = presetName
},
async createImageIdsAndCacheMetaData(obj) {
this.loading = true
try {
return await createImageIdsAndCacheMetaData({
modality: obj.Modality,
imageIds: obj.ImageIds
})
} finally {
this.loading = false
}
},
async setSeriesInfo(obj, isLocate = false) {
try {
let data = obj
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) {
data.SliceIndex = this.series.SliceIndex
}
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
this.series = {}
this.volumeId = data.SeriesInstanceUid
this.series = { ...data }
viewport
.setVolumes([{
volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT') {
setPetColorMapTransferFunctionForVolumeActor(r, true)
} else {
let volume = cache.getVolume(this.volumeId)
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
setCtMappingRange(voi.windowWidth[0], voi.windowCenter[0])
setCtTransferFunctionForVolumeActor(r)
}
console.log("渲染成功")
DicomEvent.$emit("isloaded", { isChange: false })
}
}]).then(r => {
if (data.isLocation || !this.imageInfo.zoom) {
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
}
})
viewport.render()
} catch (e) {
console.log(e)
}
},
cornerstoneToolsMouseMove(e) {
const { currentPoints } = e.detail
const worldPoint = currentPoints.world
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let referencedImageId = viewport.getCurrentImageId()
const data = viewport.getImageData()
if (!data) return
const { dimensions, imageData, metadata, voxelManager } = data
const index = imageData.worldToIndex(worldPoint)
index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2])
this.mousePosition.index = index
const modality = metadata.Modality
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
ijk = vec3.round(ijk, ijk);
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
this.isHandleOutsideImage = false
let value = voxelManager.getAtIJKPoint(ijk)
ijk[2] = viewport.getCurrentImageIdIndex()
let modalityUnit
if (modality === 'US') {
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
const hasEnhancedRegionValues = calibratedResults.values.every(
(value) => value !== null
)
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
modalityUnit = hasEnhancedRegionValues
? calibratedResults.units
: 'raw';
} else {
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
if (scalingModule && scalingModule.suvbw) {
const pixelUnitsOptions = {
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
isSuvScaled: isSuvScaled,
}
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
modality,
referencedImageId,
pixelUnitsOptions
);
}
}
this.mousePosition.value = value
this.mousePosition.modalityUnit = modalityUnit
}
},
toggleTask(evt, visitTaskNum, i) {
this.$emit('activeViewport', this.viewportIndex)
const num = visitTaskNum + i
if (num >= 0 && num <= this.taskInfo.VisitNum) {
this.$emit('toggleTaskByViewport', { series: this.series, visitTaskNum: num })
}
evt.stopImmediatePropagation()
evt.stopPropagation()
evt.preventDefault()
},
viewCD(taskId) {
this.$emit('previewCD', taskId)
},
setWwwcIdx(idx) {
this.wwwcIdx = idx
},
clickSlider(e) {
const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
this.sliderInfo.height = height
let sliceIdx = Math.trunc(this.imageInfo.total * height / 100)
sliceIdx = sliceIdx >= this.imageInfo.total ? this.imageInfo.total : sliceIdx < 0 ? 0 : sliceIdx
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(
this.viewportId
)
// viewport.setImageIdIndex(sliceIdx)
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
viewport.render()
},
sliderMouseup(e) {
this.sliderInfo.isMove = false
this.$emit('contentMouseup', e)
},
sliderMousedown(e) {
const boxHeight = this.$refs['sliderBox'].clientHeight
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
this.sliderInfo.oldM = e.clientY
this.sliderInfo.isMove = true
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
sliderMousemove(e) {
if (!this.sliderInfo.isMove) return
const delta = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
const boxHeight = this.$refs['sliderBox'].clientHeight
if (delta < 0) return
if (delta > boxHeight) return
const height = delta * 100 / boxHeight
let sliceIdx = Math.trunc(this.imageInfo.total * height / 100)
sliceIdx = sliceIdx >= this.imageInfo.total ? this.imageInfo.total - 1 : sliceIdx < 0 ? 0 : sliceIdx
this.sliderInfo.height = height
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(
this.viewportId
)
// viewport.setImageIdIndex(sliceIdx)
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
viewport.render()
},
sliderMouseleave(e) {
if (!this.sliderInfo.isMove) return
this.sliderInfo.isMove = false
},
rotate(angle) {
let renderingEngine = getRenderingEngine(this.renderingEngineId)
let viewport = renderingEngine.getViewport(this.viewportId)
const camera = viewport.getCamera()
const { viewUp, position, focalPoint } = camera
const [cx, cy, cz] = focalPoint
const [ax, ay, az] = [0, 0, 1]
const newPosition = [0, 0, 0]
const newFocalPoint = [0, 0, 0]
const newViewUp = [0, 0, 0]
const transform = mat4.identity(new Float32Array(16))
mat4.translate(transform, transform, [cx, cy, cz])
mat4.rotate(transform, transform, angle, [ax, ay, az])
mat4.translate(transform, transform, [-cx, -cy, -cz])
vec3.transformMat4(newPosition, position, transform)
vec3.transformMat4(newFocalPoint, focalPoint, transform)
mat4.identity(transform)
mat4.rotate(transform, transform, angle, [ax, ay, az])
vec3.transformMat4(newViewUp, viewUp, transform)
viewport.setCamera({
position: newPosition,
viewUp: newViewUp,
focalPoint: newFocalPoint
})
viewport.render()
},
clickRotate(e) {
// console.log('clickRotate')
const container = document.getElementById('rotateBar')
const containerWidth = container.offsetWidth
const slider = document.getElementById('rotateSlider')
const sliderWidth = slider.offsetWidth
const x = Math.trunc(e.offsetX)
const deltaX = x - this.rotateBarLeft
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
this.rotate(angle)
this.rotateBarLeft = x
},
preventDefault(e) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
}
},
computed: {
NSTip() {
return `NS: ${this.$store.state.trials.downloadTip}`
}
},
}
</script>
<style lang="scss" scoped>
.viewport-wrapper {
width: 100%;
height: 100%;
position: relative;
cursor: default !important;
.left-top-text {
position: absolute;
left: 5px;
top: 5px;
// color: #ddd;
z-index: 1;
font-size: 12px;
.cd-info {
// color: #ddd;
font-size: 18px;
}
.subject-info {
color: #f44336;
padding: 5px 0px;
margin: 0;
}
}
.top-center-tool {
position: absolute;
left: 50%;
top: 5px;
transform: translateX(-50%);
z-index: 1;
.toggle-visit-container {
display: flex;
}
.arrw_icon {
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.arrow_text {
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding: 0 10px;
font-size: 14px;
}
}
.right-top-text {
position: absolute;
right: 5px;
top: 5px;
// color: #ddd;
z-index: 1;
font-size: 12px;
}
.left-bottom-text {
position: absolute;
left: 5px;
bottom: 5px;
// color: #ddd;
z-index: 1;
font-size: 12px;
}
.right-bottom-text {
position: absolute;
right: 5px;
bottom: 5px;
// color: #ddd;
z-index: 1;
font-size: 12px;
}
.right-slider-box {
position: absolute;
right: 1px;
height: calc(100% - 140px);
transform: translateY(-50%);
top: calc(50% - 30px);
width: 10px;
background: #333;
z-index: 1;
cursor: pointer;
}
.right-slider-box:after {
content: '';
position: absolute;
bottom: -20px;
left: 0;
height: 20px;
width: 100%;
background: #333;
}
.slider {
height: 20px;
width: 100%;
position: absolute;
top: 0;
z-index: 10;
background: #9e9e9e;
cursor: move
}
.orientation-top {
position: absolute;
left: 50%;
top: 30px;
color: #f44336;
transform: translateX(-50%);
z-index: 1;
}
.orientation-bottom {
position: absolute;
left: 50%;
bottom: 15px;
color: #f44336;
transform: translateX(-50%);
z-index: 1;
}
.orientation-left {
position: absolute;
top: 50%;
left: 15px;
color: #f44336;
transform: translateY(-50%);
z-index: 1;
}
.orientation-right {
position: absolute;
top: 50%;
right: 15px;
color: #f44336;
transform: translateY(-50%);
z-index: 1;
}
}
.color_bar {
position: absolute;
transform: rotate(-90deg);
transform-origin: right;
left: -150px;
top: 30%;
z-index: 1;
}
.rotate_slider_box {
position: absolute;
width: 380px;
height: 10px;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
background: #333;
z-index: 10;
cursor: pointer;
.box {
z-index: 10;
background: #9e9e9e;
height: 100%;
width: 20px;
position: absolute;
top: 0;
cursor: move
}
}
</style>

View File

@ -1,7 +1,13 @@
<template> <template>
<div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip" <div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove" element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
@mouseleave="sliderMouseleave" :style="{ color: series.Modality === 'PT' || isMip ? '#666' : '#ddd' }"> @mouseleave="sliderMouseleave"
:style="{ color: series.Modality === 'PT' || series.Modality === 'NM' || isMip ? '#666' : '#ddd' }">
<div v-if="isFusion" class="opacity-slider-wrapper" @mousedown.stop @mousemove.stop @mouseup.stop @wheel.stop>
<div class="slider-title">{{ Math.round(fusionOpacity * 100) }}%</div>
<input type="range" min="0" max="1" step="0.01" v-model.number="fusionOpacity" @input="applyFusionOpacity"
class="opacity-slider" />
</div>
<div v-if="series && taskInfo" class="left-top-text"> <div v-if="series && taskInfo" class="left-top-text">
<div v-if="taskInfo.IsExistsClinicalData && !isMip && !isFusion" class="cd-info" <div v-if="taskInfo.IsExistsClinicalData && !isMip && !isFusion" class="cd-info"
:title="$t('trials:reading:button:clinicalData')"> :title="$t('trials:reading:button:clinicalData')">
@ -12,32 +18,18 @@
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }} {{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
</h2> </h2>
<div v-if="!isMip && !isFusion">Series: #{{ series.SeriesNumber }}</div> <div v-if="!isMip && !isFusion">Series: #{{ series.SeriesNumber }}</div>
<div v-if="series.Stack && !isMip">Image: #{{ `${series.SliceIndex + 1}/${series.Stack.length}` }}</div> <div v-if="series.Stack && !isMip">Image: #{{ `${series.SliceIndex + 1}/${imageInfo.total || series.Stack.length}` }}</div>
<div v-if="!isMip && !isFusion">{{ series.Modality }}</div> <div v-if="!isMip && !isFusion">{{ series.Modality }}</div>
<div v-if="isFusion">{{ series.Modality }} / {{ ctSeries.Modality }}</div>
<div v-if="isMip">MIP</div>
</div> </div>
<!-- <div v-if="series && taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && series.TaskInfo && !isMip && !isFusion"
class="top-center-tool">
<div class="toggle-visit-container">
<div class="arrw_icon"
:style="{ cursor: series.TaskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: series.TaskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, -1)"
@dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="arrow_text">
{{ series.TaskInfo.TaskBlindName }}
</div>
<div class="arrw_icon"
:style="{ cursor: series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, 1)"
@dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
</div> -->
<div v-if="series && !isMip && !isFusion" class="right-top-text"> <div v-if="series && !isMip && !isFusion" class="right-top-text">
<div>{{ series.Description }}</div> <div>{{ series.Description }}</div>
</div> </div>
<!-- <div v-if="isFusion && !isMip" class="fusion-order-toggle" @mousedown.stop @mouseup.stop
@click.stop="toggleFusionRenderOrder">
{{ fusionCtOnTop ? `${ctSeries.Modality}/${series.Modality}` : `${series.Modality}/${ctSeries.Modality}` }}
</div> -->
<div v-if="series" class="left-bottom-text"> <div v-if="series" class="left-bottom-text">
<div v-show="mousePosition.index.length > 0 && !isMip"> <div v-show="mousePosition.index.length > 0 && !isMip">
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }} Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
@ -104,10 +96,16 @@ import {
import * as cornerstoneTools from '@cornerstonejs/tools' import * as cornerstoneTools from '@cornerstonejs/tools'
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps' import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData' import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor' import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor' import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
const { BlendModes, OrientationAxis } = Enums; import {
setMipTransferFunctionForVolumeActor,
setPetTransferFunctionForVolumeActor
} from './helpers/index.js'
import setNmFusionColorMapTransferFunctionForVolumeActor from './helpers/setNmFusionColorMapTransferFunctionForVolumeActor'
const { BlendModes, OrientationAxis } = Enums;
const { getColormap } = csUtils.colormap; const { getColormap } = csUtils.colormap;
import { vec3, mat4 } from 'gl-matrix' import { vec3, mat4 } from 'gl-matrix'
export default { export default {
@ -134,6 +132,7 @@ export default {
petSeries: {}, petSeries: {},
isFusion: false, isFusion: false,
isMip: false, isMip: false,
fusionCtOnTop: false,
taskInfo: null, taskInfo: null,
sliderInfo: { sliderInfo: {
oldB: null, oldB: null,
@ -152,7 +151,8 @@ export default {
size: null, size: null,
location: null, location: null,
sliceThickness: null, sliceThickness: null,
wwwc: null wwwc: null,
total: null
}, },
digitPlaces: 2, digitPlaces: 2,
orientationMarkers: [], orientationMarkers: [],
@ -172,7 +172,11 @@ export default {
isMove: false isMove: false
}, },
ptVolumeId: null, ptVolumeId: null,
loading: false loading: false,
Colorbar: null,
fusionOpacity: 0.95,
topFusionVolumeActor: null,
currentVoiUpper: null
} }
}, },
mounted() { mounted() {
@ -220,8 +224,12 @@ export default {
}, },
stackNewImage(e) { stackNewImage(e) {
const { detail } = e const { detail } = e
this.series.SliceIndex = detail.imageIndex if (this.series) {
this.sliderInfo.height = detail.imageIndex * 100 / detail.numberOfSlices this.series.SliceIndex = detail.imageIndex
}
this.imageInfo.total = detail.numberOfSlices
const maxIndex = detail.numberOfSlices > 1 ? detail.numberOfSlices - 1 : 1
this.sliderInfo.height = detail.imageIndex * 100 / maxIndex
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
const zoom = viewport.getZoom() const zoom = viewport.getZoom()
@ -267,16 +275,29 @@ export default {
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
let properties = viewport.getProperties() let properties = viewport.getProperties()
if (this.isFusion) { if (this.isFusion) {
properties = viewport.getProperties(this.volumeId) properties = viewport.getProperties(this.ptVolumeId || this.volumeId)
} }
if (properties && properties.voiRange) { if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange var { lower, upper } = properties.voiRange
if ((!upper || upper < 1) && this.currentVoiUpper >= 1) {
upper = this.currentVoiUpper
} else if (upper) {
this.currentVoiUpper = upper
}
if (!upper || upper < 1) return
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel( const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
lower, lower,
upper upper
) )
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}` this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
this.$emit('upperRangeChange', Math.round(windowWidth)) if (this.series.Modality === 'PT' || this.series.Modality === 'NM' || this.isFusion) {
this.$emit('upperRangeChange', Math.round(upper))
}
if (this.isFusion && !this.fusionCtOnTop && this.topFusionVolumeActor) {
this.applyFusionOpacity()
}
} }
}, },
getOrientationMarker() { getOrientationMarker() {
@ -397,6 +418,8 @@ export default {
voiChange(v) { voiChange(v) {
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const voiRange = { lower: 0, upper: v } const voiRange = { lower: 0, upper: v }
this.currentVoiUpper = v
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return if (!viewport) return
let volumeId = this.isFusion ? this.ptVolumeId : this.volumeId let volumeId = this.isFusion ? this.ptVolumeId : this.volumeId
@ -406,6 +429,11 @@ export default {
) )
viewport.setProperties({ voiRange }, volumeId) viewport.setProperties({ voiRange }, volumeId)
// if (this.isFusion && !this.fusionCtOnTop && this.topFusionVolumeActor) {
// this.applyFusionOpacity()
// }
viewportsContainingVolumeUID.forEach((vp) => { viewportsContainingVolumeUID.forEach((vp) => {
vp.render() vp.render()
// this.$refs[vp.id].voiModified() // this.$refs[vp.id].voiModified()
@ -445,6 +473,32 @@ export default {
ctx.fillStyle = gradient ctx.fillStyle = gradient
ctx.fillRect(0, 0, rectWidth, rectHeight) ctx.fillRect(0, 0, rectWidth, rectHeight)
}, },
opacityChange(opacity) {
this.fusionOpacity = opacity
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
if (this.isFusion) {
// const ctVolumeId = this.ctSeries?.SeriesInstanceUid
const ptVolumeId = this.ptVolumeId
viewport.setProperties({ colormap: { opacity: Number(opacity) } }, ptVolumeId)
// const topVolumeId = this.fusionCtOnTop ? ctVolumeId : ptVolumeId
// const bottomVolumeId = this.fusionCtOnTop ? ptVolumeId : ctVolumeId
// const topOpacity = Number(opacity)
// const bottomOpacity = 1 - topOpacity
// if (bottomVolumeId) {
// viewport.setProperties({ colormap: { opacity: bottomOpacity } }, bottomVolumeId)
// }
// if (topVolumeId) {
// viewport.setProperties({ colormap: { opacity: topOpacity } }, topVolumeId)
// }
}
viewport.render()
},
applyFusionOpacity() {
this.opacityChange(this.fusionOpacity)
},
setPreset(presetName) { setPreset(presetName) {
this.presetName = presetName this.presetName = presetName
}, },
@ -470,11 +524,109 @@ export default {
}, },
async createImageIdsAndCacheMetaData(obj) { async createImageIdsAndCacheMetaData(obj) {
this.loading = true this.loading = true
await createImageIdsAndCacheMetaData({ try {
modality: obj.Modality, return await createImageIdsAndCacheMetaData({
imageIds: obj.ImageIds modality: obj.Modality,
imageIds: obj.ImageIds
})
} finally {
this.loading = false
}
},
getFusionVolumes() {
const ctVolumeId = this.ctSeries?.SeriesInstanceUid
const ptFusionVolumeId = this.ptVolumeId
if (!ctVolumeId || !ptFusionVolumeId) {
return []
}
const ctEntry = {
volumeId: ctVolumeId,
callback: (r) => {
setCtTransferFunctionForVolumeActor({ ...r, volumeId: ctVolumeId })
if (this.fusionCtOnTop) {
this.topFusionVolumeActor = r.volumeActor
}
this.applyFusionOpacity()
console.log("融合ct渲染成功")
}
}
const ptFusionEntry = {
volumeId: ptFusionVolumeId,
callback: (r) => {
if (this.series.Modality === 'NM') {
setNmFusionColorMapTransferFunctionForVolumeActor({
...r,
volumeId: ptFusionVolumeId,
})
} else {
setPetColorMapTransferFunctionForVolumeActor({
...r,
volumeId: ptFusionVolumeId,
})
}
if (!this.fusionCtOnTop) {
this.topFusionVolumeActor = r.volumeActor
}
this.applyFusionOpacity()
console.log("融合pet渲染成功")
}
}
const volumes = []
// if (this.series.Modality !== 'NM') {
// volumes.push({
// volumeId: this.volumeId,
// callback: (r) => {
// console.log("pet");
// }
// })
// }
volumes.push({
volumeId: this.volumeId,
callback: (r) => {
console.log("融合pet渲染成功");
}
}) })
this.loading = false
if (this.fusionCtOnTop) {
volumes.push(ptFusionEntry, ctEntry)
} else {
volumes.push(ctEntry, ptFusionEntry)
}
return volumes
},
async applyFusionRenderOrder() {
if (!this.isFusion) return
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine?.getViewport?.(this.viewportId)
if (!viewport) return
const volumes = this.getFusionVolumes()
if (!volumes.length) return
const camera = viewport.getCamera?.()
const savedVoiUpper = this.currentVoiUpper
await viewport.setVolumes(volumes)
if (camera) {
viewport.setCamera(camera)
}
if (savedVoiUpper) {
viewport.setProperties({ voiRange: { lower: 0, upper: savedVoiUpper } }, this.ptVolumeId)
}
viewport.render()
},
toggleFusionRenderOrder() {
if (!this.isFusion) return
this.fusionCtOnTop = !this.fusionCtOnTop
this.applyFusionRenderOrder()
this.$emit('upperRangeChange', Math.round(this.currentVoiUpper))
}, },
async setSeriesInfo(obj, isLocate = false, option = {}) { async setSeriesInfo(obj, isLocate = false, option = {}) {
try { try {
@ -482,17 +634,18 @@ export default {
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate) { if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate) {
data.SliceIndex = this.series.SliceIndex data.SliceIndex = this.series.SliceIndex
} }
// this.series = { ...data }
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
this.volumeId = data.SeriesInstanceUid this.volumeId = data.SeriesInstanceUid
this.ptVolumeId = null this.ptVolumeId = null
this.series = {} this.series = {}
this.topFusionVolumeActor = null
let { isFusion, isMip, colorMap } = option let { isFusion, isMip, colorMap } = option
this.isFusion = isFusion; this.isFusion = isFusion;
this.isMip = isMip; this.isMip = isMip;
if (this.isFusion) { if (this.isFusion) {
this.fusionCtOnTop = false
this.$nextTick(() => { this.$nextTick(() => {
this.renderColorBar(this.presetName) this.renderColorBar(this.presetName)
}) })
@ -500,32 +653,10 @@ export default {
let { ct, data } = obj let { ct, data } = obj
this.series = { ...data } this.series = { ...data }
this.ctSeries = { ...ct } this.ctSeries = { ...ct }
this.petSeries = { ...data }
await viewport
.setVolumes([
{ await viewport.setVolumes(this.getFusionVolumes())
volumeId: this.volumeId, callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r)
console.log("融合pet渲染成功");
}
},
{
volumeId: ct.SeriesInstanceUid, callback: (r) => {
setCtTransferFunctionForVolumeActor(r)
console.log("融合ct渲染成功")
}
},
{
volumeId: this.ptVolumeId, callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r)
console.log("融合pet渲染成功");
}
},
]).then(res => {
if (colorMap) {
this.setColorMap(this.presetName)
}
})
} else { } else {
this.series = { ...data } this.series = { ...data }
if (this.isMip) { if (this.isMip) {
@ -540,7 +671,12 @@ export default {
.setVolumes([{ .setVolumes([{
volumeId: this.volumeId, volumeId: this.volumeId,
callback: (r) => { callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r) if (this.series.Modality === 'NM') {
setMipTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
} else {
setPetTransferFunctionForVolumeActor(r)
}
// setPetColorMapTransferFunctionForVolumeActor(r)
console.log("mip渲染成功") console.log("mip渲染成功")
}, },
slabThickness, slabThickness,
@ -548,51 +684,80 @@ export default {
defaultOptions: { defaultOptions: {
orientation: OrientationAxis.CORONAL orientation: OrientationAxis.CORONAL
} }
}]).then(res => { }])
if (colorMap) {
this.setColorMap(this.presetName)
}
if (isLocate) {
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
}
})
} else { } else {
viewport viewport
.setVolumes([{ .setVolumes([{
volumeId: this.volumeId, callback: (r) => { volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT') { if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
setPetColorMapTransferFunctionForVolumeActor(r, true) // setPetColorMapTransferFunctionForVolumeActor(r, true)
setPetTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
} else { } else {
setCtTransferFunctionForVolumeActor(r) setCtTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
} }
console.log("渲染成功")
} }
}]).then(res => { }])
if (colorMap) {
this.setColorMap(this.presetName)
}
})
} }
} }
viewport.render() viewport.render()
this.voiChange(this.currentVoiUpper)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
}, },
cornerstoneToolsMouseMove(e) { cornerstoneToolsMouseMove(e) {
const { currentPoints } = e.detail try {
const worldPoint = currentPoints.world const { currentPoints } = e.detail
const renderingEngine = getRenderingEngine(this.renderingEngineId) const worldPoint = currentPoints.world
const viewport = renderingEngine.getViewport(this.viewportId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const imageData = viewport.getImageData() const viewport = renderingEngine.getViewport(this.viewportId)
if (!imageData) return let referencedImageId = viewport.getCurrentImageId()
const index = imageData.imageData.worldToIndex(worldPoint) const data = viewport.getImageData()
index[0] = Math.floor(index[0]) if (!data || !referencedImageId) return
index[1] = Math.floor(index[1]) const { dimensions, imageData, metadata, voxelManager } = data
index[2] = Math.floor(index[2]) const index = imageData.worldToIndex(worldPoint)
this.mousePosition.index = index index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2])
this.mousePosition.index = index
const modality = metadata.Modality
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
ijk = vec3.round(ijk, ijk);
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
this.isHandleOutsideImage = false
let value = voxelManager.getAtIJKPoint(ijk)
ijk[2] = viewport.getCurrentImageIdIndex()
let modalityUnit
if (modality === 'US') {
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
const hasEnhancedRegionValues = calibratedResults.values.every(
(value) => value !== null
)
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
modalityUnit = hasEnhancedRegionValues
? calibratedResults.units
: 'raw';
} else {
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
const pixelUnitsOptions = {
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
isSuvScaled: isSuvScaled,
}
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
modality,
referencedImageId,
pixelUnitsOptions
);
}
this.mousePosition.value = value
this.mousePosition.modalityUnit = modalityUnit
}
} catch(e) {
console.log(e)
}
}, },
toggleTask(evt, visitTaskNum, i) { toggleTask(evt, visitTaskNum, i) {
this.$emit('activeViewport', this.viewportIndex) this.$emit('activeViewport', this.viewportIndex)
@ -614,8 +779,10 @@ export default {
clickSlider(e) { clickSlider(e) {
const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
this.sliderInfo.height = height this.sliderInfo.height = height
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100) const totalSlices = this.imageInfo.total || this.series.Stack?.length || 1
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx const maxIndex = totalSlices > 1 ? totalSlices - 1 : 0
let sliceIdx = Math.round(maxIndex * height / 100)
sliceIdx = sliceIdx > maxIndex ? maxIndex : sliceIdx < 0 ? 0 : sliceIdx
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport( const viewport = renderingEngine.getViewport(
this.viewportId this.viewportId
@ -630,7 +797,7 @@ export default {
}, },
sliderMousedown(e) { sliderMousedown(e) {
const boxHeight = this.$refs['sliderBox'].clientHeight const boxHeight = this.$refs['sliderBox'].clientHeight
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100 this.sliderInfo.oldB = this.sliderInfo.height * boxHeight / 100
this.sliderInfo.oldM = e.clientY this.sliderInfo.oldM = e.clientY
this.sliderInfo.isMove = true this.sliderInfo.isMove = true
e.stopImmediatePropagation() e.stopImmediatePropagation()
@ -644,8 +811,10 @@ export default {
if (delta < 0) return if (delta < 0) return
if (delta > boxHeight) return if (delta > boxHeight) return
const height = delta * 100 / boxHeight const height = delta * 100 / boxHeight
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100) const totalSlices = this.imageInfo.total || this.series.Stack?.length || 1
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx const maxIndex = totalSlices > 1 ? totalSlices - 1 : 0
let sliceIdx = Math.round(maxIndex * height / 100)
sliceIdx = sliceIdx > maxIndex ? maxIndex : sliceIdx < 0 ? 0 : sliceIdx
this.sliderInfo.height = height this.sliderInfo.height = height
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport( const viewport = renderingEngine.getViewport(
@ -751,11 +920,15 @@ export default {
e.stopImmediatePropagation() e.stopImmediatePropagation()
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
} },
},
beforeDestroy() {
this.series = null
this.topFusionVolumeActor = null
}, },
computed: { computed: {
NSTip() { NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}` return `NS: ${this.$store.state.trials.downloadTip}`
} }
}, },
} }
@ -767,6 +940,43 @@ export default {
position: relative; position: relative;
cursor: default !important; cursor: default !important;
.opacity-slider-wrapper {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 4px;
width: 40px;
.slider-title {
font-size: 12px;
color: #ddd;
margin-bottom: 10px;
width: 30px;
text-align: center;
}
.opacity-slider {
-webkit-appearance: slider-vertical;
appearance: slider-vertical;
writing-mode: vertical-lr;
direction: rtl;
width: 10px;
height: 150px;
padding: 0;
margin: 0;
cursor: pointer;
outline: none;
}
}
.left-top-text { .left-top-text {
position: absolute; position: absolute;
left: 5px; left: 5px;
@ -826,6 +1036,20 @@ export default {
font-size: 12px; font-size: 12px;
} }
.fusion-order-toggle {
position: absolute;
right: 5px;
top: 5px;
z-index: 2;
font-size: 12px;
padding: 2px 8px;
background: rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 2px;
cursor: pointer;
user-select: none;
}
.left-bottom-text { .left-bottom-text {
position: absolute; position: absolute;
left: 5px; left: 5px;
@ -943,4 +1167,4 @@ export default {
cursor: move cursor: move
} }
} }
</style> </style>

View File

@ -1986,12 +1986,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -2002,7 +2002,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -2010,7 +2010,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -2021,7 +2021,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -0,0 +1,259 @@
<template>
<el-form ref="segmentForm" :model="form" label-width="120px" label-position="left" :rules="rules">
<!-- 检查名称 -->
<el-form-item :label="$t('segment:form:label:studyName')" prop="taskBlindName">
<el-select v-model="form.studyId" clearable @change="(e) => handleChange(e, 'study')">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode"
:value="item.StudyId" />
</el-select>
</el-form-item>
<!-- 序列名称 -->
<el-form-item :label="$t('segment:form:label:seriesName')" prop="taskBlindName">
<el-select v-model="form.seriesId" clearable @change="(e) => handleChange(e, 'series')">
<el-option v-for="item in seriesList" :key="item.Id"
:label="`#${item.SeriesNumber}/${item.Modality}/${item.Description}`" :value="item.Id" />
</el-select>
</el-form-item>
<!-- 分割组名称 -->
<el-form-item :label="$t('segment:form:label:segmentGroupName')" prop="segmentGroupId">
<el-select v-model="form.segmentGroupId" clearable @change="(e) => handleChange(e, 'segmentGroup')">
<el-option v-for="item in segmentGroupList" :key="item.Id" :label="item.SegmentationName"
:value="item.Id" />
</el-select>
</el-form-item>
<!-- 分割名称 -->
<el-form-item :label="$t('segment:form:label:segmentName')" prop="taskBlindName">
<el-select v-model="form.segmentId" clearable>
<el-option v-for="item in segmentList" :key="item.Id" :label="item.SegmentName" :value="item.Id" />
</el-select>
</el-form-item>
<el-form-item style="text-align:right;">
<!-- 取消 -->
<el-button size="mini" @click="handleCancel">{{ $t('common:button:cancel') }}</el-button>
<!-- 确认 -->
<el-button type="primary" size="mini" @click="handleSave" :disabled="!form.segmentId">
{{ $t('common:button:confirm') }}</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { getSegmentationList, getSegmentList, getSegmentBindingList } from '@/api/reading'
export default {
name: 'FusionForm',
props: {
visible: {
type: Boolean,
default: false
},
visitInfo: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
form: {
studyId: '',
seriesId: '',
segmentGroupId: '',
segmentId: ""
},
studyList: [],
seriesList: [],
segmentGroupList: [],
segmentList: [],
segmentionList: [],
series: {},
rules: {
segmentGroupId: [
{
validator: (rule, value, callback) => {
if (value) {
// callback(new Error(''));
let segmentGroup = this.segmentGroupList.find(item => item.Id === value)
if (!segmentGroup.IsSaved) {
callback(new Error(this.$t('segment:confirm:message:segmentGroupIsNotSave')));
} else {
callback();
}
} else {
callback();
}
}, trigger: ['blur', 'change']
}
]
}
}
},
// mounted() {
// this.init()
// },
methods: {
setSeries(series) {
this.series = series
},
async init() {
let studyList = Object.assign(this.visitInfo.StudyList, {})
let s = await this.getSegmentationList()
this.segmentionList = s
let StudyIds = s.map(item => item.StudyId)
let SeriesIds = s.map(item => item.SeriesId)
studyList = studyList.filter(item => StudyIds.includes(item.StudyId))
studyList.forEach(study => {
study.SeriesArr = study.SeriesList.filter(item => SeriesIds.includes(item.Id))
})
this.studyList = studyList
if (this.visitInfo.operateStateEnum === 21) {
this.form.studyId = this.series.StudyId
this.handleChange(null, 'study')
this.form.seriesId = this.series.Id
this.handleChange(null, 'series')
}
if (this.visitInfo.operateStateEnum === 22) {
let o = {}
if (this.isTableQuestion) {
o.TableQuestionId = this.visitInfo.operateQuestionId
o.RowId = this.visitInfo.operateRowId
} else {
o.QuestionId = this.visitInfo.operateQuestionId
}
let list = await this.getSegmentBindingList(o)
let segmentGroup = this.segmentionList.find(item => item.Id === list[0].SegmentationId)
this.form.studyId = segmentGroup.StudyId
this.handleChange(null, 'study')
this.form.seriesId = segmentGroup.SeriesId
this.handleChange(null, 'series')
this.form.segmentGroupId = list[0].SegmentationId
this.handleChange(null, 'segmentGroup')
this.form.segmentId = list[0].SegmentId
}
},
async handleChange(e, key) {
if (key === 'study') {
this.seriesList = this.studyList.find(item => item.StudyId === this.form.studyId).SeriesArr
}
if (key === 'series') {
this.segmentGroupList = this.segmentionList.filter(item => item.SeriesId === this.form.seriesId)
}
if (key === 'segmentGroup') {
let list = await this.getSegmentList(this.form.segmentGroupId)
this.segmentList = list.filter(item => item.SegmentJson)
}
},
handleCancel() {
this.$emit("update:visible", false)
},
async handleSave() {
let validate = await this.$refs.segmentForm.validate()
if (!validate) return false
let segment = this.segmentList.find(item => item.Id === this.form.segmentId)
if (segment.SegmentJson) {
let obj = JSON.parse(segment.SegmentJson)
segment.stats = obj.stats
segment.bidirectional = obj.bidirectional
}
this.$emit('handleSegmentSave', segment)
this.handleCancel()
},
//
async getSegmentationList() {
try {
let data = {
VisitTaskId: this.visitInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
this.loading = true;
let res = await getSegmentationList(data);
this.loading = false;
if (res.IsSuccess) {
return res.Result.CurrentPageData;
}
} catch (err) {
this.loading = false
console.log(err)
}
},
//
async getSegmentList(id) {
try {
let data = {
SegmentationId: id,
PageSize: 9999,
PageIndex: 1,
}
this.loading = true;
let res = await getSegmentList(data)
this.loading = false;
if (res.IsSuccess) {
return res.Result.CurrentPageData
}
} catch (err) {
this.loading = false
console.log(err)
}
},
//
async getSegmentBindingList(param = {}) {
try {
let data = {
VisitTaskId: this.visitInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
data = Object.assign(data, param)
let res = await getSegmentBindingList(data)
if (res.IsSuccess) {
return res.Result.CurrentPageData
}
} catch (err) {
console.log(err)
}
},
}
}
</script>
<style lang="scss" scoped>
.series-table {
::v-deep.el-table {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
::v-deep.el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #dfdfdf;
}
.el-table--border::after,
.el-table--group::after,
.el-table::before {
background-color: #1e1e1e;
}
::v-deep.el-table__header-wrapper {
th {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
}
::v-deep.el-table__body-wrapper {
tr {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
tr:hover>td {
background-color: #1e1e1e !important;
}
}
::v-deep.el-table__empty-block {
background-color: #1e1e1e !important;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,10 @@
<template> <template>
<div v-loading="loading" class="study-wrapper"> <div v-loading="loading" class="study-wrapper">
<div class="study-info"> <div class="study-info">
<div <div v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo" :title="taskInfo.SubjectCode">
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
:title="taskInfo.SubjectCode"
>
{{ taskInfo.SubjectCode }} {{ taskInfo.SubjectCode }}
</div> </div>
<div <div v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo" :title="visitTaskInfo.TaskBlindName">
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
:title="visitTaskInfo.TaskBlindName"
>
{{ visitTaskInfo.TaskBlindName }} {{ visitTaskInfo.TaskBlindName }}
</div> </div>
</div> </div>
@ -18,15 +12,14 @@
<el-collapse v-model="activeNames"> <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"> <template slot="title">
<div <div v-if="!study.IsCriticalSequence" class="dicom-desc">
v-if="!study.IsCriticalSequence"
class="dicom-desc"
>
<template v-if="taskInfo && taskInfo.IsShowStudyName"> <template v-if="taskInfo && taskInfo.IsShowStudyName">
<div style="text-overflow: ellipsis;overflow: hidden;"> <div style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.StudyCode">{{ study.StudyCode }}</span> <span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="study.StudyName" :title="study.StudyName" style="margin-left: 5px;">{{ study.StudyName }}</span> <span v-if="study.StudyName" :title="study.StudyName" style="margin-left: 5px;">{{ study.StudyName
<span v-else :title="study.Modalities" style="margin-left: 5px;">{{ `${study.Modalities} (${study.SeriesCount})` }}</span> }}</span>
<span v-else :title="study.Modalities" style="margin-left: 5px;">{{ `${study.Modalities}
(${study.SeriesCount})` }}</span>
</div> </div>
<div v-if="study.StudyName" style="text-overflow: ellipsis;overflow: hidden;"> <div v-if="study.StudyName" style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.Modalities">{{ `${study.Modalities} (${study.SeriesCount})` }}</span> <span :title="study.Modalities">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
@ -46,53 +39,38 @@
</div> </div>
</template> </template>
<div class="dicom-list-container"> <div class="dicom-list-container">
<div <div v-for="(series, i) in study.SeriesList" :key="series.Id" style="position:relative;margin-top:1px;"
v-for="(series, i) in study.SeriesList" @click="activeSeries(series, i, index)">
:key="series.Id" <div :class="{ 'series-active': index === activeStudyIndex && i === activeSeriesIndex }"
style="position:relative;margin-top:1px;" class="series-wrapper">
@click="activeSeries(series, i, index)"
>
<div
:class="{'series-active': index === activeStudyIndex && i === activeSeriesIndex}"
class="series-wrapper"
>
<div class="series-image"> <div class="series-image">
<el-image <el-image style="width: 100%;height: 100%;"
style="width: 100%;height: 100%;"
:src="`${OSSclientConfig.basePath}${series.ImageResizePath || series.NoneDicomFileFirstFile}`" :src="`${OSSclientConfig.basePath}${series.ImageResizePath || series.NoneDicomFileFirstFile}`"
fit="fill" fit="fill" crossorigin="anonymous" />
crossorigin="anonymous"
/>
</div> </div>
<div class="series-text"> <div class="series-text">
<div v-if="series.IsExistMutiFrames && series.InstanceCount > 1" <div v-if="series.IsExistMutiFrames && series.InstanceCount > 1"
style="position: absolute;right: 0;top: 0;"> style="position: absolute;right: 0;top: 0;">
<el-popover <el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
placement="right"
trigger="hover"
popper-class="instance_frame_wrapper"
>
<div class="frame_list"> <div class="frame_list">
<div <div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id" class="frame_content"
v-for="(instance, idx) in series.InstanceInfoList" :style="{ 'margin-bottom': idx < series.InstanceInfoList.length - 1 ? '5px' : '0px' }"
:key="instance.Id" @click.stop="showMultiFrames(index, series, i, instance)">
class="frame_content"
:style="{'margin-bottom':idx<series.InstanceInfoList.length-1? '5px':'0px'}"
@click.stop="showMultiFrames(index,series, i, instance)"
>
<div> <div>
<div>{{ instance.InstanceNumber }}</div> <div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ? instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div> <div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ?
instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
</div> </div>
</div> </div>
</div> </div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;color: #ffeb3b;" /> <i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
</el-popover> </el-popover>
</div> </div>
<div v-if="!study.IsCriticalSequence" class="text-desc" :title="series.SeriesNumber"> <div v-if="!study.IsCriticalSequence" class="text-desc" :title="series.SeriesNumber">
#{{ series.SeriesNumber }} #{{ series.SeriesNumber }}
</div> </div>
<div v-if="series.Description" class="text-desc" :title="series.Description"> <div v-if="series.Description" class="text-desc" :title="series.Description">
{{ series.Description }} {{ series.Description }}
@ -104,17 +82,19 @@
<span v-show="series.LoadedImageCount < series.InstanceCount"> <span v-show="series.LoadedImageCount < series.InstanceCount">
{{ series.Modality }}: {{ series.LoadedImageCount }}/{{ series.InstanceCount }} image {{ series.Modality }}: {{ series.LoadedImageCount }}/{{ series.InstanceCount }} image
</span> </span>
<span v-show="series.LoadedImageCount >= series.InstanceCount">{{ series.Modality }}: {{ series.InstanceCount }} image</span> <span v-show="series.LoadedImageCount >= series.InstanceCount">{{ series.Modality }}: {{
series.InstanceCount
}} image</span>
</div> </div>
<div style="line-height: 12px;"> <div style="line-height: 12px;">
<i v-show="series.IsBeMark || markedSeriesIds.includes(series.Id)" class="el-icon-star-on" style="font-size: 12px;color: #ff5722;" /> <i v-show="series.IsBeMark || markedSeriesIds.includes(series.Id)" class="el-icon-star-on"
style="font-size: 12px;color: #ff5722;" />
</div> </div>
</div> </div>
</div> </div>
<div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount" style="width: 100%;"> <div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount"
<el-progress style="width: 100%;">
:percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))" <el-progress :percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -124,6 +104,7 @@
</div> </div>
</template> </template>
<script> <script>
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
export default { export default {
name: 'StudyList', name: 'StudyList',
props: { props: {
@ -168,6 +149,7 @@ export default {
this.activeStudyIndex = studyIndex this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex this.activeSeriesIndex = seriesIndex
this.$emit('activeSeries', series) this.$emit('activeSeries', series)
DicomEvent.$emit('activeSeries', series)
}, },
activeStudy(id) { activeStudy(id) {
if (this.activeNames.indexOf(id) > -1) return if (this.activeNames.indexOf(id) > -1) return
@ -179,6 +161,7 @@ export default {
const studyId = this.studyList[studyIndex].StudyId const studyId = this.studyList[studyIndex].StudyId
if (!studyId) return if (!studyId) return
this.activeStudy(studyId) this.activeStudy(studyId)
DicomEvent.$emit('activeSeries', this.studyList[studyIndex].SeriesList[this.activeSeriesIndex])
}, },
showMultiFrames(studyIndex, series, seriesIndex, instance) { showMultiFrames(studyIndex, series, seriesIndex, instance) {
let obj = Object.assign({}, series) let obj = Object.assign({}, series)
@ -203,7 +186,7 @@ export default {
obj.ImageIds = imageIds obj.ImageIds = imageIds
obj.SliceIndex = 0 obj.SliceIndex = 0
this.$emit('showMultiFrame', obj) this.$emit('showMultiFrame', obj)
}, },
getPreviousOrNextSeries(type, series) { getPreviousOrNextSeries(type, series) {
const seriseList = this.studyList.map(s => s.SeriesList).flat() const seriseList = this.studyList.map(s => s.SeriesList).flat()
@ -224,13 +207,14 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.study-wrapper{ .study-wrapper {
width:100%; width: 100%;
height: 100%; height: 100%;
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.study-info { .study-info {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@ -241,7 +225,8 @@ export default {
background-color: #4c4c4c; background-color: #4c4c4c;
height: 50px; height: 50px;
} }
.dicom-desc{
.dicom-desc {
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 13px;
text-align: left; text-align: left;
@ -258,19 +243,22 @@ export default {
touch-action: auto; touch-action: auto;
overflow-y: auto; overflow-y: auto;
} }
.series-active { .series-active {
background-color: #607d8b!important; background-color: #607d8b !important;
border: 1px solid #607d8b!important; border: 1px solid #607d8b !important;
} }
::v-deep.el-progress__text{ ::v-deep .el-progress__text{
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
.dicom-list-container{
.dicom-list-container {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
.series-wrapper { .series-wrapper {
width: 100%; width: 100%;
padding: 5px; padding: 5px;
@ -279,21 +267,26 @@ export default {
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
background-color: #3a3a3a; background-color: #3a3a3a;
.el-progress__text{
.el-progress__text {
display: none; display: none;
} }
.el-progress-bar{
padding-right:0px; .el-progress-bar {
padding-right: 0px;
} }
.series-image { .series-image {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.series-text { .series-text {
flex: 1; flex: 1;
padding-left: 5px; padding-left: 5px;
color: #ddd; color: #ddd;
position: relative; position: relative;
.text-desc { .text-desc {
width: 100px; width: 100px;
white-space: nowrap; white-space: nowrap;
@ -306,27 +299,31 @@ export default {
} }
} }
::v-deep.el-collapse{ ::v-deep .el-collapse{
border: none; border: none;
.el-collapse-item{
background-color: #000!important; .el-collapse-item {
background-color: #000 !important;
color: #ddd; color: #ddd;
} }
.el-collapse-item__content{
padding-bottom:0px; .el-collapse-item__content {
background-color: #000!important; padding-bottom: 0px;
background-color: #000 !important;
} }
.el-collapse-item__header{
background-color: #000!important; .el-collapse-item__header {
background-color: #000 !important;
color: #ddd; color: #ddd;
border-bottom-color:#5a5a5a; border-bottom-color: #5a5a5a;
padding-left: 5px; padding-left: 5px;
// height: 50px; // height: 50px;
line-height: 20px; line-height: 20px;
} }
} }
::v-deep .el-progress-bar__inner{
::v-deep .el-progress-bar__inner {
transition: width 0s ease; transition: width 0s ease;
} }
} }

View File

@ -40,10 +40,10 @@
<div v-if="series" class="right-bottom-text"> <div v-if="series" class="right-bottom-text">
<div v-show="imageInfo.location">Location: {{ <div v-show="imageInfo.location">Location: {{
`${Number(imageInfo.location).toFixed(digitPlaces)} mm` `${Number(imageInfo.location).toFixed(digitPlaces)} mm`
}}</div> }}</div>
<div v-show="imageInfo.sliceThickness">Slice Thickness: {{ <div v-show="imageInfo.sliceThickness">Slice Thickness: {{
`${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm` `${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm`
}}</div> }}</div>
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div> <div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
</div> </div>
<div class="orientation-top"> <div class="orientation-top">
@ -68,6 +68,8 @@
<script> <script>
import { import {
metaData, metaData,
volumeLoader,
setVolumesForViewports,
getRenderingEngine, getRenderingEngine,
utilities as csUtils, utilities as csUtils,
cache cache
@ -75,10 +77,12 @@ import {
import * as cornerstoneTools from '@cornerstonejs/tools' import * as cornerstoneTools from '@cornerstonejs/tools'
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData' import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor' import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor' import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
import { vec3, mat4 } from 'gl-matrix' import { vec3, mat4 } from 'gl-matrix'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
export default { export default {
name: 'ImageViewport', name: 'MPRViewport',
props: { props: {
renderingEngineId: { renderingEngineId: {
type: String, type: String,
@ -92,12 +96,6 @@ export default {
type: Number, type: Number,
required: true required: true
}, },
MPRInfo: {
type: Object,
default: () => {
return {}
}
}
}, },
data() { data() {
return { return {
@ -125,7 +123,9 @@ export default {
sliceThickness: null, sliceThickness: null,
wwwc: null, wwwc: null,
total: 0, total: 0,
sliceThickness: 0 sliceThickness: 0,
imageOrientationPatient: [],
imagePositionPatient: []
}, },
digitPlaces: 2, digitPlaces: 2,
orientationMarkers: [], orientationMarkers: [],
@ -181,6 +181,7 @@ export default {
// resizeObserver.observe(this.element) // resizeObserver.observe(this.element)
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage) this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified) this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
this.element.addEventListener('CORNERSTONE_IMAGE_RENDERED', this.imageRendered)
this.element.addEventListener('wheel', (e) => { this.element.addEventListener('wheel', (e) => {
console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL') console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
@ -189,10 +190,10 @@ export default {
const totalImages = this.imageInfo.total; const totalImages = this.imageInfo.total;
// //
if (currentImageIdIndex >= totalImages - 1) { if (currentImageIdIndex >= totalImages - 1 && e.wheelDeltaY < 0) {
// //
csUtils.jumpToSlice(viewport.element, { imageIndex: 0 }); csUtils.jumpToSlice(viewport.element, { imageIndex: 0 });
} else if (currentImageIdIndex <= 0) { } else if (currentImageIdIndex <= 0 && e.wheelDeltaY > 0) {
// //
csUtils.jumpToSlice(viewport.element, { imageIndex: totalImages - 1 }); csUtils.jumpToSlice(viewport.element, { imageIndex: totalImages - 1 });
} }
@ -272,19 +273,31 @@ export default {
const zoom = viewport.getZoom() const zoom = viewport.getZoom()
this.imageInfo.zoom = zoom.toFixed(4) this.imageInfo.zoom = zoom.toFixed(4)
let imageIds = viewport.getImageIds(this.volumeId) let imageIds = viewport.getImageIds(this.volumeId)
let imageId = imageIds[0] let imageId = imageIds[detail.imageIndex]
let volume = cache.getVolume(this.volumeId)
let { spacing } = volume
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
if (imageId) { if (imageId) {
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
const imagePlaneModule = metaData.get('imagePlaneModule', imageId) const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient) this.imageInfo.imageOrientationPatient = imagePlaneModule.imageOrientationPatient
this.imageInfo.location = type === this.series.orientation ? imagePlaneModule.sliceLocation : '' this.imageInfo.imagePositionPatient = imagePlaneModule.imagePositionPatient
this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0] this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}`
this.imageInfo.location = imagePlaneModule.sliceLocation
this.imageInfo.total = detail.numberOfSlices this.imageInfo.total = detail.numberOfSlices
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient)
let volume = cache.getVolume(this.volumeId)
let spacing = volume ? volume.spacing : []
this.imageInfo.sliceThickness = type === 'AXIAL' ? spacing[2] : spacing[0]
this.getOrientationMarker() this.getOrientationMarker()
let properties = viewport.getProperties(this.volumeId) if (this.series && this.series.Id) {
let annotations = cornerstoneTools.annotation.state.getAllAnnotations().filter(item => item.metadata.toolName !== 'ScaleOverlay' && item.metadata.volumeId !== this.volumeId && !item.metadata.segmentationId && item.seriesId !== this.series.Id)
annotations.forEach(item => {
cornerstoneTools.annotation.state.removeAnnotation(item.annotationUID)
})
}
this.$emit('renderAnnotations', this.series)
let properties = viewport.getProperties()
if (this.isFusion) {
properties = viewport.getProperties(this.ptVolumeId)
}
if (properties && properties.voiRange) { if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange var { lower, upper } = properties.voiRange
const windowWidth = upper - lower const windowWidth = upper - lower
@ -293,15 +306,15 @@ export default {
this.defaultWindowLevel.windowCenter = windowCenter this.defaultWindowLevel.windowCenter = windowCenter
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}` this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
} }
const toolGroupId = this.viewportId
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled('ScaleOverlay')
} }
// const toolGroupId = this.viewportId
const toolGroupId = 'share-viewport-volume'
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled('ScaleOverlay')
}, },
setFullScreen(index) { setFullScreen(index) {
setTimeout(() => { setTimeout(() => {
this.series.SliceIndex = index
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport( const viewport = renderingEngine.getViewport(
this.viewportId this.viewportId
@ -324,6 +337,22 @@ export default {
this.$emit('upperRangeChange', Math.round(windowWidth)) this.$emit('upperRangeChange', Math.round(windowWidth))
} }
}, },
imageRendered(e) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
lower,
upper
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
const zoom = viewport.getZoom()
this.imageInfo.zoom = zoom.toFixed(4)
},
getOrientationMarker() { getOrientationMarker() {
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
@ -462,11 +491,28 @@ export default {
}, },
async createImageIdsAndCacheMetaData(obj) { async createImageIdsAndCacheMetaData(obj) {
this.loading = true this.loading = true
await createImageIdsAndCacheMetaData({ try {
modality: obj.Modality, return await createImageIdsAndCacheMetaData({
imageIds: obj.ImageIds modality: obj.Modality,
imageIds: obj.ImageIds
})
} finally {
this.loading = false
}
},
async getVolume(serie, isFusion = false) {
return new Promise(async res => {
let volumeId = `${isFusion ? 'fusion_' : ''}` + serie.SeriesInstanceUid;
let volume = null;
if (cache.getVolume(volumeId)) {
volume = cache.getVolume(volumeId)
} else {
let imageIds = await this.createImageIdsAndCacheMetaData(serie)
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: imageIds })
volume.load()
}
res({ volumeId, volume })
}) })
this.loading = false
}, },
async setSeriesInfo(obj, isLocate = false) { async setSeriesInfo(obj, isLocate = false) {
try { try {
@ -474,26 +520,34 @@ export default {
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) { if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) {
data.SliceIndex = this.series.SliceIndex data.SliceIndex = this.series.SliceIndex
} }
// console.log(data)
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
this.series = {} this.series = {}
this.volumeId = data.SeriesInstanceUid let res = await this.getVolume(obj)
this.volumeId = res.volumeId
this.series = { ...data } this.series = { ...data }
viewport viewport
.setVolumes([{ .setVolumes([{
volumeId: this.volumeId, callback: (r) => { volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT') { if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
setPetColorMapTransferFunctionForVolumeActor(r, true) setPetColorMapTransferFunctionForVolumeActor(r, true)
} else { } else {
const voi = metaData.get('voiLutModule', res.volume._imageIds[Math.ceil((res.volume._imageIds.length - 1) / 2)])
setCtMappingRange(voi.windowWidth[0], voi.windowCenter[0])
setCtTransferFunctionForVolumeActor(r) setCtTransferFunctionForVolumeActor(r)
} }
console.log("渲染成功") console.log("渲染成功")
} }
}]).then(res => { }]).then(r => {
if (data.segment) {
return DicomEvent.$emit("isloaded", { segment: data.segment, isChange: data.isChange })
}
if (data.isLocation) { if (data.isLocation) {
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); }) setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
} }
DicomEvent.$emit("isloaded", { isChange: data.isChange })
}) })
viewport.render() viewport.render()
} catch (e) { } catch (e) {
@ -506,13 +560,50 @@ export default {
const worldPoint = currentPoints.world const worldPoint = currentPoints.world
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
const imageData = viewport.getImageData() let referencedImageId = viewport.getCurrentImageId()
if (!imageData) return const data = viewport.getImageData()
const index = imageData.imageData.worldToIndex(worldPoint) if (!data) return
const { dimensions, imageData, metadata, voxelManager } = data
const index = imageData.worldToIndex(worldPoint)
index[0] = Math.floor(index[0]) index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1]) index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2]) index[2] = Math.floor(index[2])
this.mousePosition.index = index this.mousePosition.index = index
const modality = metadata.Modality
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
ijk = vec3.round(ijk, ijk);
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
this.isHandleOutsideImage = false
let value = voxelManager.getAtIJKPoint(ijk)
ijk[2] = viewport.getCurrentImageIdIndex()
let modalityUnit
if (modality === 'US') {
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
const hasEnhancedRegionValues = calibratedResults.values.every(
(value) => value !== null
)
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
modalityUnit = hasEnhancedRegionValues
? calibratedResults.units
: 'raw';
} else {
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
if (scalingModule && scalingModule.suvbw) {
const pixelUnitsOptions = {
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
isSuvScaled: isSuvScaled,
}
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
modality,
referencedImageId,
pixelUnitsOptions
);
}
}
this.mousePosition.value = value
this.mousePosition.modalityUnit = modalityUnit
}
}, },
toggleTask(evt, visitTaskNum, i) { toggleTask(evt, visitTaskNum, i) {
this.$emit('activeViewport', this.viewportIndex) this.$emit('activeViewport', this.viewportIndex)
@ -629,7 +720,7 @@ export default {
}, },
computed: { computed: {
NSTip() { NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}` return `NS: ${this.$store.state.trials.downloadTip}`
} }
}, },
} }

View File

@ -30,8 +30,8 @@
</div> </div>
</div> </div>
</div> </div>
<div style="margin-left:-1px;border: 1px solid #424242;"> <div v-if="modality !== 'NM'" style="margin-left:-1px;border: 1px solid #424242;">
<el-input v-model="range" size="mini" style="width:120px" maxlength="3" <el-input v-model="range" size="mini" style="width:120px" :maxlength="maxLength"
oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}" oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}"
@change="upperRangeChange"> @change="upperRangeChange">
<template slot="append">g/ml</template> <template slot="append">g/ml</template>
@ -54,12 +54,24 @@ import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/C
const { registerColormap, getColormapNames, getColormap } = csUtils.colormap const { registerColormap, getColormapNames, getColormap } = csUtils.colormap
export default { export default {
name: "colorMap", name: "colorMap",
props: {
modality: {
type: String,
default: ''
},
maxLength: {
type: [Number, String],
default: 6
}
},
data() { data() {
return { return {
colorMaps: [], colorMaps: [],
rgbPresetName: 'siemens', rgbPresetName: 'siemens',
range: 40, range: 40,
upper: 6, upper: 6,
isSlideMoving: false,
req: null
} }
}, },
mounted() { mounted() {
@ -72,9 +84,24 @@ export default {
this.renderColorMaps() this.renderColorMaps()
this.upperRangeChange(this.range) this.upperRangeChange(this.range)
this.initSlider() this.initSlider()
// this.syncSliderPosition()
}) })
}, },
methods: { methods: {
syncSliderPosition(retry = 0) {
var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas')
if (!sliderBox || !container) return
var maxLeft = container.clientWidth - sliderBox.clientWidth
if (maxLeft <= 0) {
if (retry < 30) {
requestAnimationFrame(() => this.syncSliderPosition(retry + 1))
}
return
}
this.updateSliderPosition()
},
renderColorMaps() { renderColorMaps() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15) this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.colorMaps.forEach((e, index) => { this.colorMaps.forEach((e, index) => {
@ -84,61 +111,127 @@ export default {
voiChange(v) { voiChange(v) {
this.$emit('voiChange', v) this.$emit('voiChange', v)
}, },
changeVoi(v) {
//
if (v === this.upper) return
var range = Number(this.range) || 0
var upper = Number(v) || 0
if (range <= 0) {
upper = 0
} else if (upper > range) {
upper = range
} else if (upper < 0) {
upper = 0
}
this.upper = upper
this.syncSliderPosition()
},
initSlider() { initSlider() {
var slider = document.getElementById('slider') var slider = document.getElementById('slider')
var sliderBox = document.getElementById('sliderBox') var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas') var container = document.getElementById('colorBarCanvas')
if (!slider || !sliderBox || !container) return
slider.addEventListener('mousedown', () => { slider.addEventListener('mousedown', () => {
this.isSlideMoving = true this.isSlideMoving = true
}) })
document.addEventListener('mousemove', (e) => { document.addEventListener('mousemove', (e) => {
if (this.isSlideMoving) { if (this.isSlideMoving) {
var containerWidth = container.clientWidth if (this.req) return;
var sliderWidth = sliderBox.clientWidth this.req = requestAnimationFrame(() => {
var maxLeft = containerWidth - sliderWidth this.req = null;
var left = e.clientX - container.getBoundingClientRect().left var containerWidth = container.clientWidth
var position = null var sliderWidth = sliderBox.clientWidth
position = left var maxLeft = containerWidth - sliderWidth
if (left < 0) { if (maxLeft <= 0) return
left = 6
position = 0
} else if (left > maxLeft) {
left = maxLeft + 6
position = maxLeft
}
slider.style.left = left + 'px' var left = e.clientX - container.getBoundingClientRect().left
var positionValue = document.getElementById('slider-position') if (left < 0) {
var upper = this.range left = 0
position = parseInt((position / maxLeft) * upper) } else if (left > maxLeft) {
positionValue.textContent = position left = maxLeft
this.upper = position }
this.voiChange(position) var position = left
slider.style.left = (left + 6) + 'px'
var positionValue = document.getElementById('slider-position')
var upper = Number(this.range) || 0
position = parseInt((position / maxLeft) * upper)
if (position > upper) position = upper
if (position < 0) position = 0
if (this.modality === 'NM') {
positionValue.textContent = upper > 0 ? Math.round((position / upper) * 100) + '%' : '0%'
} else {
positionValue.textContent = position
}
this.upper = position
this.voiChange(position)
});
} }
}) })
document.addEventListener('mouseup', () => { document.addEventListener('mouseup', () => {
this.isSlideMoving = false this.isSlideMoving = false
}) })
}, },
upperRangeChange(v) { updateSliderPosition() {
if (v === 0 || v < this.upper) {
return
}
var sliderBox = document.getElementById('sliderBox') var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas') var container = document.getElementById('colorBarCanvas')
if (!sliderBox || !container) return
var containerWidth = container.clientWidth var containerWidth = container.clientWidth
var sliderWidth = sliderBox.clientWidth var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth var maxLeft = containerWidth - sliderWidth
var left = (this.upper / this.range) * maxLeft if (maxLeft <= 0) return
var range = Number(this.range) || 0
var upper = Number(this.upper) || 0
if (range <= 0) {
upper = 0
} else if (upper > range) {
upper = range
} else if (upper < 0) {
upper = 0
}
if (upper !== this.upper) {
this.upper = upper
}
var left = range > 0 ? (upper / range) * maxLeft : 0
if (left < 0) { if (left < 0) {
left = 6 left = 0
} else if (left >= maxLeft) { } else if (left >= maxLeft) {
left = maxLeft + 6 left = maxLeft
} }
var slider = document.getElementById('slider') var slider = document.getElementById('slider')
slider.style.left = left + 'px' if (slider) {
slider.style.left = (left + 6) + 'px'
}
var positionValue = document.getElementById('slider-position') var positionValue = document.getElementById('slider-position')
positionValue.textContent = this.upper if (positionValue) {
if (this.modality === 'NM') {
positionValue.textContent = range > 0 ? Math.round((upper / range) * 100) + '%' : '0%'
} else {
positionValue.textContent = upper
}
}
},
upperRangeChange(v) {
if (this.upper === v) return
var nextRange = Number(v) || 0
if (nextRange <= 0) {
this.upper = 0
this.updateSliderPosition()
this.voiChange(0)
return
}
this.range = nextRange
if (this.upper > nextRange) {
this.upper = nextRange
this.voiChange(this.upper)
}
this.syncSliderPosition()
}, },
createColorBar(rgbPresetName, elId, width, height) { createColorBar(rgbPresetName, elId, width, height) {
var colorMap = null var colorMap = null
@ -185,6 +278,7 @@ export default {
init() { init() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15) this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.$emit("setColorMap", this.rgbPresetName) this.$emit("setColorMap", this.rgbPresetName)
this.syncSliderPosition()
} }
} }
} }
@ -322,4 +416,4 @@ export default {
color: #ddd; color: #ddd;
} }
} }
</style> </style>

View File

@ -59,6 +59,7 @@
:parentQsId="parentQsId" :isBaseline="isBaseline" :reading-task-state="readingTaskState" :parentQsId="parentQsId" :isBaseline="isBaseline" :reading-task-state="readingTaskState"
:question-form="QuestionsForm" :visit-task-id="visitTaskId" :criterion-id="criterionId" :question-form="QuestionsForm" :visit-task-id="visitTaskId" :criterion-id="criterionId"
:type="addOrEdit.type" :calculationList="calculationList" :questionsMarkStatus="questionsMarkStatus" :type="addOrEdit.type" :calculationList="calculationList" :questionsMarkStatus="questionsMarkStatus"
:questionsSegmentMarkStatus="questionsSegmentMarkStatus"
@formItemTableNumberChange="formItemTableNumberChange" @resetFormItemData="resetTableFormItemData" @formItemTableNumberChange="formItemTableNumberChange" @resetFormItemData="resetTableFormItemData"
@setFormItemData="setFormTableItemData" @operateImageMarker="operateImageMarker" @save="save" @setFormItemData="setFormTableItemData" @operateImageMarker="operateImageMarker" @save="save"
@handleReadingChart="handleReadingChart" /> @handleReadingChart="handleReadingChart" />
@ -183,7 +184,49 @@
<el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled /> <el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled />
<!-- 数值 --> <!-- 数值 -->
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)"> <template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"> <div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"
v-if="question.ImageMarkTypeEnum === 1">
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)"
@blur="questionsSegmentMarkStatus[question.Id] ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
v-model="questionForm[question.Id]"
:title="questionsSegmentMarkStatus[question.Id] ? `${questionsSegmentMarkStatus[question.Id].SegmentationName}\n${questionsSegmentMarkStatus[question.Id].SegmentName}\n${questionForm[question.Id]}` : question.Remark"
:disabled="(questionsSegmentMarkStatus[question.Id] && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1 || readingTaskState === 2"
style="width: 150px;margin-right: 5px;">
<template v-if="question.Unit !== 0" slot="append">
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
</template>
</el-input>
<svg-icon v-if="question.ShowChartTypeEnum > 0 && taskInfo.IsReadingTaskViewInOrder === 1"
icon-class="readingChart" class="svg-icon svg-readingChart" @click.stop="(e) => handleReadingChart({
e,
data: {
QuestionId: question.Id,
QuestionName: question.QuestionName
}
})" />
<!-- 绑定 -->
<el-button v-if="readingTaskState < 2 && (!questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
@click="operateImageMarker({ operateStateEnum: 21, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:bind') }}
</el-button>
<!-- 更改 -->
<el-button v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
@click="operateImageMarker({ operateStateEnum: 22, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:edit') }}
</el-button>
<!-- 查看 -->
<el-button v-if="questionsSegmentMarkStatus[question.Id]" size="mini" type="text"
@click="operateImageMarker({ operateStateEnum: 23, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:view') }}
</el-button>
<!-- 移除 -->
<el-button v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
@click="operateImageMarker({ operateStateEnum: 24, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:remove') }}
</el-button>
</div>
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;" v-else>
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }" <el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)" @input="numberInput(question.Id)"
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)" @blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
@ -312,9 +355,10 @@
:isNoneDicom="isNoneDicom" :reading-task-state="readingTaskState" :question-form="questionForm" :isNoneDicom="isNoneDicom" :reading-task-state="readingTaskState" :question-form="questionForm"
:visit-task-id="visitTaskId" :criterion-id="criterionId" :calculationList="calculationList" :visit-task-id="visitTaskId" :criterion-id="criterionId" :calculationList="calculationList"
:questionMarkInfoList="questionMarkInfoList" :questionsMarkStatus="questionsMarkStatus" :questionMarkInfoList="questionMarkInfoList" :questionsMarkStatus="questionsMarkStatus"
@formItemNumberChange="formItemNumberChange" @setFormItemData="setFormItemData" :questionsSegmentMarkStatus="questionsSegmentMarkStatus" @formItemNumberChange="formItemNumberChange"
@resetFormItemData="resetFormItemData" @getQuestions="getQuestions" @operateImageMarker="operateImageMarker" @setFormItemData="setFormItemData" @resetFormItemData="resetFormItemData" @getQuestions="getQuestions"
@unBindAnnotationToQuestion="unBindAnnotationToQuestion" @handleReadingChart="handleReadingChart" /> @operateImageMarker="operateImageMarker" @unBindAnnotationToQuestion="unBindAnnotationToQuestion"
@handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" />
</template> </template>
<!-- <base-model :config="addOrEdit" <!-- <base-model :config="addOrEdit"
@ -397,6 +441,12 @@ export default {
return {} return {}
} }
}, },
questionsSegmentMarkStatus: {
type: Object,
default() {
return {}
}
},
isNoneDicom: { isNoneDicom: {
type: Boolean, type: Boolean,
default: false default: false
@ -556,8 +606,65 @@ export default {
this.operateImageMarker({ operateStateEnum, question: this.question, picturePath }) this.operateImageMarker({ operateStateEnum, question: this.question, picturePath })
} }
}) })
DicomEvent.$on('setTableQuestionAnswer', async (DATA) => {
let { id, answer, ParentQsId, data } = DATA
// console.log(ParentQsId, this.question.Id)
if (this.question.Id === ParentQsId) {
this.QuestionsForm[id] = answer
if (data.RowId) {
let i = this.AnswersList.findIndex(i => i.RowId === this.QuestionsForm.RowId)
this.AnswersList[i][id] = this.QuestionsForm[id]
this.$emit('setFormItemData', { key: this.question.Id, val: this.AnswersList, question: this.question })
this.formItemNumberChange(this.question.Id, true)
this.$emit('saveSegmentBindingAndAnswer', [data])
} else {
try {
// loading = this.$loading({ fullscreen: true })
let answers = []
let reg = new RegExp(/^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/)
for (const k in this.QuestionsForm) {
if (reg.test(k)) {
if (answers.findIndex(i => i.tableQuestionId === k) === -1) {
answers.push({ tableQuestionId: k, answer: this.QuestionsForm[k] })
}
}
}
this.QuestionsList.forEach(k => {
if (reg.test(k.Id)) {
if (answers.findIndex(i => i.tableQuestionId === k.Id) === -1) {
answers.push({ tableQuestionId: k.Id, answer: '' })
}
}
})
let params = {
questionId: this.question.Id,
RowIndex: this.questionForm[this.question.Id].length + 1,
RowId: this.QuestionsForm.RowId ? this.QuestionsForm.RowId : '',
visitTaskId: this.visitTaskId,
trialId: this.$route.query.trialId,
answerList: answers
}
let res = await submitTableQuestion(params)
if (res.IsSuccess) {
this.QuestionsForm.RowId = res.Result.RowId
obj.rowId = res.Result.RowId
data.RowId = res.Result.RowId
this.AnswersList.push(this.QuestionsForm)
this.$emit('setFormItemData', { key: this.question.Id, val: this.AnswersList, question: this.question })
this.formItemNumberChange(this.question.Id, true)
this.$emit('saveSegmentBindingAndAnswer', [data])
}
} catch (e) {
console.log(e)
}
}
}
})
}, },
methods: { methods: {
saveSegmentBindingAndAnswer(list) {
this.$emit('saveSegmentBindingAndAnswer', list)
},
handleReadingChart(row) { handleReadingChart(row) {
this.$emit('handleReadingChart', row) this.$emit('handleReadingChart', row)
}, },
@ -1349,13 +1456,13 @@ export default {
getAnnotationProp(annotation, prop) { getAnnotationProp(annotation, prop) {
if (!annotation) return if (!annotation) return
let referencedImageId = null let referencedImageId = null
if (annotation.from) { if (annotation.from || annotation.metadata.volumeId) {
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')}` referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? Number(0).toFixed(3) : Number(i).toFixed(3)).join(',')}`
} else { } else {
referencedImageId = annotation?.metadata?.referencedImageId referencedImageId = annotation?.metadata?.referencedImageId
} }
if (!referencedImageId) return null if (!referencedImageId) return null
const cacheKey = annotation.from ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}` const cacheKey = annotation.from || annotation.metadata.volumeId ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
const points = ['x', 'y', 'z']; const points = ['x', 'y', 'z'];
const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey] const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey]
const hasProp = cachedStats const hasProp = cachedStats

View File

@ -19,9 +19,10 @@
:isNoneDicom="isNoneDicom" :question="question" :question-form="questionForm" :isNoneDicom="isNoneDicom" :question="question" :question-form="questionForm"
:reading-task-state="readingTaskState" :criterion-id="criterionId" :calculation-list="calculationList" :reading-task-state="readingTaskState" :criterion-id="criterionId" :calculation-list="calculationList"
:question-mark-info-list="questionMarkInfoList" :questions-mark-status="questionsMarkStatus" :question-mark-info-list="questionMarkInfoList" :questions-mark-status="questionsMarkStatus"
:is-baseline="isBaseLineTask" @resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" :questionsSegmentMarkStatus="questionsSegmentMarkStatus" :is-baseline="isBaseLineTask"
@getQuestions="getQuestions" @operateImageMarker="operateImageMarker" @resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" @getQuestions="getQuestions"
@unBindAnnotationToQuestion="unBindAnnotationToQuestion" @handleReadingChart="handleReadingChart" /> @operateImageMarker="operateImageMarker" @unBindAnnotationToQuestion="unBindAnnotationToQuestion"
@handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" />
</template> </template>
<el-form-item v-if="readingTaskState < 2"> <el-form-item v-if="readingTaskState < 2">
@ -58,7 +59,7 @@
<script> <script>
import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, verifyVisitTaskQuestions, getQuestionCalculateRelation, saveTaskQuestion } from '@/api/trials' import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, verifyVisitTaskQuestions, getQuestionCalculateRelation, saveTaskQuestion } from '@/api/trials'
import { setSkipReadingCache, resetReadingTask, saveTableQuestionMark, saveAnswerAndBindingNoneDicomMark, changePlottingScaleChangeAnswer } from '@/api/reading' import { setSkipReadingCache, resetReadingTask, saveTableQuestionMark, saveAnswerAndBindingNoneDicomMark, changePlottingScaleChangeAnswer, getSegmentBindingList, saveSegmentBindingAndAnswer, getSegmentList, getSegmentationList } from '@/api/reading'
import const_ from '@/const/sign-code' import const_ from '@/const/sign-code'
import QuestionFormItem from './QuestionFormItem' import QuestionFormItem from './QuestionFormItem'
import SignForm from '@/views/trials/components/newSignForm' import SignForm from '@/views/trials/components/newSignForm'
@ -116,6 +117,7 @@ export default {
imageTool: '', imageTool: '',
imageToolAttribute: '', imageToolAttribute: '',
questionsMarkStatus: {}, questionsMarkStatus: {},
questionsSegmentMarkStatus: {},
digitPlaces: 2, digitPlaces: 2,
questionImageToolAttributeInfo: {}, questionImageToolAttributeInfo: {},
unSaveTargets: [], unSaveTargets: [],
@ -133,6 +135,7 @@ export default {
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.getQuestionCalculateRelation() this.getQuestionCalculateRelation()
this.getQuestions(true) this.getQuestions(true)
this.initSegmentBinding()
DicomEvent.$on('opentableCol', (data) => { DicomEvent.$on('opentableCol', (data) => {
let { visible } = data let { visible } = data
this.isTableVisible = visible this.isTableVisible = visible
@ -433,8 +436,13 @@ export default {
REMOVE: 4, // REMOVE: 4, //
SAVE_OUTER: 5, // SAVE_OUTER: 5, //
UPDATE: 6, // UPDATE: 6, //
SAVE_TABLE: 7 // SAVE_TABLE: 7, //
BIND_SEGMENT: 21, //
CHANGE_SEGMENT: 22, //
VIEW_SEGMENT: 23, //
REMOVE_SEGMENT: 24, //
} }
// console.log(obj, 'obj')
const { question, operateStateEnum, rowId, answer } = obj const { question, operateStateEnum, rowId, answer } = obj
const { Id, IsTableQuestion, ImageTool, ImageToolAttribute, ParentQsId, RowId, QuestionName, QuestionEnName } = question const { Id, IsTableQuestion, ImageTool, ImageToolAttribute, ParentQsId, RowId, QuestionName, QuestionEnName } = question
@ -446,7 +454,7 @@ export default {
operateQuestionId: Id, operateQuestionId: Id,
operateQuestionName: this.isEN ? QuestionEnName : QuestionName, operateQuestionName: this.isEN ? QuestionEnName : QuestionName,
operateRowId: RowId, operateRowId: RowId,
operateParentQsId: ParentQsId operateParentQsId: ParentQsId,
}) })
const stateHandlers = { const stateHandlers = {
[STATE.BIND]: () => this.$emit('setReadingToolPassive'), [STATE.BIND]: () => this.$emit('setReadingToolPassive'),
@ -456,12 +464,182 @@ export default {
[STATE.REMOVE]: this.handleRemoveAnnotation, [STATE.REMOVE]: this.handleRemoveAnnotation,
[STATE.SAVE_OUTER]: this.isNoneDicom ? this.handleSaveNoneDicomOuterQuestions : this.handleSaveOuterQuestions, [STATE.SAVE_OUTER]: this.isNoneDicom ? this.handleSaveNoneDicomOuterQuestions : this.handleSaveOuterQuestions,
[STATE.UPDATE]: this.handleUpdateValue, [STATE.UPDATE]: this.handleUpdateValue,
[STATE.SAVE_TABLE]: this.isNoneDicom ? this.handleSaveNoneDicomTableQuestions : this.handleSaveTableQuestions [STATE.SAVE_TABLE]: this.isNoneDicom ? this.handleSaveNoneDicomTableQuestions : this.handleSaveTableQuestions,
[STATE.BIND_SEGMENT]: this.handleBindSegment,
[STATE.CHANGE_SEGMENT]: this.handleBindSegment,
[STATE.VIEW_SEGMENT]: this.handleViewSegment,
[STATE.REMOVE_SEGMENT]: this.handleRemoveSegment,
} }
const handler = stateHandlers[operateStateEnum] const handler = stateHandlers[operateStateEnum]
handler && await handler.call(this, obj) handler && await handler.call(this, obj)
}, },
async handleViewSegment(obj) {
try {
let o = {}
if (this.isTableQuestion) {
o.TableQuestionId = this.operateQuestionId
o.RowId = this.operateRowId
} else {
o.QuestionId = this.operateQuestionId
}
let list = await this.getSegmentBindingList(o)
if (list && list.length > 0) {
let segment = await this.getSegmentList(list[0].SegmentId)
if (segment[0].SegmentJson) {
let obj = JSON.parse(segment[0].SegmentJson)
segment[0].stats = obj.stats
segment[0].bidirectional = obj.bidirectional
segment[0].segmentationId = segment[0].SegmentationId
segment[0].segmentIndex = segment[0].SegmentNumber
}
let segmentGroup = await this.getSegmentationList(list[0].SegmentationId)
this.$emit('viewCustomAnnotationSeries', {
visitTaskId: this.visitTaskId,
segment: segment[0],
segmentGroup: segmentGroup[0]
})
}
} catch (err) {
console.log(err)
}
},
handleBindSegment(obj) {
this.$emit("openSegmentForm", { visitInfo: this.visitInfo, operateStateEnum: obj.operateStateEnum, isTableQuestion: this.isTableQuestion, operateQuestionId: this.operateQuestionId, operateRowId: this.operateRowId })
},
handleSegmentSave(obj) {
let imageToolAttribute = this.imageToolAttribute
let answer = ''
if (imageToolAttribute === 'length' || imageToolAttribute === 'width') {
let s = {
length: "maxMajor",
width: 'maxMinor'
}
if (!obj.bidirectional || !obj.bidirectional.maxMajor) return this.$confirm(this.$t("segment:error:notValue"))
answer = obj.bidirectional[s[imageToolAttribute]] ? Number(obj.bidirectional[s[imageToolAttribute]]).toFixed(this.digitPlaces) : ''
} else {
if (!obj.stats) return this.$confirm(this.$t("segment:error:notValue"))
answer = obj.stats[imageToolAttribute] ? Number((obj.stats[imageToolAttribute]).value).toFixed(this.digitPlaces) : ''
}
let o = {
Answer: answer,
SegmentId: obj.Id,
SegmentationId: obj.SegmentationId,
QuestionId: this.isTableQuestion ? this.operateParentQsId : this.operateQuestionId,
RowId: this.isTableQuestion ? this.operateRowId : null,
TableQuestionId: this.isTableQuestion ? this.operateQuestionId : null,
VisitTaskId: this.visitTaskId,
}
if (this.isTableQuestion) {
return DicomEvent.$emit('setTableQuestionAnswer', { id: this.operateQuestionId, answer, ParentQsId: this.operateParentQsId, data: o })
} else {
this.$set(this.questionForm, this.operateQuestionId, answer)
}
this.saveSegmentBindingAndAnswer([o])
},
//
async handleRemoveSegment(obj) {
const { question } = obj
let confirm = await this.$confirm(this.$t('segment:confirm:sureDelete'))
if (!confirm) return false
this.$set(this.questionForm, question.Id, '')
let o = {
Answer: '',
SegmentId: null,
SegmentationId: null,
QuestionId: this.isTableQuestion ? this.operateParentQsId : this.operateQuestionId,
RowId: this.isTableQuestion ? this.operateRowId : null,
TableQuestionId: this.isTableQuestion ? this.operateQuestionId : null,
VisitTaskId: this.visitTaskId,
}
this.saveSegmentBindingAndAnswer([o])
},
//
async saveSegmentBindingAndAnswer(list) {
try {
let data = {
VisitTaskId: this.visitTaskId,
BindingList: list
}
let res = await saveSegmentBindingAndAnswer(data)
if (res.IsSuccess) {
this.initSegmentBinding()
}
} catch (err) {
console.log(err)
}
},
//
async initSegmentBinding() {
try {
let list = await this.getSegmentBindingList()
this.questionsSegmentMarkStatus = {}
list.forEach(item => {
if (item.TableQuestionId && item.RowId) {
this.$set(this.questionsSegmentMarkStatus, `${item.RowId}_${item.TableQuestionId}`, { SegmentId: item.SegmentId, SegmentationName: item.SegmentationName, SegmentName: item.SegmentName, })
} else {
this.$set(this.questionsSegmentMarkStatus, item.QuestionId, { SegmentId: item.SegmentId, SegmentationName: item.SegmentationName, SegmentName: item.SegmentName, })
}
})
} catch (err) {
console.log(err)
}
},
//
async getSegmentBindingList(param = {}) {
try {
let data = {
VisitTaskId: this.visitTaskId,
PageSize: 9999,
PageIndex: 1,
}
data = Object.assign(data, param)
let res = await getSegmentBindingList(data)
if (res.IsSuccess) {
return res.Result.CurrentPageData
}
} catch (err) {
console.log(err)
}
},
//
async getSegmentList(id) {
try {
let data = {
// SegmentationId: id,
PageSize: 9999,
PageIndex: 1,
}
if (id) data.Id = id
let res = await getSegmentList(data)
if (res.IsSuccess) {
return res.Result.CurrentPageData
}
} catch (err) {
console.log(err)
}
},
//
async getSegmentationList(id) {
try {
let data = {
VisitTaskId: this.visitInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
if (id) data.Id = id
let res = await getSegmentationList(data);
if (res.IsSuccess) {
return res.Result.CurrentPageData;
}
} catch (err) {
console.log(err)
}
},
async handleViewAnnotation(obj) { async handleViewAnnotation(obj) {
const index = this.findMarkIndex(obj.question) const index = this.findMarkIndex(obj.question)
if (index === -1) return if (index === -1) return
@ -1049,13 +1227,13 @@ export default {
getAnnotationProp(annotation, prop) { getAnnotationProp(annotation, prop) {
if (!annotation || !prop) return if (!annotation || !prop) return
let referencedImageId = null let referencedImageId = null
if (annotation.from) { if (annotation.from || annotation.metadata.volumeId) {
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')}` referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? Number(0).toFixed(3) : Number(i).toFixed(3)).join(',')}`
} else { } else {
referencedImageId = annotation?.metadata?.referencedImageId referencedImageId = annotation?.metadata?.referencedImageId
} }
if (!referencedImageId) return null if (!referencedImageId) return null
const cacheKey = annotation.from ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}` const cacheKey = annotation.from || annotation.metadata.volumeId ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
const points = ['x', 'y', 'z'] const points = ['x', 'y', 'z']
const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey] const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey]
const hasProp = cachedStats const hasProp = cachedStats

View File

@ -107,7 +107,53 @@
<el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled /> <el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled />
<!-- 数值 --> <!-- 数值 -->
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)"> <template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"> <div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"
v-if="question.ImageMarkTypeEnum === 1">
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)"
@blur="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
v-model="questionForm[question.Id]"
:title="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? `${questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].SegmentationName}\n${questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].SegmentName}\n${questionForm[question.Id]}` : question.Remark"
:disabled="(questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1 || question.IsPreinstall"
style="width: 150px;margin-right: 5px;">
<template v-if="question.Unit !== 0" slot="append">
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
</template>
</el-input>
<svg-icon v-if="question.ShowChartTypeEnum > 0 && taskInfo.IsReadingTaskViewInOrder === 1"
icon-class="readingChart" class="svg-icon svg-readingChart" @click.stop="(e) => handleReadingChart({
e,
data: {
TableQuestionId: question.Id,
RowIndex: questionForm.RowIndex,
QuestionName: question.QuestionName
}
})" />
<!-- 绑定 -->
<el-button
v-if="readingTaskState < 2 && (!questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 21, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:bind') }}
</el-button>
<!-- 更改 -->
<el-button
v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 22, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:edit') }}
</el-button>
<!-- 查看 -->
<el-button v-if="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id]" size="mini"
type="text" @click="operateImageMarker({ operateStateEnum: 23, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:view') }}
</el-button>
<!-- 移除 -->
<el-button
v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 24, question })">
{{ $t('dicom3D:CustomizeQuestionFormItem:button:remove') }}
</el-button>
</div>
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;" v-else>
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }" <el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)" @input="numberInput(question.Id)"
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)" @blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
@ -276,6 +322,12 @@ export default {
return {} return {}
} }
}, },
questionsSegmentMarkStatus: {
type: Object,
default() {
return {}
}
},
parentQsId: { parentQsId: {
type: String, type: String,
default: '' default: ''

View File

@ -0,0 +1,8 @@
import setPetTransferFunctionForVolumeActor from "./setPetTransferFunctionForVolumeActor";
import setMipTransferFunctionForVolumeActor from "./setMipTransferFunctionForVolumeActor";
export {
setPetTransferFunctionForVolumeActor,
setMipTransferFunctionForVolumeActor
};

View File

@ -0,0 +1,49 @@
import { cache, metaData, utilities } from "@cornerstonejs/core";
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
export default function setMipTransferFunctionForVolumeActor({
volumeActor,
volumeId,
}) {
const mapper = volumeActor.getMapper?.();
if (mapper?.setSampleDistance) {
mapper.setSampleDistance(1.0);
}
const rgbTransferFunction = volumeActor
.getProperty()
.getRGBTransferFunction(0);
let range = null;
const imageVolume = volumeId ? cache.getVolume(volumeId) : null;
const imageId = imageVolume?.imageIds?.[0];
if (imageId) {
const voiLutModule = metaData.get("voiLutModule", imageId);
const rawCenter = Array.isArray(voiLutModule?.windowCenter)
? voiLutModule.windowCenter[0]
: voiLutModule?.windowCenter;
const rawWidth = Array.isArray(voiLutModule?.windowWidth)
? voiLutModule.windowWidth[0]
: voiLutModule?.windowWidth;
const center = Number(rawCenter);
const width = Number(rawWidth);
if (Number.isFinite(center) && Number.isFinite(width) && width > 0) {
const upper = center + width / 2;
range = [0, upper];
} else if (Number.isFinite(center)) {
range = [0, center];
}
}
if (!range) {
range = [0, 5];
}
rgbTransferFunction.setRange(range[0], range[1]);
utilities.invertRgbTransferFunction(rgbTransferFunction);
const upper = Number(range[1]);
if (Number.isFinite(upper) && upper > 0) {
const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(0, 0.0);
ofun.addPoint(upper * 0.02, 0.9);
ofun.addPoint(upper, 1.0);
volumeActor.getProperty().setScalarOpacity(0, ofun);
}
}

View File

@ -0,0 +1,39 @@
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
import { cache, metaData, utilities } from "@cornerstonejs/core";
const { getColormap } = utilities.colormap;
function getWindowCenterFromVolumeId(volumeId) {
if (!volumeId) return null;
const imageVolume = cache.getVolume?.(volumeId);
const imageId = imageVolume?.imageIds?.[0];
if (!imageId) return null;
const voiLutModule = metaData.get("voiLutModule", imageId);
const rawCenter = Array.isArray(voiLutModule?.windowCenter)
? voiLutModule.windowCenter[0]
: voiLutModule?.windowCenter;
const center = Number(rawCenter);
return Number.isFinite(center) ? center : null;
}
export default function setPetColorMapTransferFunctionForVolumeActor({
volumeActor,
volumeId,
preset,
}) {
const mapper = volumeActor.getMapper?.();
if (mapper?.setSampleDistance) {
mapper.setSampleDistance(1.0);
}
const cfun = vtkColorTransferFunction.newInstance();
const presetToUse = preset || getColormap("siemens");
cfun.applyColorMap(presetToUse);
const center = getWindowCenterFromVolumeId(volumeId);
const upper = center > 1 ? center : 5;
cfun.setMappingRange(1, upper);
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
}

View File

@ -0,0 +1,11 @@
import { utilities } from '@cornerstonejs/core';
export default function setPetTransferFunction({ volumeActor }) {
const rgbTransferFunction = volumeActor
.getProperty()
.getRGBTransferFunction(0);
rgbTransferFunction.setRange(0, 5);
utilities.invertRgbTransferFunction(rgbTransferFunction);
}

View File

@ -2070,12 +2070,12 @@ export default {
.merge-table { .merge-table {
padding: 0 10px; padding: 0 10px;
::v-deep.el-table { ::v-deep .el-table {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #383838; color: #383838;
} }
::v-deep.el-table td.el-table__cell, ::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf { .el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #383838; border-bottom: 1px solid #383838;
} }
@ -2086,7 +2086,7 @@ export default {
background-color: #1e1e1e; background-color: #1e1e1e;
} }
::v-deep.el-table__header-wrapper { ::v-deep .el-table__header-wrapper {
th { th {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -2094,7 +2094,7 @@ export default {
} }
} }
::v-deep.el-table__body-wrapper { ::v-deep .el-table__body-wrapper {
tr { tr {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
color: #dfdfdf; color: #dfdfdf;
@ -2105,7 +2105,7 @@ export default {
} }
} }
::v-deep.el-table__empty-block { ::v-deep .el-table__empty-block {
background-color: #1e1e1e !important; background-color: #1e1e1e !important;
} }

View File

@ -386,6 +386,35 @@ const config = {
'disabledReason': '' 'disabledReason': ''
}, },
], ],
'customizeStandardsSegmentDicom': [
// {
// 'name': 'Contour分割',
// 'icon': 'contour',
// 'toolName': 'Contour',
// 'props': ['length'],
// 'i18nKey': 'trials:reading:button:Contour',
// 'isDisabled': false,
// 'disabledReason': ''
// },
{
'name': 'Labelmap分割',
'icon': 'labelmap',
'toolName': 'Labelmap',
'props': ['max', 'min', 'volume', 'count', 'mean', 'stdDev', 'length', 'width'],
'i18nKey': 'trials:reading:button:Labelmap',
'isDisabled': false,
'disabledReason': ''
},
// {
// 'name': 'Surface分割',
// 'icon': 'surface',
// 'toolName': 'Surface',
// 'props': ['area', 'mean', 'max', 'stdDev'],
// 'i18nKey': 'trials:reading:button:Surface',
// 'isDisabled': false,
// 'disabledReason': ''
// },
],
} }
const getTools = (criterionType) => { const getTools = (criterionType) => {
const standard = config.standards.find(s => s.type === criterionType) const standard = config.standards.find(s => s.type === criterionType)
@ -399,4 +428,8 @@ const getCustomizeStandardsNoneDicomTools = (toolNames) => {
const filteredTools = config.customizeStandardsNoneDicom.filter(item => toolNames.includes(item.toolName)) const filteredTools = config.customizeStandardsNoneDicom.filter(item => toolNames.includes(item.toolName))
return filteredTools || [] return filteredTools || []
} }
export { config, getTools, getCustomizeStandardsTools, getCustomizeStandardsNoneDicomTools } const getCustomizeStandardsSegmentDicomTools = (toolNames) => {
const filteredTools = config.customizeStandardsSegmentDicom.filter(item => toolNames.includes(item.toolName))
return filteredTools || []
}
export { config, getTools, getCustomizeStandardsTools, getCustomizeStandardsNoneDicomTools, getCustomizeStandardsSegmentDicomTools }

View File

@ -316,11 +316,13 @@ class ScaleOverlayTool extends AnnotationDisplayTool {
width: canvas.width / window.devicePixelRatio || 1, width: canvas.width / window.devicePixelRatio || 1,
height: canvas.height / window.devicePixelRatio || 1, height: canvas.height / window.devicePixelRatio || 1,
}; };
if(!annotation||!annotation.data) return false
const topLeft = annotation.data.handles.points[0]; const topLeft = annotation.data.handles.points[0];
const topRight = annotation.data.handles.points[1]; const topRight = annotation.data.handles.points[1];
const bottomLeft = annotation.data.handles.points[2]; const bottomLeft = annotation.data.handles.points[2];
const bottomRight = annotation.data.handles.points[3]; const bottomRight = annotation.data.handles.points[3];
const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight]; const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight];
if(!bottomLeft) return false
const worldWidthViewport = vec3.distance(bottomLeft, bottomRight); const worldWidthViewport = vec3.distance(bottomLeft, bottomRight);
const worldHeightViewport = vec3.distance(topLeft, bottomLeft); const worldHeightViewport = vec3.distance(topLeft, bottomLeft);
const hscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location); const hscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location);

View File

@ -0,0 +1,264 @@
// import { getEnabledElement, utilities as csUtils, getEnabledElementByViewportId, utilities, } from '@cornerstonejs/core';
// import { addAnnotation, getAllAnnotations, getAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
// import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
// import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
// import { drawLine as drawLineSvg, drawHandles as drawHandlesSvg, } from '../../drawingSvg';
// import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
// import { hideElementCursor } from '../../cursors/elementCursor';
// import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
// import BidirectionalTool from '../annotation/BidirectionalTool';
// import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/segmentationColor';
import { utilities, getEnabledElementByViewportId, getEnabledElement } from '@cornerstonejs/core';
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
annotation,
drawing,
utilities: TSUtilities,
BidirectionalTool,
cursors,
segmentation
} = cornerstoneTools
const { addAnnotation, getAllAnnotations, getAnnotations, removeAnnotation } = annotation.state
const { isAnnotationLocked } = annotation.locking
const { isAnnotationVisible } = annotation.visibility
const { drawLine: drawLineSvg, drawHandles: drawHandlesSvg } = drawing
const { getViewportIdsWithToolToRender } = TSUtilities.viewportFilters
const { triggerAnnotationRenderForViewportIds } = TSUtilities
const { hideElementCursor } = cursors.elementCursor
const { getSegmentIndexColor } = segmentation.config.color
class SegmentBidirectionalTool extends BidirectionalTool {
static { this.toolName = 'SegmentBidirectional'; }
constructor(toolProps = {}) {
super(toolProps);
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
let renderStatus = true;
const { viewport } = enabledElement;
const { element } = viewport;
const viewportId = viewport.id;
let annotations = getAnnotations(this.getToolName(), element);
if (!annotations?.length) {
return renderStatus;
}
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
if (!annotations?.length) {
return renderStatus;
}
const targetId = this.getTargetId(viewport);
const renderingEngine = viewport.getRenderingEngine();
const styleSpecifier = {
toolGroupId: this.toolGroupId,
toolName: this.getToolName(),
viewportId: enabledElement.viewport.id,
};
for (let i = 0; i < annotations.length; i++) {
const annotation = annotations[i];
const { annotationUID, data } = annotation;
const { points, activeHandleIndex } = data.handles;
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
styleSpecifier.annotationUID = annotationUID;
const { segmentIndex, segmentationId } = annotation.metadata;
const { lineWidth, lineDash, shadow } = this.getAnnotationStyle({
annotation,
styleSpecifier,
});
const colorArray = getSegmentIndexColor(viewportId, segmentationId, segmentIndex) || [255, 255, 255];
// console.log(viewportId, segmentationId, segmentIndex, 'colorArray')
// console.log(colorArray, 'colorArray')
const color = `rgb(${colorArray.slice(0, 3).join(',')})`;
if (!data.cachedStats[targetId] ||
data.cachedStats[targetId].unit == null) {
data.cachedStats[targetId] = {
length: null,
width: null,
unit: null,
};
this._calculateCachedStats(annotation, renderingEngine, enabledElement);
}
else if (annotation.invalidated) {
this._throttledCalculateCachedStats(annotation, renderingEngine, enabledElement);
}
if (!viewport.getRenderingEngine()) {
console.warn('Rendering Engine has been destroyed');
return renderStatus;
}
let activeHandleCanvasCoords;
if (!isAnnotationVisible(annotationUID)) {
continue;
}
if (!isAnnotationLocked(annotationUID) &&
!this.editData &&
activeHandleIndex !== null) {
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
}
if (activeHandleCanvasCoords) {
const handleGroupUID = '0';
drawHandlesSvg(svgDrawingHelper, annotationUID, handleGroupUID, activeHandleCanvasCoords, {
color,
});
}
const dataId1 = `${annotationUID}-line-1`;
const dataId2 = `${annotationUID}-line-2`;
const lineUID = '0';
drawLineSvg(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], {
color,
lineWidth,
lineDash,
shadow,
}, dataId1);
const secondLineUID = '1';
drawLineSvg(svgDrawingHelper, annotationUID, secondLineUID, canvasCoordinates[2], canvasCoordinates[3], {
color,
lineWidth,
lineDash,
shadow,
}, dataId2);
renderStatus = true;
const textLines = this.configuration.getTextLines(data, targetId);
if (!textLines || textLines.length === 0) {
continue;
}
if (!this.renderLinkedTextBoxAnnotation({
enabledElement,
svgDrawingHelper,
annotation,
styleSpecifier,
textLines,
canvasCoordinates,
})) {
continue;
}
}
return renderStatus;
};
}
addNewAnnotation(evt) {
const eventDetail = evt.detail;
const { currentPoints, element } = eventDetail;
const worldPos = currentPoints.world;
const enabledElement = getEnabledElement(element);
const { viewport } = enabledElement;
this.isDrawing = true;
const camera = viewport.getCamera();
const { viewPlaneNormal, viewUp } = camera;
const referencedImageId = this.getReferencedImageId(viewport, worldPos, viewPlaneNormal, viewUp);
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
const annotation = {
highlighted: true,
invalidated: true,
metadata: {
toolName: this.getToolName(),
viewPlaneNormal: [...viewPlaneNormal],
viewUp: [...viewUp],
FrameOfReferenceUID,
referencedImageId,
...viewport.getViewReference({ points: [worldPos] }),
},
data: {
handles: {
points: [
[...worldPos],
[...worldPos],
[...worldPos],
[...worldPos],
],
textBox: {
hasMoved: false,
worldPosition: [0, 0, 0],
worldBoundingBox: {
topLeft: [0, 0, 0],
topRight: [0, 0, 0],
bottomLeft: [0, 0, 0],
bottomRight: [0, 0, 0],
},
},
activeHandleIndex: null,
},
label: '',
cachedStats: {},
},
};
addAnnotation(annotation, element);
const viewportIdsToRender = getViewportIdsWithToolToRender(element, this.getToolName());
this.editData = {
annotation,
viewportIdsToRender,
handleIndex: 1,
movingTextBox: false,
newAnnotation: true,
hasMoved: false,
};
this._activateDraw(element);
hideElementCursor(element);
evt.preventDefault();
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
return annotation;
}
static {
this.hydrate = (viewportId, axis, options) => {
const enabledElement = getEnabledElementByViewportId(viewportId);
if (!enabledElement) {
return;
}
const { viewport } = enabledElement;
const existingAnnotations = getAllAnnotations();
const toolAnnotations = existingAnnotations.filter((annotation) => annotation.metadata.toolName === 'SegmentBidirectional');
const existingAnnotation = toolAnnotations.find((annotation) => {
const { metadata } = annotation;
if (metadata.segmentIndex === options?.segmentIndex &&
metadata.segmentationId === options?.segmentationId) {
return true;
}
return false;
});
if (existingAnnotation) {
removeAnnotation(existingAnnotation.annotationUID);
}
const { FrameOfReferenceUID, referencedImageId, viewPlaneNormal, instance, } = this.hydrateBase(SegmentBidirectionalTool, enabledElement, axis[0], options);
const [majorAxis, minorAxis] = axis;
const [major0, major1] = majorAxis;
const [minor0, minor1] = minorAxis;
const points = [major0, major1, minor0, minor1];
const { toolInstance, ...serializableOptions } = options || {};
const annotation = {
annotationUID: options?.annotationUID || utilities.uuidv4(),
data: {
handles: {
points,
activeHandleIndex: null,
textBox: {
hasMoved: false,
worldPosition: [0, 0, 0],
worldBoundingBox: {
topLeft: [0, 0, 0],
topRight: [0, 0, 0],
bottomLeft: [0, 0, 0],
bottomRight: [0, 0, 0],
},
},
},
cachedStats: {},
},
highlighted: false,
autoGenerated: false,
invalidated: false,
isLocked: false,
isVisible: true,
metadata: {
segmentIndex: options?.segmentIndex,
segmentationId: options?.segmentationId,
toolName: instance.getToolName(),
viewPlaneNormal,
FrameOfReferenceUID,
referencedImageId,
...serializableOptions,
},
};
addAnnotation(annotation, viewport.element);
triggerAnnotationRenderForViewportIds([viewport.id]);
return annotation;
};
}
}
export default SegmentBidirectionalTool;

View File

@ -317,7 +317,7 @@ export default {
color: yellow; color: yellow;
} }
} }
::v-deep.el-button--text{ ::v-deep .el-button--text{
color: #d0d0d0; color: #d0d0d0;
} }
} }

View File

@ -494,7 +494,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.medical-feedback-ir{ .medical-feedback-ir{
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
} }

View File

@ -209,24 +209,28 @@ export default {
}, },
methods: { methods: {
// //
getMessageList() { async getMessageList() {
this.loading = true try {
var param = { this.loading = true
taskMedicalReviewId: this.taskMedicalReviewId var param = {
} taskMedicalReviewId: this.taskMedicalReviewId
getMedicalReviewDialog(param).then(res => { }
let res = await getMedicalReviewDialog(param)
this.otherInfo = res.OtherInfo this.otherInfo = res.OtherInfo
this.recordContent = res.Result this.recordContent = res.Result
this.setScrollHeight() this.setScrollHeight()
this.loading = false this.loading = false
}).catch(() => { } catch(e) {
console.log(e)
this.loading = false this.loading = false
}) }
}, },
setScrollHeight() { setScrollHeight() {
setTimeout(() => { setTimeout(() => {
var container = document.querySelectorAll('.chat-content')[0] var container = document.querySelectorAll('.chat-content')[0]
container.scrollTop = container.scrollHeight if (container && container.scrollHeight) {
container.scrollTop = container.scrollHeight
}
}, 100) }, 100)
}, },
// //
@ -366,7 +370,7 @@ export default {
color: yellow; color: yellow;
} }
} }
::v-deep.el-button--text{ ::v-deep .el-button--text{
color: #d0d0d0; color: #d0d0d0;
} }
} }

View File

@ -80,26 +80,32 @@ export default {
handleClose() { handleClose() {
this.$emit('close') this.$emit('close')
}, },
handleSave() { async handleSave() {
this.$refs['closeQCForm'].validate((valid) => { let validate = await this.$refs['closeQCForm'].validate()
if (!valid) return if (!validate) return
this.loading = true this.form.TaskMedicalReviewId = this.taskMedicalReviewId
this.form.TaskMedicalReviewId = this.taskMedicalReviewId this.form.IsClosedDialog = true
this.form.IsClosedDialog = true this.$emit('closeAndSign', this.form)
closedMedicalReviewDialog(this.form) },
.then(res => { // handleSave() {
this.loading = false // this.$refs['closeQCForm'].validate((valid) => {
if (res.IsSuccess) { // if (!valid) return
// ! // this.loading = true
this.$message.success(this.$t('trials:qcQuality:message:closedSuccessfully')) // this.form.TaskMedicalReviewId = this.taskMedicalReviewId
this.$emit('close') // this.form.IsClosedDialog = true
this.$emit('refresh') // closedMedicalReviewDialog(this.form)
} // .then(res => {
}).catch(() => { // this.loading = false
this.loading = false // if (res.IsSuccess) {
}) // // !
}) // this.$message.success(this.$t('trials:qcQuality:message:closedSuccessfully'))
} // this.$emit('refresh')
// }
// }).catch(() => {
// this.loading = false
// })
// })
// }
} }
} }
</script> </script>

View File

@ -161,16 +161,16 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.issues-form{ .issues-form{
::v-deep.el-radio-group{ ::v-deep .el-radio-group{
width:300px; width:300px;
} }
::v-deep.el-textarea{ ::v-deep .el-textarea{
width:300px; width:300px;
} }
::v-deep.el-input{ ::v-deep .el-input{
width:300px; width:300px;
} }
::v-deep.el-select{ ::v-deep .el-select{
width:300px; width:300px;
} }

View File

@ -553,8 +553,8 @@
> >
<CloseQC <CloseQC
:task-medical-review-id="currentRow.Id" :task-medical-review-id="currentRow.Id"
@closeAndSign="closeAndSign"
@close="closeQuestionVisible = false" @close="closeQuestionVisible = false"
@refresh="refresh"
/> />
</el-dialog> </el-dialog>
<!--签名框 --> <!--签名框 -->
@ -645,7 +645,8 @@ export default {
signCode: null, signCode: null,
currentUser: zzSessionStorage.getItem('userName'), currentUser: zzSessionStorage.getItem('userName'),
signVisible: false, signVisible: false,
timeList: [] timeList: [],
closeObj: null
} }
}, },
watch: { watch: {
@ -697,6 +698,12 @@ export default {
this.TrialReadingCriterionId = this.trialCriterionList[0].TrialReadingCriterionId this.TrialReadingCriterionId = this.trialCriterionList[0].TrialReadingCriterionId
}).catch(() => {}) }).catch(() => {})
}, },
closeAndSign(obj) {
this.closeObj = Object.assign({}, obj)
const { MedicalAudit } = const_.processSignature
this.signCode = MedicalAudit
this.signVisible = true
},
// //
closeSignDialog(isSign, signInfo) { closeSignDialog(isSign, signInfo) {
if (isSign) { if (isSign) {
@ -706,38 +713,39 @@ export default {
} }
}, },
// //
signConfirm(signInfo) { async signConfirm(signInfo) {
this.loading = true try {
const params = { this.loading = true
data: { const params = {
taskMedicalReviewId: this.currentRow.Id data: {
}, taskMedicalReviewId: this.currentRow.Id,
signInfo: signInfo isClosedDialog: this.closeObj.IsClosedDialog,
} medicalDialogCloseEnum: this.closeObj.MedicalDialogCloseEnum,
FinishMedicalReview(params).then(res => { dialogCloseReason: this.closeObj.DialogCloseReason,
},
signInfo: signInfo
}
let res = await FinishMedicalReview(params)
if (res.IsSuccess) { if (res.IsSuccess) {
await this.$refs['chatForm'].getMessageList()
this.$message.success(this.$t('common:message:savedSuccessfully')) this.$message.success(this.$t('common:message:savedSuccessfully'))
this.$refs['signForm'].btnLoading = false this.$refs['signForm'].btnLoading = false
this.signVisible = false this.signVisible = false
this.$nextTick(() => { this.$nextTick(() => {
this.closeQuestionVisible = false
this.chatForm.visible = false this.chatForm.visible = false
}) })
await this.getList()
this.$emit('nextTask', this.taskMedicalReviewId)
} }
this.loading = false this.loading = false
this.getList()
.then(() => {
this.loading = true
this.$emit('nextTask', this.taskMedicalReviewId)
})
.catch(action => {
}) } catch(e) {
}).catch(_ => {
this.loading = false this.loading = false
if (this.$refs['signForm']) { if (this.$refs['signForm']) {
this.$refs['signForm'].btnLoading = false this.$refs['signForm'].btnLoading = false
} }
}) }
}, },
changeTimeList() { changeTimeList() {
if (this.timeList) { if (this.timeList) {

View File

@ -297,7 +297,7 @@ export default {
height: 100%; height: 100%;
padding: 0 10px; padding: 0 10px;
box-sizing: border-box; box-sizing: border-box;
::v-deep.el-tabs{ ::v-deep .el-tabs{
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
display: flex; display: flex;
@ -324,12 +324,12 @@ export default {
} }
} }
::v-deep.dialog-container{ ::v-deep .dialog-container{
margin-top: 50px !important; margin-top: 50px !important;
width:75%; width:75%;
height:80%; height:80%;
} }
::v-deep.el-dialog__body{ ::v-deep .el-dialog__body{
padding: 20px 20px 0 20px; padding: 20px 20px 0 20px;
height: calc(100% - 70px); height: calc(100% - 70px);
} }
@ -339,7 +339,7 @@ export default {
} }
.full-dialog-container{ .full-dialog-container{
::v-deep.is-fullscreen .el-dialog__body{ ::v-deep .is-fullscreen .el-dialog__body{
height: calc(100% - 70px); height: calc(100% - 70px);
} }
} }

View File

@ -884,7 +884,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.read-task-allocation { .read-task-allocation {
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
} }

View File

@ -548,7 +548,7 @@ export default {
height: 100%; height: 100%;
background-color: #fff; background-color: #fff;
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
} }

View File

@ -323,12 +323,8 @@ export default {
) )
var token = getToken() var token = getToken()
var path = '' var path = ''
if (this.readingTool === 0 || this.readingTool === 2) { if (this.readingTool === 0 || this.readingTool === 2 || this.readingTool === 3) {
if (this.criterionType === 0 && this.trialId === '08dd28b3-6843-fc05-0242-ac1301000000') { path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
path = `/fusion?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&studyId=62b3dfc4-1e04-4180-910d-fe595f398361&ctseriesId=1bd24f53-d419-32e5-92d4-2b04640aaa65&ptseriesId=2b7b128d-8c3f-8357-ad14-e38f3acbbdff&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&TokenKey=${token}&lang=${this.$i18n.locale}`
} else {
path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
}
} else { } else {
path = `/noneDicomReading?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}` path = `/noneDicomReading?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
} }
@ -408,7 +404,7 @@ export default {
height: 100%; height: 100%;
background-color: #fff; background-color: #fff;
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
} }

View File

@ -466,7 +466,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.reread-task{ .reread-task{
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
} }

View File

@ -246,7 +246,7 @@ export default {
border: 1px solid #607d8b !important; border: 1px solid #607d8b !important;
} }
::v-deep.el-progress__text { ::v-deep .el-progress__text {
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
@ -295,7 +295,7 @@ export default {
} }
} }
::v-deep.el-collapse { ::v-deep .el-collapse {
border: none; border: none;
.el-collapse-item { .el-collapse-item {

View File

@ -221,12 +221,12 @@ export default {
overflow-y: auto; overflow-y: auto;
} }
::v-deep.el-collapse-item__header { ::v-deep .el-collapse-item__header {
background: #e5ecef; background: #e5ecef;
padding-left: 10px; padding-left: 10px;
} }
::v-deep.el-collapse-item__content { ::v-deep .el-collapse-item__content {
padding: 10px; padding: 10px;
} }

View File

@ -562,7 +562,7 @@ export default {
background: #fff; background: #fff;
padding: 0px 10px; padding: 0px 10px;
box-sizing: border-box; box-sizing: border-box;
::v-deep.search { ::v-deep .search {
padding: 0px !important; padding: 0px !important;
} }
::v-deep .el-tabs--border-card>.el-tabs__content { ::v-deep .el-tabs--border-card>.el-tabs__content {

View File

@ -484,8 +484,15 @@
:disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio> :disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 测量工具 ImageTool --> <!--标记类型-->
<el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2" <el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2"
:label="$t('trials:readingUnit:qsList:title:ImageMarkTypeEnum')" prop="ImageMarkTypeEnum">
<el-radio-group v-model="form.ImageMarkTypeEnum" @change="ImageMarkTypeEnumChange">
<el-radio v-for="item of $d.ImageMarkType" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<!-- 测量工具 ImageTool -->
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 0"
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[ :label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
{ required: true, message: this.$t('common:ruleMessage:select') } { required: true, message: this.$t('common:ruleMessage:select') }
]"> ]">
@ -495,6 +502,17 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 分割工具 ImageTool -->
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 1"
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
{ required: true, message: this.$t('common:ruleMessage:select') }
]">
<el-radio-group v-model="form.ImageTool" @change="imageSegmentToolChange">
<el-radio v-for="tool of readingSegmentTools" :key="tool.toolName" :label="tool.toolName">
{{ $t(tool.i18nKey) }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 测量值 ImageToolAttribute --> <!-- 测量值 ImageToolAttribute -->
<el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0" <el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0"
:label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[ :label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[
@ -696,6 +714,12 @@ export default {
return [] return []
} }
}, },
readingSegmentTools: {
type: Array,
default() {
return []
}
},
readingVersionEnum: { readingVersionEnum: {
type: Number, type: Number,
default: 0 default: 0
@ -754,6 +778,7 @@ export default {
ClassifyEditType: null, ClassifyEditType: null,
ClassifyShowType: null, ClassifyShowType: null,
ImageMarkEnum: 0, ImageMarkEnum: 0,
ImageMarkTypeEnum: 0,
ShowChartTypeEnum: 0, ShowChartTypeEnum: 0,
ImageTool: '', ImageTool: '',
ImageToolAttribute: '', ImageToolAttribute: '',
@ -1050,9 +1075,12 @@ export default {
} }
} }
} }
if (this.form.ImageTool) { if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 0) {
this.imageToolChange(this.form.ImageTool) this.imageToolChange(this.form.ImageTool)
} }
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 1) {
this.imageSegmentToolChange(this.form.ImageTool)
}
} }
if (!this.data.ShowOrder && this.data.ShowOrder !== 0) { if (!this.data.ShowOrder && this.data.ShowOrder !== 0) {
if (this.list.length > 0) { if (this.list.length > 0) {
@ -1172,14 +1200,26 @@ export default {
this.form.ImageTool = '' this.form.ImageTool = ''
this.form.ImageToolAttribute = '' this.form.ImageToolAttribute = ''
this.imageToolAttributes = [] this.imageToolAttributes = []
this.form.ImageMarkTypeEnum = 0
} }
}, },
ImageMarkTypeEnumChange(val) {
this.form.ImageTool = ''
this.form.ImageToolAttribute = ''
this.imageToolAttributes = []
},
imageToolChange(v) { imageToolChange(v) {
let i = this.readingTools.findIndex(tool => tool.toolName === v) let i = this.readingTools.findIndex(tool => tool.toolName === v)
if (i > -1) { if (i > -1) {
this.imageToolAttributes = this.readingTools[i].props this.imageToolAttributes = this.readingTools[i].props
} }
}, },
imageSegmentToolChange(v) {
let i = this.readingSegmentTools.findIndex(tool => tool.toolName === v)
if (i > -1) {
this.imageToolAttributes = this.readingSegmentTools[i].props
}
},
parentQuestionChange(val, form) { parentQuestionChange(val, form) {
this.isParentExistGroup = false this.isParentExistGroup = false
if (val) { if (val) {
@ -1273,6 +1313,7 @@ export default {
form.ClassifyType = null form.ClassifyType = null
form.ClassifyShowType = null form.ClassifyShowType = null
form.ImageMarkEnum = 0 form.ImageMarkEnum = 0
form.ImageMarkTypeEnum = 0
form.ShowChartTypeEnum = 0 form.ShowChartTypeEnum = 0
form.ImageTool = '' form.ImageTool = ''
form.ImageToolAttribute = '' form.ImageToolAttribute = ''

View File

@ -1,148 +1,81 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<div <div class="search-form" style="text-align: left;">
class="search-form"
style="text-align: left;"
>
<!-- 新增 --> <!-- 新增 -->
<el-button <el-button v-if="!isConfirm && hasPermi(['trials:trials-panel:setting:reading-unit:edit']) && !isFromSystem"
v-if="!isConfirm && hasPermi(['trials:trials-panel:setting:reading-unit:edit']) && !isFromSystem" size="mini" type="primary" @click="handleAdd">
size="mini"
type="primary"
@click="handleAdd"
>
{{ $t('common:button:add') }} {{ $t('common:button:add') }}
</el-button> </el-button>
<!-- 预览 --> <!-- 预览 -->
<el-button <el-button v-if="isPreview" :disabled="tblList.length === 0" size="mini" type="primary" @click="handlePreview">
v-if="isPreview"
:disabled="tblList.length===0"
size="mini"
type="primary"
@click="handlePreview"
>
{{ $t('common:button:preview') }} {{ $t('common:button:preview') }}
</el-button> </el-button>
</div> </div>
<el-table <el-table :data="tblList" size="small">
:data="tblList" <el-table-column prop="ShowOrder" label="" width="50" />
size="small"
>
<el-table-column
prop="ShowOrder"
label=""
width="50"
/>
<!-- 分组 --> <!-- 分组 -->
<el-table-column <el-table-column prop="QuestionGroupName" v-if="$i18n.locale === 'zh'"
prop="QuestionGroupName" :label="$t('trials:readingUnit:qsList:title:groupNameEn')" show-overflow-tooltip />
v-if="$i18n.locale === 'zh'"
:label="$t('trials:readingUnit:qsList:title:groupNameEn')"
show-overflow-tooltip
/>
<!-- 分组(EN) --> <!-- 分组(EN) -->
<el-table-column <el-table-column prop="QuestionGroupEnName" v-if="$i18n.locale === 'en'"
prop="QuestionGroupEnName" :label="$t('trials:readingUnit:qsList:title:groupNameEn')" show-overflow-tooltip />
v-if="$i18n.locale === 'en'"
:label="$t('trials:readingUnit:qsList:title:groupNameEn')"
show-overflow-tooltip
/>
<!-- 名称 --> <!-- 名称 -->
<el-table-column <el-table-column prop="QuestionName" v-if="$i18n.locale === 'zh'"
prop="QuestionName" :label="$t('trials:readingUnit:qsList:title:qsNameEn')" show-overflow-tooltip />
v-if="$i18n.locale === 'zh'"
:label="$t('trials:readingUnit:qsList:title:qsNameEn')"
show-overflow-tooltip
/>
<!-- 名称(EN) --> <!-- 名称(EN) -->
<el-table-column <el-table-column prop="QuestionEnName" v-if="$i18n.locale === 'en'"
prop="QuestionEnName" :label="$t('trials:readingUnit:qsList:title:qsNameEn')" show-overflow-tooltip />
v-if="$i18n.locale === 'en'"
:label="$t('trials:readingUnit:qsList:title:qsNameEn')"
show-overflow-tooltip
/>
<!-- 题型 --> <!-- 题型 -->
<el-table-column <el-table-column prop="Type" :label="$t('trials:readingUnit:qsList:title:type')" show-overflow-tooltip>
prop="Type"
:label="$t('trials:readingUnit:qsList:title:type')"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('Criterion_Question_Type',scope.row.Type) }} {{ $fd('Criterion_Question_Type', scope.row.Type) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 是否显示 --> <!-- 是否显示 -->
<el-table-column <el-table-column prop="ShowQuestion" :label="$t('trials:readingUnit:qsList:title:isShow')" show-overflow-tooltip>
prop="ShowQuestion"
:label="$t('trials:readingUnit:qsList:title:isShow')"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('ShowQuestion',scope.row.ShowQuestion) }} {{ $fd('ShowQuestion', scope.row.ShowQuestion) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 是否必填 --> <!-- 是否必填 -->
<el-table-column <el-table-column prop="IsRequired" :label="$t('trials:readingUnit:qsList:title:isRequired')"
prop="IsRequired" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:isRequired')"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('QuestionRequired',scope.row.IsRequired) }} {{ $fd('QuestionRequired', scope.row.IsRequired) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 是否裁判问题 --> <!-- 是否裁判问题 -->
<el-table-column <el-table-column prop="IsJudgeQuestion" :label="$t('trials:readingUnit:qsList:title:isJudgeQuestion')" width="120"
prop="IsJudgeQuestion" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:isJudgeQuestion')"
width="120"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsJudgeQuestion) }} {{ $fd('YesOrNo', scope.row.IsJudgeQuestion) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 是否在阅片页面显示 --> <!-- 是否在阅片页面显示 -->
<el-table-column <el-table-column prop="IsShowInDicom" :label="$t('trials:readingUnit:qsList:title:isShowInDicom')" width="140"
prop="IsShowInDicom" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:isShowInDicom')"
width="140"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsShowInDicom) }} {{ $fd('YesOrNo', scope.row.IsShowInDicom) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 是否在全局阅片显示 --> <!-- 是否在全局阅片显示 -->
<el-table-column <el-table-column prop="GlobalReadingShowType" :label="$t('trials:readingUnit:qsList:title:globalReadingShowType')"
prop="GlobalReadingShowType" width="160" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:globalReadingShowType')"
width="160"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('GlobalReadingShowType', scope.row.GlobalReadingShowType) }} {{ $fd('GlobalReadingShowType', scope.row.GlobalReadingShowType) }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 导出目标表格 --> <!-- 导出目标表格 -->
<el-table-column <el-table-column prop="ExportResult" :label="$t('trials:readingUnit:qsList:title:ExportResult')" width="160"
prop="ExportResult" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:ExportResult')"
width="160"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ getStringResult(scope.row.ExportResult, 'ExportResult') }} {{ getStringResult(scope.row.ExportResult, 'ExportResult') }}
</template> </template>
</el-table-column> </el-table-column>
<!-- 限制编辑 --> <!-- 限制编辑 -->
<el-table-column <el-table-column prop="LimitEdit" :label="$t('trials:readingUnit:qsList:title:limitEdit')" width="160"
prop="LimitEdit" show-overflow-tooltip>
:label="$t('trials:readingUnit:qsList:title:limitEdit')"
width="160"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
{{ $fd('LimitEdit', scope.row.LimitEdit) }} {{ $fd('LimitEdit', scope.row.LimitEdit) }}
</template> </template>
@ -154,105 +87,50 @@
width="140" width="140"
show-overflow-tooltip show-overflow-tooltip
/> --> /> -->
<el-table-column <el-table-column prop="" :label="$t('common:action:action')" width="300" show-overflow-tooltip>
prop=""
:label="$t('common:action:action')"
width="300"
show-overflow-tooltip
>
<template slot-scope="scope"> <template slot-scope="scope">
<!-- 编辑 --> <!-- 编辑 -->
<el-button <el-button v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])" type="primary" size="mini" @click="handleEdit(scope.row)">
type="primary"
size="mini"
@click="handleEdit(scope.row)"
>
{{ $t('trials:readingUnit:qsList:title:edit') }} {{ $t('trials:readingUnit:qsList:title:edit') }}
</el-button> </el-button>
<!-- 查看 --> <!-- 查看 -->
<el-button <el-button v-else type="primary" size="mini" @click="handlelook(scope.row)">
v-else
type="primary"
size="mini"
@click="handlelook(scope.row)"
>
{{ $t('trials:readingUnit:qsList:title:view') }} {{ $t('trials:readingUnit:qsList:title:view') }}
</el-button> </el-button>
<!-- 表格问题 --> <!-- 表格问题 -->
<el-button <el-button v-if="scope.row.Type === 'table' || scope.row.Type === 'basicTable'" type="primary" size="mini"
v-if="scope.row.Type === 'table' || scope.row.Type === 'basicTable'" @click="handleConfig(scope.row)">
type="primary"
size="mini"
@click="handleConfig(scope.row)"
>
{{ $t('trials:readingUnit:qsList:title:tableQs') }} {{ $t('trials:readingUnit:qsList:title:tableQs') }}
</el-button> </el-button>
<!-- 删除 --> <!-- 删除 -->
<el-button <el-button v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])" type="danger" size="mini" @click="handleDelete(scope.row)">
type="danger"
size="mini"
@click="handleDelete(scope.row)"
>
{{ $t('trials:readingUnit:qsList:title:delete') }} {{ $t('trials:readingUnit:qsList:title:delete') }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog <el-dialog v-if="addOrEdit.visible" :visible.sync="addOrEdit.visible" :close-on-click-modal="false"
v-if="addOrEdit.visible" :title="addOrEdit.title" width="800px" append-to-body custom-class="base-dialog-wrapper">
:visible.sync="addOrEdit.visible" <questions-form ref="addOrEdit" :data="rowData" :trial-criterion-id="trialCriterionId"
:close-on-click-modal="false" :is-from-system="isFromSystem" :digit-places="digitPlaces" :readingTools="readingTools"
:title="addOrEdit.title" :readingSegmentTools="readingSegmentTools" :readingVersionEnum="readingVersionEnum" :list="tblList"
width="800px" :is-look="isLook" :is-system-criterion="isSystemCriterion" @close="addOrEdit.visible = false" @getList="getList"
append-to-body @reloadArbitrationRules="reloadArbitrationRules" />
custom-class="base-dialog-wrapper"
>
<questions-form
ref="addOrEdit"
:data="rowData"
:trial-criterion-id="trialCriterionId"
:is-from-system="isFromSystem"
:digit-places="digitPlaces"
:readingTools="readingTools"
:readingVersionEnum="readingVersionEnum"
:list="tblList"
:is-look="isLook"
:is-system-criterion="isSystemCriterion"
@close="addOrEdit.visible = false"
@getList="getList"
@reloadArbitrationRules="reloadArbitrationRules"
/>
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-if="preview.visible" :visible.sync="preview.visible" :close-on-click-modal="false"
v-if="preview.visible" :title="preview.title" :fullscreen="true">
:visible.sync="preview.visible"
:close-on-click-modal="false"
:title="preview.title"
:fullscreen="true"
>
<questions-preview :criterion-id="trialCriterionId" :is-system-criterion="isSystemCriterion" :form-type="1" /> <questions-preview :criterion-id="trialCriterionId" :is-system-criterion="isSystemCriterion" :form-type="1" />
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-if="config.visible" :visible.sync="config.visible" :close-on-click-modal="false" :title="config.title"
v-if="config.visible" :fullscreen="true">
:visible.sync="config.visible" <table-qs-list :digit-places="digitPlaces" :reading-question-id="rowData.Id" :is-from-system="isFromSystem"
:close-on-click-modal="false" :is-confirm="isConfirm" :criterion-id="trialCriterionId" :readingTools="readingTools"
:title="config.title" :readingSegmentTools="readingSegmentTools" :readingVersionEnum="readingVersionEnum"
:fullscreen="true" @close="config.visible = false" />
>
<table-qs-list
:digit-places="digitPlaces"
:reading-question-id="rowData.Id"
:is-from-system="isFromSystem"
:is-confirm="isConfirm"
:criterion-id="trialCriterionId"
:readingTools="readingTools"
:readingVersionEnum="readingVersionEnum"
@close="config.visible = false"
/>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -310,6 +188,12 @@ export default {
return [] return []
} }
}, },
readingSegmentTools: {
type: Array,
default() {
return []
}
},
readingVersionEnum: { readingVersionEnum: {
type: Number, type: Number,
default: 0 default: 0
@ -350,7 +234,8 @@ export default {
this.rowData = { this.rowData = {
ReadingQuestionCriterionTrialId: this.trialCriterionId, ReadingQuestionCriterionTrialId: this.trialCriterionId,
ReadingCriterionPageId: this.readingCriterionPageId, ReadingCriterionPageId: this.readingCriterionPageId,
Id: '' } Id: ''
}
this.isLook = false this.isLook = false
this.addOrEdit.title = this.$t('trials:readingUnit:qsList:title:add')// '' this.addOrEdit.title = this.$t('trials:readingUnit:qsList:title:add')// ''
this.addOrEdit.visible = true this.addOrEdit.visible = true
@ -362,9 +247,9 @@ export default {
this.rowData.ReadingCriterionPageId = this.readingCriterionPageId this.rowData.ReadingCriterionPageId = this.readingCriterionPageId
let title = '' let title = ''
if (this.$i18n.locale === 'zh' && (row.QuestionName || row.GroupName)) { if (this.$i18n.locale === 'zh' && (row.QuestionName || row.GroupName)) {
title =`${row.QuestionName ? row.QuestionName : row.GroupName}` title = `${row.QuestionName ? row.QuestionName : row.GroupName}`
} else if (this.$i18n.locale === 'en' && (row.QuestionEnName || row.GroupEnName)) { } else if (this.$i18n.locale === 'en' && (row.QuestionEnName || row.GroupEnName)) {
title =`: ${row.QuestionEnName ? row.QuestionEnName : row.GroupEnName}` title = `: ${row.QuestionEnName ? row.QuestionEnName : row.GroupEnName}`
} }
this.addOrEdit.title = `${this.$t('trials:readingUnit:qsList:title:view')} ${title} `// '' this.addOrEdit.title = `${this.$t('trials:readingUnit:qsList:title:view')} ${title} `// ''
this.addOrEdit.visible = true this.addOrEdit.visible = true
@ -400,8 +285,8 @@ export default {
// this.preview.visible = true // this.preview.visible = true
window.localStorage.setItem('TrialReadingCriterionId', this.TrialReadingCriterionId) window.localStorage.setItem('TrialReadingCriterionId', this.TrialReadingCriterionId)
var token = getToken() var token = getToken()
var path = `/criterionquestions?TrialReadingCriterionId=${this.trialCriterionId}&isSystemCriterion=${this.isSystemCriterion}&TokenKey=${token}` var path = `/criterionquestions?TrialReadingCriterionId=${this.trialCriterionId}&isSystemCriterion=${this.isSystemCriterion}&TokenKey=${token}`
var routeData = this.$router.resolve({ path }) var routeData = this.$router.resolve({ path })
this.openWindow = window.open(routeData.href, '_blank') this.openWindow = window.open(routeData.href, '_blank')
}, },
@ -431,5 +316,4 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -1,59 +1,37 @@
<template> <template>
<div> <div>
<el-form <el-form ref="readingCriterionsForm" v-loading="loading" :model="form" :rules="rules" label-width="120px"
ref="readingCriterionsForm" size="small">
v-loading="loading"
:model="form"
:rules="rules"
label-width="120px"
size="small"
>
<!-- '表单问题' --> <!-- '表单问题' -->
<el-form-item :label="$t('trials:readingUnit:readingCriterion:title:formQs')"> <el-form-item :label="$t('trials:readingUnit:readingCriterion:title:formQs')">
<questions-list <questions-list :ref="`questionList${trialReadingCriterionId}`" v-if="form.FormType === 1"
:ref="`questionList${trialReadingCriterionId}`" :trial-reading-criterion-id="trialReadingCriterionId" :list="readingInfo.TrialQuestionList"
v-if="form.FormType===1" :trial-criterion-id="readingInfo.TrialCriterionId" :is-confirm="isConfirm"
:trial-reading-criterion-id="trialReadingCriterionId" :is-system-criterion="isSystemCriterion" :is-from-system="readingInfo.IsFromSystem"
:list="readingInfo.TrialQuestionList" :digit-places="digitPlaces" :readingTools="readingTools" :readingSegmentTools="readingSegmentTools"
:trial-criterion-id="readingInfo.TrialCriterionId" :readingVersionEnum="readingVersionEnum" @reloadArbitrationRules="reloadArbitrationRules" />
:is-confirm="isConfirm"
:is-system-criterion="isSystemCriterion"
:is-from-system="readingInfo.IsFromSystem"
:digit-places="digitPlaces"
:readingTools="readingTools"
:readingVersionEnum="readingVersionEnum"
@reloadArbitrationRules="reloadArbitrationRules"
/>
</el-form-item> </el-form-item>
<el-form-item v-if=" hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"> <el-form-item v-if="hasPermi(['trials:trials-panel:setting:reading-unit:edit'])">
<!-- &lt;!&ndash; 保存 &ndash;&gt;--> <!-- &lt;!&ndash; 保存 &ndash;&gt;-->
<!-- <el-button--> <!-- <el-button-->
<!-- v-if="!isConfirm && isAdditionalAssessment"--> <!-- v-if="!isConfirm && isAdditionalAssessment"-->
<!-- type="primary"--> <!-- type="primary"-->
<!-- @click="handleSave(true)"--> <!-- @click="handleSave(true)"-->
<!-- >--> <!-- >-->
<!-- {{ $t('common:button:save') }}--> <!-- {{ $t('common:button:save') }}-->
<!-- </el-button>--> <!-- </el-button>-->
<!-- 基础数据配置 --> <!-- 基础数据配置 -->
<el-button <el-button type="primary" @click="configBaseDataVisible = true">
type="primary"
@click="configBaseDataVisible = true"
>
{{ $t('trials:readingUnit:readingCriterion:title:baseDataCfg') }} {{ $t('trials:readingUnit:readingCriterion:title:baseDataCfg') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 基础数据配置 --> <!-- 基础数据配置 -->
<el-dialog <el-dialog v-if="configBaseDataVisible" :title="$t('trials:readingUnit:readingCriterion:title:baseDataCfg')"
v-if="configBaseDataVisible" :visible.sync="configBaseDataVisible" :close-on-click-modal="false" :fullscreen="true" append-to-body
:title="$t('trials:readingUnit:readingCriterion:title:baseDataCfg')" custom-class="base-dialog-wrapper">
:visible.sync="configBaseDataVisible" <BaseDataConfig :trial-reading-criterion-id="trialReadingCriterionId" :is-from-system="readingInfo.IsFromSystem"
:close-on-click-modal="false" :is-confirm="isConfirm" />
:fullscreen="true"
append-to-body
custom-class="base-dialog-wrapper"
>
<BaseDataConfig :trial-reading-criterion-id="trialReadingCriterionId" :is-from-system="readingInfo.IsFromSystem" :is-confirm="isConfirm" />
</el-dialog> </el-dialog>
@ -85,6 +63,12 @@ export default {
default() { default() {
return [] return []
} }
},
readingSegmentTools: {
type: Array,
default() {
return []
}
} }
}, },
data() { data() {
@ -122,7 +106,7 @@ export default {
this.additionalAssessmentOptionList = res.Result this.additionalAssessmentOptionList = res.Result
if (this.additionalAssessmentOptionList.length > 0) { if (this.additionalAssessmentOptionList.length > 0) {
this.additionalAssessmentOptionList.forEach(v => { this.additionalAssessmentOptionList.forEach(v => {
this.$set(this.form, 'AdditionalAssessmentType'+v.Id, v.IsSelected) this.$set(this.form, 'AdditionalAssessmentType' + v.Id, v.IsSelected)
}) })
} }
getTrialReadingCriterionInfo({ trialId, TrialReadingCriterionId: this.trialReadingCriterionId }).then(res => { getTrialReadingCriterionInfo({ trialId, TrialReadingCriterionId: this.trialReadingCriterionId }).then(res => {
@ -185,7 +169,7 @@ export default {
reloadArbitrationRules() { reloadArbitrationRules() {
this.$emit('reloadArbitrationRules') this.$emit('reloadArbitrationRules')
}, },
handleConfig() {} handleConfig() { }
} }
} }

View File

@ -30,9 +30,8 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 测量工具 --> <!-- 测量工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
<el-form-item <el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
v-if="CriterionType === 0 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2) && form.ReadingVersionEnum === 1"
:label="$t('trials:readingUnit:readingRules:title:measureTool')"> :label="$t('trials:readingUnit:readingRules:title:measureTool')">
<el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm || <el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit']) !hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
@ -42,6 +41,17 @@
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<!--分割工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
<el-checkbox-group v-model="form.SegmentToolList" :disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
">
<el-checkbox v-for="tool in segmentTools" :key="tool.toolName" :label="tool.toolName" name="SegmentToolList">
{{ $t(`${tool.i18nKey}`) }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!--检查类型筛选--> <!--检查类型筛选-->
<el-form-item :label="$t('trials:processCfg:form:IsImageFilter')" prop="IsImageFilter"> <el-form-item :label="$t('trials:processCfg:form:IsImageFilter')" prop="IsImageFilter">
<el-radio-group v-model="form.IsImageFilter" @input="IsImageFilterChange" :disabled="isConfirm || <el-radio-group v-model="form.IsImageFilter" @input="IsImageFilterChange" :disabled="isConfirm ||
@ -421,6 +431,7 @@ export default {
ReadingTool: 0, ReadingTool: 0,
ReadingVersionEnum: null, ReadingVersionEnum: null,
ReadingToolList: [], ReadingToolList: [],
SegmentToolList: [],
ReadingTaskViewEnum: null, ReadingTaskViewEnum: null,
IsImageLabeled: null, IsImageLabeled: null,
IsReadingShowSubjectInfo: null, IsReadingShowSubjectInfo: null,
@ -616,7 +627,8 @@ export default {
modalityList: [], modalityList: [],
CriterionModalitys: [], CriterionModalitys: [],
modalityIsCheck: false, // modalityIsCheck: false, //
tools: [] tools: [],
segmentTools: []
} }
}, },
mounted() { mounted() {
@ -737,13 +749,20 @@ export default {
// //
handleReadingToolInput(val) { handleReadingToolInput(val) {
this.form.ReadingToolList = [] this.form.ReadingToolList = []
if (this.CriterionType !== 0) return this.tools = [] this.form.SegmentToolList = []
if (val === 0 || val === 2) { if (this.CriterionType !== 0) {
this.segmentTools = []
return this.tools = []
}
if (val === 0 || val === 2 || val === 3) {
this.tools = [...config.customizeStandards] this.tools = [...config.customizeStandards]
} }
if (val === 1) { if (val === 1) {
this.tools = [...config.customizeStandardsNoneDicom] this.tools = [...config.customizeStandardsNoneDicom]
} }
if (val === 3) {
this.segmentTools = [...config.customizeStandardsSegmentDicom]
}
}, },
// //
IsImageFilterChange(data) { IsImageFilterChange(data) {
@ -786,12 +805,15 @@ export default {
this.form.KeyFileListStr = this.form.KeyFileList.map(item => item.FileName).join(',') this.form.KeyFileListStr = this.form.KeyFileList.map(item => item.FileName).join(',')
} }
if (this.CriterionType === 0) { if (this.CriterionType === 0) {
if (this.form.ReadingTool === 0 || this.form.ReadingTool === 2) { if (this.form.ReadingTool === 0 || this.form.ReadingTool === 2 || this.form.ReadingTool === 3) {
this.tools = [...config.customizeStandards] this.tools = [...config.customizeStandards]
} }
if (this.form.ReadingTool === 1) { if (this.form.ReadingTool === 1) {
this.tools = [...config.customizeStandardsNoneDicom] this.tools = [...config.customizeStandardsNoneDicom]
} }
if (this.form.ReadingTool === 3) {
this.segmentTools = [...config.customizeStandardsSegmentDicom]
}
} }
this.CriterionModalitys = this.form.CriterionModalitys this.CriterionModalitys = this.form.CriterionModalitys
? this.form.CriterionModalitys.split('|') ? this.form.CriterionModalitys.split('|')
@ -817,7 +839,7 @@ export default {
this.$emit('setGlobalReading', res.Result.IsGlobalReading) this.$emit('setGlobalReading', res.Result.IsGlobalReading)
this.$emit('setOncologyReading', res.Result.IsOncologyReading) this.$emit('setOncologyReading', res.Result.IsOncologyReading)
this.$emit('setDigitPlaces', res.Result.DigitPlaces) this.$emit('setDigitPlaces', res.Result.DigitPlaces)
this.$emit('setReadingTools', { readingTools: res.Result.ReadingToolList, isNoneDicom: this.form.ReadingTool === 1 }) this.$emit('setReadingTools', { readingTools: res.Result.ReadingToolList, readingSegmentTools: res.Result.SegmentToolList, ReadingTool: this.form.ReadingTool })
if (res.Result.ReadingType === 1) { if (res.Result.ReadingType === 1) {
this.$emit('setArbitrationReading', false) this.$emit('setArbitrationReading', false)
@ -859,7 +881,7 @@ export default {
this.$emit('setGlobalReading', this.form.IsGlobalReading) this.$emit('setGlobalReading', this.form.IsGlobalReading)
this.$emit('setOncologyReading', this.form.IsOncologyReading) this.$emit('setOncologyReading', this.form.IsOncologyReading)
this.$emit('setDigitPlaces', this.form.DigitPlaces) this.$emit('setDigitPlaces', this.form.DigitPlaces)
this.$emit('setReadingTools', { readingTools: this.form.ReadingToolList, isNoneDicom: this.form.ReadingTool === 1 }) this.$emit('setReadingTools', { readingTools: this.form.ReadingToolList, readingSegmentTools: this.form.SegmentToolList, ReadingTool: this.form.ReadingTool })
if (this.form.ReadingType === 1) { if (this.form.ReadingType === 1) {
this.$emit('setArbitrationReading', false) this.$emit('setArbitrationReading', false)
} }

View File

@ -286,8 +286,15 @@
:disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio> :disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 测量工具 ImageTool --> <!--标记类型-->
<el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2" <el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2"
:label="$t('trials:readingUnit:qsList:title:ImageMarkTypeEnum')" prop="ImageMarkTypeEnum">
<el-radio-group v-model="form.ImageMarkTypeEnum" @change="ImageMarkTypeEnumChange">
<el-radio v-for="item of $d.ImageMarkType" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<!-- 测量工具 ImageTool -->
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 0"
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[ :label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
{ required: true, message: this.$t('common:ruleMessage:select') } { required: true, message: this.$t('common:ruleMessage:select') }
]"> ]">
@ -297,6 +304,17 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 分割工具 ImageTool -->
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 1"
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
{ required: true, message: this.$t('common:ruleMessage:select') }
]">
<el-radio-group v-model="form.ImageTool" @change="imageSegmentToolChange">
<el-radio v-for="tool of readingSegmentTools" :key="tool.toolName" :label="tool.toolName">
{{ $t(tool.i18nKey) }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 测量值 ImageToolAttribute --> <!-- 测量值 ImageToolAttribute -->
<el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0" <el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0"
:label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[ :label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[
@ -563,6 +581,12 @@ export default {
return [] return []
} }
}, },
readingSegmentTools: {
type: Array,
default() {
return []
}
},
readingVersionEnum: { readingVersionEnum: {
type: Number, type: Number,
default: 0 default: 0
@ -617,6 +641,7 @@ export default {
ClassifyEditType: null, ClassifyEditType: null,
ClassifyShowType: null, ClassifyShowType: null,
ImageMarkEnum: 0, ImageMarkEnum: 0,
ImageMarkTypeEnum: 0,
ShowChartTypeEnum: 0, ShowChartTypeEnum: 0,
ImageTool: '', ImageTool: '',
ImageToolAttribute: '', ImageToolAttribute: '',
@ -898,9 +923,12 @@ export default {
} }
} }
} }
if (this.form.ImageTool) { if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 0) {
this.imageToolChange(this.form.ImageTool) this.imageToolChange(this.form.ImageTool)
} }
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 1) {
this.imageSegmentToolChange(this.form.ImageTool)
}
} }
if (this.form.ClassifyTableQuestionId) { if (this.form.ClassifyTableQuestionId) {
this.classifyQuestionChange(this.form.ClassifyTableQuestionId) this.classifyQuestionChange(this.form.ClassifyTableQuestionId)
@ -1011,14 +1039,26 @@ export default {
this.form.ImageTool = '' this.form.ImageTool = ''
this.form.ImageToolAttribute = '' this.form.ImageToolAttribute = ''
this.imageToolAttributes = [] this.imageToolAttributes = []
this.form.ImageMarkTypeEnum = 0
} }
}, },
ImageMarkTypeEnumChange(val) {
this.form.ImageTool = ''
this.form.ImageToolAttribute = ''
this.imageToolAttributes = []
},
imageToolChange(v) { imageToolChange(v) {
let i = this.readingTools.findIndex(tool => tool.toolName === v) let i = this.readingTools.findIndex(tool => tool.toolName === v)
if (i > -1) { if (i > -1) {
this.imageToolAttributes = this.readingTools[i].props this.imageToolAttributes = this.readingTools[i].props
} }
}, },
imageSegmentToolChange(v) {
let i = this.readingSegmentTools.findIndex(tool => tool.toolName === v)
if (i > -1) {
this.imageToolAttributes = this.readingSegmentTools[i].props
}
},
async parentQuestionChange(val, form) { async parentQuestionChange(val, form) {
if (val) { if (val) {
var index = this.parentOptions.findIndex(item => { var index = this.parentOptions.findIndex(item => {
@ -1129,6 +1169,7 @@ export default {
form.ClassifyType = null form.ClassifyType = null
form.ClassifyShowType = null form.ClassifyShowType = null
form.ImageMarkEnum = 0 form.ImageMarkEnum = 0
form.ImageMarkTypeEnum = 0
form.ShowChartTypeEnum = 0 form.ShowChartTypeEnum = 0
form.ImageTool = '' form.ImageTool = ''
form.ImageToolAttribute = '' form.ImageToolAttribute = ''

Some files were not shown because too many files have changed in this diff Show More