136 Commits

Author SHA1 Message Date
caiyiling a66e15eb39 MRI-PDFF测量逻辑更改
continuous-integration/drone/push Build encountered an error
2025-06-04 16:25:48 +08:00
caiyiling 895e3a6bed MRI-PDFF更改
continuous-integration/drone/push Build encountered an error
2025-06-04 13:35:42 +08:00
caiyiling 9dddc0c012 MRI-PDFF测量逻辑更改
continuous-integration/drone/push Build encountered an error
2025-06-04 10:53:45 +08:00
caiyiling a965f08a1e MRI-PDFF标准更改
continuous-integration/drone/push Build encountered an error
2025-06-04 09:24:41 +08:00
caiyiling 9962b1a829 反色bug修复
continuous-integration/drone/push Build encountered an error
2025-05-28 18:46:53 +08:00
caiyiling 049128b90e 添加阅片完成支持添加临时标注(历史标注不允许更改)功能及阅片页面双击放大缺陷修复
continuous-integration/drone/push Build encountered an error
2025-05-28 15:44:38 +08:00
wangxiaoshuang 45e6a20988 Merge branch 'uat'
continuous-integration/drone/push Build encountered an error
# Conflicts:
#	src/utils/metaDataProvider.js
2025-05-26 12:30:44 +08:00
wangxiaoshuang 5141eb5cbd 发布配置修改
continuous-integration/drone/push Build is passing
2025-05-23 18:12:49 +08:00
wangxiaoshuang 073b06a76f 1
continuous-integration/drone/push Build is passing
2025-05-23 17:10:45 +08:00
wangxiaoshuang 20723e8d2c 1
continuous-integration/drone/push Build is passing
2025-05-23 16:25:30 +08:00
wangxiaoshuang fba00157a1 crc一致性核查回复弹窗添加取消按钮
continuous-integration/drone/push Build is passing
2025-05-23 16:06:12 +08:00
wangxiaoshuang f0e6708533 1
continuous-integration/drone/push Build is passing
2025-05-23 15:24:49 +08:00
wangxiaoshuang e770dc1078 1 2025-05-23 15:24:07 +08:00
wangxiaoshuang e15934d7ba iqc质控页面数据已退回禁用所有按钮
continuous-integration/drone/push Build is passing
2025-05-23 13:49:36 +08:00
wangxiaoshuang 4baf039d71 iqc下载非dicom文件夹名称问题
continuous-integration/drone/push Build is passing
2025-05-23 13:38:59 +08:00
wangxiaoshuang 5c4bc96a26 pm重传审批样式修改
continuous-integration/drone/push Build is passing
2025-05-23 13:27:51 +08:00
wangxiaoshuang 30f9e807b7 crc一致性核查逻辑变更
continuous-integration/drone/push Build is passing
2025-05-23 11:07:17 +08:00
wangxiaoshuang 269f9f9477 pm重传审批修改
continuous-integration/drone/push Build is passing
2025-05-23 10:11:52 +08:00
wangxiaoshuang 810c786512 1
continuous-integration/drone/push Build is passing
2025-05-22 18:04:29 +08:00
wangxiaoshuang e27705eb22 IRC国际化替换
continuous-integration/drone/push Build is passing
2025-05-22 17:49:43 +08:00
wangxiaoshuang 83bf082c43 crc一致性核查回复信息有误
continuous-integration/drone/push Build is passing
2025-05-22 16:52:50 +08:00
wangxiaoshuang 37f12b30cc 阅片跟踪影像退回添加退回原因
continuous-integration/drone/push Build is passing
2025-05-22 15:30:14 +08:00
wangxiaoshuang f045f68115 一致性核查回复修改
continuous-integration/drone/push Build is passing
2025-05-22 14:06:38 +08:00
wangxiaoshuang 64e67ad106 一致性核查回复修改
continuous-integration/drone/push Build is passing
2025-05-22 13:54:27 +08:00
wangxiaoshuang f2ee666083 重阅审批的弹框,国际化取值不正确修改
continuous-integration/drone/push Build is passing
2025-05-22 11:04:30 +08:00
caiyiling 8ece8915ff 高亮颜色更改
continuous-integration/drone/push Build is passing
2025-05-21 16:01:04 +08:00
caiyiling 8270841ca1 典型肝内病灶鼠标悬浮显示
continuous-integration/drone/push Build is passing
2025-05-21 15:21:33 +08:00
caiyiling e99c4527b9 阅片页面radio样式更改及病灶信息高亮更改
continuous-integration/drone/push Build is passing
2025-05-21 15:10:49 +08:00
caiyiling e295bff859 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-21 14:06:54 +08:00
caiyiling 7a53a75038 影像质量不正常时维护病灶默认状态 2025-05-21 14:06:06 +08:00
wangxiaoshuang d96822818c 1
continuous-integration/drone/push Build is passing
2025-05-21 14:02:23 +08:00
caiyiling 9e2255e324 阅片标准更改
continuous-integration/drone/push Build is passing
2025-05-21 10:37:12 +08:00
caiyiling dbdd18dcf6 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-21 09:49:22 +08:00
caiyiling 4ae2504278 阅片标准更改 2025-05-21 09:48:54 +08:00
wangxiaoshuang 6a0a5f4724 1
continuous-integration/drone/push Build is passing
2025-05-20 16:14:40 +08:00
wangxiaoshuang ee2e33405c 申请原因必填添加星号
continuous-integration/drone/push Build is passing
2025-05-20 11:07:41 +08:00
wangxiaoshuang 286865ba8f 影像质疑列表操作栏样式调整
continuous-integration/drone/push Build is passing
2025-05-20 09:43:02 +08:00
wangxiaoshuang 75a0f3a91a 影像退回添加申请原因、备注
continuous-integration/drone/push Build is passing
2025-05-19 17:53:54 +08:00
wangxiaoshuang 4fbcd8f8f5 项目培训新增默认字段IsPublish修改
continuous-integration/drone/push Build is passing
2025-05-19 16:05:36 +08:00
wangxiaoshuang 3c2edbffc5 1
continuous-integration/drone/push Build is passing
2025-05-19 15:39:04 +08:00
caiyiling 38fb56e09e Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-19 15:19:18 +08:00
caiyiling 105537c49e 非淋巴结靶病灶保存验证更改 2025-05-19 15:18:48 +08:00
wangxiaoshuang 355f6bcf86 培训文档管理预览附件
continuous-integration/drone/push Build is passing
2025-05-19 14:48:47 +08:00
wangxiaoshuang d6e9f9ddc2 培训文档管理预览附件
continuous-integration/drone/push Build is passing
2025-05-19 14:35:17 +08:00
wangxiaoshuang 16e005e664 test环境新增onlyoffice地址配置
continuous-integration/drone/push Build is passing
2025-05-19 14:27:00 +08:00
wangxiaoshuang b92b55a45a 培训课程管理问题
continuous-integration/drone/push Build is passing
2025-05-19 14:18:59 +08:00
wangxiaoshuang 0ed070ecd7 【临床数据】新增临床数据时,选择模板同时存在中文模板和英文模板,根据语言环境只显示一个 2025-05-19 14:18:45 +08:00
caiyiling 49940ab034 lugano更改
continuous-integration/drone/push Build is passing
2025-05-19 14:15:41 +08:00
caiyiling aca5b0c721 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-19 11:28:14 +08:00
caiyiling 8ea7540581 阅片报告页更改 2025-05-19 11:27:50 +08:00
wangxiaoshuang ec6a72e143 影像质控问题配置未勾选问题提交会报错
continuous-integration/drone/push Build is passing
2025-05-15 16:44:16 +08:00
wangxiaoshuang f512c1456a 阅片结果查看跳转修改
continuous-integration/drone/push Build is passing
2025-05-15 15:39:23 +08:00
wangxiaoshuang 4aa74b5420 阅片结果查看跳转修改
continuous-integration/drone/push Build is passing
2025-05-15 15:32:24 +08:00
wangxiaoshuang 50c37824aa ir已阅查看跳转修改
continuous-integration/drone/push Build is passing
2025-05-15 15:21:47 +08:00
wangxiaoshuang 358864c6c6 onlyoffice引入方式修改
continuous-integration/drone/push Build is passing
2025-05-15 11:08:33 +08:00
wangxiaoshuang e2db9c2d51 通用培训文档修改 2025-05-15 11:08:06 +08:00
wangxiaoshuang 78fbae1c1f 通用培训记录的相关文件添加/编辑弹框需要增加标题
continuous-integration/drone/push Build is passing
2025-05-15 09:46:25 +08:00
wangxiaoshuang 26c58f14fa 稽查轨迹关联操作页面的标题需要区分
continuous-integration/drone/push Build is passing
2025-05-14 16:14:05 +08:00
caiyiling f8774224b3 视角信息更改
continuous-integration/drone/push Build is passing
2025-05-14 16:05:43 +08:00
caiyiling bc41f4b4c1 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-14 15:48:29 +08:00
caiyiling 0280dfb959 lugano更改 2025-05-14 15:48:08 +08:00
wangxiaoshuang 49aa98af9f suv融合pt图像调整
continuous-integration/drone/push Build is passing
2025-05-14 15:06:52 +08:00
wangxiaoshuang 9ce6ff25b9 1
continuous-integration/drone/push Build is passing
2025-05-14 14:23:04 +08:00
wangxiaoshuang fca8c1c9bf suv问题修复
continuous-integration/drone/push Build is passing
2025-05-14 14:12:31 +08:00
wangxiaoshuang d0ebc3e791 融合影像数存在较大差距校验
continuous-integration/drone/push Build is passing
2025-05-14 13:39:45 +08:00
caiyiling ea4f23fb16 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-14 13:18:17 +08:00
caiyiling 01d01b1126 视口信息更改 2025-05-14 13:17:56 +08:00
wangxiaoshuang 040d9309b9 影像质控问题配置未勾选问题提交会报错
continuous-integration/drone/push Build is passing
2025-05-14 13:05:00 +08:00
caiyiling 1d6656a5b8 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-05-14 11:25:22 +08:00
caiyiling 76e328dcca 阅片页面视角信息更改 2025-05-14 11:24:49 +08:00
wangxiaoshuang 9852d99a4a crc上传文件大小统计修改
continuous-integration/drone/push Build is passing
2025-05-13 15:40:30 +08:00
wangxiaoshuang 39f7ba7181 标记缓存修改
continuous-integration/drone/push Build is passing
2025-05-13 15:24:32 +08:00
wangxiaoshuang fb68892cd4 基线二维上的病灶被渲染到了随访的融合上
continuous-integration/drone/push Build is passing
2025-05-12 17:22:05 +08:00
caiyiling 1e6ec0df27 1
continuous-integration/drone/push Build is passing
2025-05-12 17:02:08 +08:00
caiyiling 4ff6416edb 矩形工具更改
continuous-integration/drone/push Build is passing
2025-05-12 16:45:48 +08:00
wangxiaoshuang b2645f6423 从随访切到基线再切到随访,测量工具会被禁用,当前访视无法标记
continuous-integration/drone/push Build is passing
2025-05-12 15:38:21 +08:00
wangxiaoshuang 122c76d9b2 suv部分问题解决
continuous-integration/drone/push Build is passing
2025-05-12 14:58:07 +08:00
wangxiaoshuang 2d155612e5 1
continuous-integration/drone/push Build is passing
2025-05-12 13:31:41 +08:00
wangxiaoshuang 329d6b1ff6 【NM自定义】三维MIP视口影像显示异常
continuous-integration/drone/push Build is passing
2025-05-12 13:22:36 +08:00
wangxiaoshuang 7006cf849e suv融合部分问题修复
continuous-integration/drone/push Build is passing
2025-05-12 13:13:36 +08:00
wangxiaoshuang 71f3519769 suv融合部分问题修复
continuous-integration/drone/push Build is passing
2025-05-09 17:54:14 +08:00
wangxiaoshuang bbf2dd16e6 【NM自定义】pet视口放大缩小,标记显示的位置产生了偏移
continuous-integration/drone/push Build is passing
2025-05-09 15:58:27 +08:00
wangxiaoshuang 520d499a0b 1
continuous-integration/drone/push Build is passing
2025-05-09 14:26:23 +08:00
wangxiaoshuang 00cfb92087 suv融合
continuous-integration/drone/push Build is passing
2025-05-09 14:20:24 +08:00
wangxiaoshuang 8eb0b96c8e 重传审批操作添加权限判断
continuous-integration/drone/push Build is passing
2025-05-09 10:05:22 +08:00
wangxiaoshuang 24b9120a0a 部分问题解决
continuous-integration/drone/push Build is passing
2025-05-08 14:47:26 +08:00
wangxiaoshuang 02fcc4a2c1 suv融合
continuous-integration/drone/push Build is passing
2025-05-08 10:36:57 +08:00
wangxiaoshuang f53412acbf 复制、粘贴快捷方式不禁用默认事件
continuous-integration/drone/push Build is passing
2025-05-07 13:39:30 +08:00
caiyiling 3948596014 阅片页面更改
continuous-integration/drone/push Build is passing
2025-05-06 09:13:59 +08:00
wangxiaoshuang e899c0814e 部分问题修复
continuous-integration/drone/push Build is passing
2025-04-30 14:51:13 +08:00
wangxiaoshuang d12b4adb68 部分问题修复
continuous-integration/drone/push Build is passing
2025-04-30 13:43:23 +08:00
wangxiaoshuang 3551a521c8 suv融合
continuous-integration/drone/push Build is passing
2025-04-29 17:51:54 +08:00
caiyiling e80d439377 阅片更改
continuous-integration/drone/push Build is passing
2025-04-28 18:32:16 +08:00
caiyiling f499557c01 1
continuous-integration/drone/push Build is passing
2025-04-28 17:37:34 +08:00
caiyiling ecaca550ab 自定义阅片工具更改
continuous-integration/drone/push Build is passing
2025-04-28 17:32:40 +08:00
caiyiling b7d7b93c4a 获取病灶截图更改
continuous-integration/drone/push Build is passing
2025-04-28 17:07:15 +08:00
caiyiling 7bf92c60ea 阅片完成后禁用“更多”操作
continuous-integration/drone/push Build is passing
2025-04-28 16:38:49 +08:00
caiyiling 36a08d1055 自定义阅片报告页更改
continuous-integration/drone/push Build is passing
2025-04-28 16:31:45 +08:00
caiyiling 5ebf9032a5 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-28 16:12:02 +08:00
caiyiling 1896c58b50 自定义阅片bug修复 2025-04-28 16:11:08 +08:00
wangxiaoshuang 9168ef3ad7 1
continuous-integration/drone/push Build is passing
2025-04-28 15:55:27 +08:00
wangxiaoshuang 4ad299e243 suv融合
continuous-integration/drone/push Build is passing
2025-04-28 15:47:07 +08:00
caiyiling 5d337b0b26 自定义阅片bug修复
continuous-integration/drone/push Build is passing
2025-04-28 14:22:35 +08:00
caiyiling ca579f8731 自定义阅片更改
continuous-integration/drone/push Build is passing
2025-04-28 10:19:37 +08:00
caiyiling 92d41dff28 1
continuous-integration/drone/push Build is passing
2025-04-27 18:00:53 +08:00
wangxiaoshuang 8d6ae3a49f suv融合
continuous-integration/drone/push Build is passing
2025-04-27 17:04:54 +08:00
caiyiling 18f23d2bba 阅片交互更改
continuous-integration/drone/push Build is passing
2025-04-25 15:27:00 +08:00
caiyiling 0a428bf954 多帧图像保存病灶更改及关键帧渲染更改
continuous-integration/drone/push Build is passing
2025-04-25 15:02:13 +08:00
caiyiling a6b5d0b196 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-25 10:53:09 +08:00
caiyiling 8bf6d5f515 自定义标准工具添加 2025-04-25 10:52:37 +08:00
wangxiaoshuang 74f3852ecc Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-25 09:39:03 +08:00
wangxiaoshuang 9408ce12e2 稽查文档删除添加二次确定 2025-04-25 09:38:08 +08:00
wangxiaoshuang 06a2361ea0 【通用培训管理】新员工培训时限默认值设为14天 2025-04-25 09:37:45 +08:00
caiyiling e3313edefd mRecist标准bug修复
continuous-integration/drone/push Build is passing
2025-04-24 16:47:08 +08:00
caiyiling 2676ff4295 阅片单元配置更改
continuous-integration/drone/push Build is passing
2025-04-24 10:34:24 +08:00
caiyiling 41b3a98594 recist1.1标准bug修复
continuous-integration/drone/push Build is passing
2025-04-23 16:59:54 +08:00
caiyiling c2aba6c3fe 自定义阅片更改
continuous-integration/drone/push Build is passing
2025-04-23 15:14:35 +08:00
caiyiling 81a5d5af17 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-23 14:10:26 +08:00
caiyiling 23ac15f978 布局更改 2025-04-23 14:10:08 +08:00
wangxiaoshuang 1ec4c25472 稽查文档通用培训记录导出
continuous-integration/drone/push Build is running
2025-04-23 14:10:06 +08:00
wangxiaoshuang ac8710b26b 稽查文档右键下载问题
continuous-integration/drone/push Build is passing
2025-04-23 11:44:05 +08:00
caiyiling 1130b55677 视口滚动图像翻页更改
continuous-integration/drone/push Build is passing
2025-04-23 09:21:32 +08:00
caiyiling fb57dfe90c 新版本阅片添加iRecist标准
continuous-integration/drone/push Build is failing
2025-04-22 17:44:50 +08:00
caiyiling 2784958ccb 多帧对齐更改
continuous-integration/drone/push Build is passing
2025-04-22 15:00:35 +08:00
caiyiling d6a3bcba30 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-22 13:43:19 +08:00
caiyiling 4ff9a7c482 多帧渲染更改 2025-04-22 13:42:58 +08:00
wangxiaoshuang 33b3818a9e 测试
continuous-integration/drone/push Build is passing
2025-04-22 10:44:48 +08:00
wangxiaoshuang 38c31af137 已发布、已下线的培训文档默认不可多选
continuous-integration/drone/push Build is passing
2025-04-21 11:28:41 +08:00
wangxiaoshuang fb671defb5 1
continuous-integration/drone/push Build is passing
2025-04-21 11:09:55 +08:00
wangxiaoshuang a25aa2482f 通用培训材料发布、下线功能修改
continuous-integration/drone/push Build is passing
2025-04-21 10:57:32 +08:00
wangxiaoshuang 4249ccd9d6 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-21 09:47:31 +08:00
wangxiaoshuang 9a88930a04 通用培训创建默认设置为失效 2025-04-21 09:47:20 +08:00
wangxiaoshuang e0321bae72 影像预览问题解决
continuous-integration/drone/push Build encountered an error
2025-04-12 12:04:20 +08:00
wangxiaoshuang 1e720e399d 确认收入项,修改选项后,其他文件也改了
continuous-integration/drone/push Build encountered an error
2025-04-03 11:15:54 +08:00
wangxiaoshuang 1368397da9 项目总览—>上传记录:中心编号 查询条件,查询结果不对 2025-04-03 11:15:36 +08:00
wangxiaoshuang c8c049b5d0 DICOM影像中,存在Tag最大像素值、最小像素值等,但是和实际的数据统计结果不同 2025-04-03 11:15:22 +08:00
86 changed files with 6937 additions and 2039 deletions
+2 -1
View File
@@ -5,7 +5,8 @@ NODE_ENV = 'development'
VUE_APP_BASE_PATH = '/'
VUE_APP_IS_TEST = false
# onlyoffice地址
VUE_APP_ONLYOFFICE_URL = "https://onlyoffice.test.extimaging.com"
# base api
VUE_APP_BASE_API = 'http://123.56.94.154:7000'
+3
View File
@@ -5,6 +5,9 @@ NODE_ENV = 'prod'
# base public path
VUE_APP_BASE_PATH = '/'
# onlyoffice地址
VUE_APP_ONLYOFFICE_URL = "https://office.extimaging.com"
# 是否开启登陆限制 true:是 false:否
VUE_APP_LOGIN_FOR_PERMISSION = true
+2
View File
@@ -3,6 +3,8 @@ ENV = 'production'
NODE_ENV = 'production'
# base public path
VUE_APP_BASE_PATH = '/'
# onlyoffice地址
VUE_APP_ONLYOFFICE_URL = "https://onlyoffice.test.extimaging.com"
VUE_APP_IS_TEST = true
+3
View File
@@ -4,6 +4,9 @@ NODE_ENV = 'production'
# base public path
VUE_APP_BASE_PATH = '/'
# onlyoffice地址
VUE_APP_ONLYOFFICE_URL = "https://onlyoffice.test.extimaging.com"
# base public path
VUE_APP_BASE_PATH = '/'
+2 -2
View File
@@ -252,7 +252,7 @@ export default {
}
},
handleSave() {
this.$confirm('确定修改当前页面国际化内容?')
this.$confirm(this.$t("i18n:confirm:updatei18n"))
.then(() => {
batchAddOrUpdateFrontInternationalization(this.tableData).then(
async (res) => {
@@ -270,7 +270,7 @@ export default {
i18n.mergeLocaleMessage('zh', zhMessages)
i18n.mergeLocaleMessage('en', enMessages)
this.drawer = false
this.$message.success('国际化修改成功')
this.$message.success(this.$t("i18n:message:updatei18nSuccessfully"))
if (this.il8nExternal) {
this.$EventBus.$emit('il8nUpdate')
}
+9
View File
@@ -178,6 +178,15 @@ export function pMTrainingRecordList_Export(data) {
data
})
}
// 导出系统培训记录
export function getSysDocumentConfirmList_Export(data) {
return requestDownload({
url: `/ExcelExport/getSysDocumentConfirmList_Export`,
responseType: 'blob',
method: 'post',
data
})
}
// 导出影像指控
export function qCVisitList_Export(data) {
return requestDownload({
+2 -2
View File
@@ -346,9 +346,9 @@ export function getReReadingOrBackInfluenceTaskList(taskId, isReReading, applyId
})
}
export function PMSetTaskBack(trialId, taskId) {
export function PMSetTaskBack(trialId, taskId, pmBackReason) {
return request({
url: `/VisitTask/PMSetTaskBack/${trialId}/${taskId}`,
url: `/VisitTask/PMSetTaskBack/${trialId}/${taskId}/${pmBackReason}`,
method: 'put'
})
}
+3 -2
View File
@@ -12,7 +12,7 @@
<!-- :src="`/static/pdfjs/web/viewer.html?file=${filePath}`">-->
<!-- </pdf>-->
<iframe v-else
:src="`/static/onlyOffice/viewer.html?url=${OSSclientConfig.basePath}${filePath}?type=${fileType}&title=${title}&documentType=${documentType}&userName=${currentUser}`"
:src="`/static/onlyOffice/viewer.html?url=${OSSclientConfig.basePath}${filePath}?onlyOffice_url=${onlyOffice_url}&type=${fileType}&title=${title}&documentType=${documentType}&userName=${currentUser}`"
width="100%" height="100%" frameborder="0" crossorigin="anonymous" />
<!-- <div v-else>
{{ $t('common:message:downloadFile') }}
@@ -45,7 +45,8 @@ export default {
data() {
return {
currentUser: zzSessionStorage.getItem('userName'),
COMPANY: process.env.VUE_APP_COMPANY_NAME
COMPANY: process.env.VUE_APP_COMPANY_NAME,
onlyOffice_url: process.env.VUE_APP_ONLYOFFICE_URL
}
},
computed: {
+2 -1
View File
@@ -3,7 +3,7 @@
custom-class="base-dialog-wrapper" @close="handleClose">
<div class="base-modal-body" style="border: 2px solid #ccc; padding: 10px">
<iframe v-if="visible"
:src="`/static/onlyOffice/viewer.html?url=${OSSclientConfig.basePath}${path}?type=${type}&title=${title}&documentType=${documentType}&userName=${currentUser}`"
:src="`/static/onlyOffice/viewer.html?url=${OSSclientConfig.basePath}${path}?onlyOffice_url=${onlyOffice_url}&type=${type}&title=${title}&documentType=${documentType}&userName=${currentUser}`"
width="100%" height="99%" frameborder="0" crossorigin="anonymous" />
</div>
</el-dialog>
@@ -20,6 +20,7 @@ export default {
title: null,
documentType: null,
currentUser: zzSessionStorage.getItem('userName'),
onlyOffice_url: process.env.VUE_APP_ONLYOFFICE_URL
};
},
methods: {
+5
View File
@@ -310,6 +310,11 @@ async function VueInit() {
text = text.replace(test, item.TrialName)
})
}
let CompanyInfo = JSON.parse(localStorage.getItem('CompanyInfo'))
if(CompanyInfo&&CompanyInfo.SystemShortName){
let test = new RegExp('IRC', 'ig')
text = text.replace(test, CompanyInfo.SystemShortName)
}
// return i18n.t(key)
return text;
}
+6
View File
@@ -68,6 +68,12 @@ body .el-table th.gutter {
}
}
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.el-loading-mask {
background-color: rgba(255, 255, 255, 0.5);
}
+137 -10
View File
@@ -39,6 +39,108 @@ function getNumberValues(dataSet, tag, minimumLength) {
return values;
}
function getLutDescriptor(dataSet, tag) {
if (!dataSet.elements[tag] || dataSet.elements[tag].length !== 6) {
return;
}
return [
dataSet.uint16(tag, 0),
dataSet.uint16(tag, 1),
dataSet.uint16(tag, 2),
];
}
function getLutData(lutDataSet, tag, lutDescriptor) {
const lut = [];
const lutData = lutDataSet.elements[tag];
for (let i = 0; i < lutDescriptor[0]; i++) {
// Output range is always unsigned
if (lutDescriptor[2] === 16) {
lut[i] = lutDataSet.uint16(tag, i);
} else {
lut[i] = lutDataSet.byteArray[i + lutData.dataOffset];
}
}
return lut;
}
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
const pixelRepresentation = dataSet.uint16('x00280103');
if (pixelRepresentation === 0) {
imagePixelModule.smallestPixelValue = dataSet.uint16('x00280106');
imagePixelModule.largestPixelValue = dataSet.uint16('x00280107');
} else {
imagePixelModule.smallestPixelValue = dataSet.int16('x00280106');
imagePixelModule.largestPixelValue = dataSet.int16('x00280107');
}
imagePixelModule.largestPixelValue = imagePixelModule.largestPixelValue === 0 ? undefined : imagePixelModule.largestPixelValue;
}
function populatePaletteColorLut(dataSet, imagePixelModule) {
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
'x00281101'
);
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
'x00281102'
);
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
'x00281103'
);
// The first Palette Color Lookup Table Descriptor value is the number of entries in the lookup table.
// When the number of table entries is equal to 2ˆ16 then this value shall be 0.
// See http://dicom.nema.org/MEDICAL/DICOM/current/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.5
if (imagePixelModule.redPaletteColorLookupTableDescriptor[0] === 0) {
imagePixelModule.redPaletteColorLookupTableDescriptor[0] = 65536;
imagePixelModule.greenPaletteColorLookupTableDescriptor[0] = 65536;
imagePixelModule.bluePaletteColorLookupTableDescriptor[0] = 65536;
}
// The third Palette Color Lookup Table Descriptor value specifies the number of bits for each entry in the Lookup Table Data.
// It shall take the value of 8 or 16.
// The LUT Data shall be stored in a format equivalent to 8 bits allocated when the number of bits for each entry is 8, and 16 bits allocated when the number of bits for each entry is 16, where in both cases the high bit is equal to bits allocated-1.
// The third value shall be identical for each of the Red, Green and Blue Palette Color Lookup Table Descriptors.
//
// Note: Some implementations have encoded 8 bit entries with 16 bits allocated, padding the high bits;
// this can be detected by comparing the number of entries specified in the LUT Descriptor with the actual value length of the LUT Data entry.
// The value length in bytes should equal the number of entries if bits allocated is 8, and be twice as long if bits allocated is 16.
const numLutEntries =
imagePixelModule.redPaletteColorLookupTableDescriptor[0];
const lutData = dataSet.elements.x00281201;
const lutBitsAllocated = lutData.length === numLutEntries ? 8 : 16;
// If the descriptors do not appear to have the correct values, correct them
if (
imagePixelModule.redPaletteColorLookupTableDescriptor[2] !==
lutBitsAllocated
) {
imagePixelModule.redPaletteColorLookupTableDescriptor[2] = lutBitsAllocated;
imagePixelModule.greenPaletteColorLookupTableDescriptor[2] =
lutBitsAllocated;
imagePixelModule.bluePaletteColorLookupTableDescriptor[2] =
lutBitsAllocated;
}
imagePixelModule.redPaletteColorLookupTableData = getLutData(
dataSet,
'x00281201',
imagePixelModule.redPaletteColorLookupTableDescriptor
);
imagePixelModule.greenPaletteColorLookupTableData = getLutData(
dataSet,
'x00281202',
imagePixelModule.greenPaletteColorLookupTableDescriptor
);
imagePixelModule.bluePaletteColorLookupTableData = getLutData(
dataSet,
'x00281203',
imagePixelModule.bluePaletteColorLookupTableDescriptor
);
}
function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId);
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
@@ -100,7 +202,7 @@ function metaDataProvider(type, imageId) {
};
}
if (type === 'imagePixelModule') {
return {
const imagePixelModule = {
samplesPerPixel: dataSet.uint16('x00280002'),
photometricInterpretation: dataSet.string('x00280004'),
rows: dataSet.uint16('x00280010'),
@@ -110,16 +212,41 @@ function metaDataProvider(type, imageId) {
highBit: dataSet.uint16('x00280102'),
pixelRepresentation: dataSet.uint16('x00280103'),
planarConfiguration: dataSet.uint16('x00280006'),
pixelAspectRatio: dataSet.uint16('x00280034'),
smallestPixelValue: null,
largestPixelValue: null,
redPaletteColorLookupTableDescriptor: dataSet.string('x00281101'),
greenPaletteColorLookupTableDescriptor: dataSet.string('x00281102'),
bluePaletteColorLookupTableDescriptor: dataSet.string('x00281103'),
redPaletteColorLookupTableData: dataSet.string('x00281201'),
greenPaletteColorLookupTableData: dataSet.string('x00281202'),
bluePaletteColorLookupTableData: dataSet.string('x00281203')
pixelAspectRatio: dataSet.string('x00280034'),
};
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
if (
imagePixelModule.photometricInterpretation === 'PALETTE COLOR' &&
dataSet.elements.x00281101
) {
populatePaletteColorLut(dataSet, imagePixelModule);
}
return imagePixelModule;
}
// if (type === 'imagePixelModule') {
// return {
// samplesPerPixel: dataSet.uint16('x00280002'),
// photometricInterpretation: dataSet.string('x00280004'),
// rows: dataSet.uint16('x00280010'),
// columns: dataSet.uint16('x00280011'),
// bitsAllocated: dataSet.uint16('x00280100'),
// bitsStored: dataSet.uint16('x00280101'),
// highBit: dataSet.uint16('x00280102'),
// pixelRepresentation: dataSet.uint16('x00280103'),
// planarConfiguration: dataSet.uint16('x00280006'),
// pixelAspectRatio: dataSet.uint16('x00280034'),
// smallestPixelValue: null,
// largestPixelValue: null,
// // smallestPixelValue: dataSet.uint16('x00280106'),
// // largestPixelValue: dataSet.uint16('x00280107'),
// redPaletteColorLookupTableDescriptor: dataSet.string('x00281101'),
// greenPaletteColorLookupTableDescriptor: dataSet.string('x00281102'),
// bluePaletteColorLookupTableDescriptor: dataSet.string('x00281103'),
// redPaletteColorLookupTableData: dataSet.string('x00281201'),
// greenPaletteColorLookupTableData: dataSet.string('x00281202'),
// bluePaletteColorLookupTableData: dataSet.string('x00281203')
// }
// }
}
export default metaDataProvider;
+1 -1
View File
@@ -21,7 +21,7 @@ service.interceptors.request.use(
async config => {
path = router && router.app && router.app._route && router.app._route.path
config.headers['Content-Type'] = 'application/json;charset=UTF-8'
config.baseURL = process.env.NODE_ENV === 'prod' ? "https://api.irc.extimaging.com" : '/api'
// config.baseURL = process.env.NODE_ENV === 'prod' ? "https://api.irc.extimaging.com" : '/api'
config.headers['Self-Referer'] = window.location.href
var language = zzSessionStorage.getItem('lang')
config.headers['Accept-Language'] = language === 'en' ? 'en-US,en;q=0.5' : 'zh-CN,zh;q=0.9'
@@ -36,10 +36,10 @@
<el-input-number v-model="form.SignViewMinimumMinutes" controls-position="right" :min="1" :max="50" />
</el-form-item>
<el-form-item :label="$t('dictionary:signature:form:CurrentStaffTrainDays')" prop="CurrentStaffTrainDays">
<el-input-number v-model="form.CurrentStaffTrainDays" controls-position="right" :min="1" :max="1000" />
<el-input-number v-model="form.CurrentStaffTrainDays" controls-position="right" :min="0" :max="1000" />
</el-form-item>
<el-form-item :label="$t('dictionary:signature:form:NewStaffTrainDays')" prop="NewStaffTrainDays">
<el-input-number v-model="form.NewStaffTrainDays" controls-position="right" :min="1" :max="1000" />
<el-input-number v-model="form.NewStaffTrainDays" controls-position="right" :min="0" :max="1000" />
</el-form-item>
<!-- <el-form-item v-if="form.Id !== ''" label="是否废除: ">
<el-radio-group v-model="form.IsDeleted">
@@ -77,11 +77,12 @@ export default {
FileTypeId: '',
Name: '',
Path: '',
IsDeleted: false,
IsPublish: false,
IsDeleted: true,
SignViewMinimumMinutes: null,
DocUserSignType: 0,
CurrentStaffTrainDays: null,
NewStaffTrainDays: null,
CurrentStaffTrainDays: 1,
NewStaffTrainDays: 14,
},
rules: {
FileTypeId: [
@@ -137,6 +138,7 @@ export default {
this.form.Name = this.data.Name
this.form.Path = this.data.Path
this.form.IsDeleted = this.data.IsDeleted
this.form.IsPublish = this.data.IsPublish
this.form.SignViewMinimumMinutes = this.data.SignViewMinimumMinutes
this.form.DocUserSignType = this.data.DocUserSignType
this.form.CurrentStaffTrainDays = this.data.CurrentStaffTrainDays
@@ -58,7 +58,7 @@ export default {
FilePath: null,
FileSize: null,
OffLine: false,
SystemDocumentId: null
SystemDocumentId: null,
},
rules: {
Name: [
@@ -2,7 +2,12 @@
<div v-if="config.visible">
<base-model :config="config">
<template slot="dialog-body">
<el-button size="mini" type="primary" style="float:right" @click.stop="edit({})">
<el-form ref="form" :model="rowData" label-width="80px" style="width: 80%;display: inline-block;">
<el-form-item :label="`${$t('dictionary:signature:table:Name')}: `">
<span class="name text-ellipsis" :title="rowData.Name">{{ rowData.Name }}</span>
</el-form-item>
</el-form>
<el-button size="mini" type="primary" style="float:right" @click.stop="edit({})" v-if="!isView">
{{ $t('common:button:add') }}</el-button>
<el-table :data="list" style="width: 100%" max-height="300px" v-loading="loading"
@sort-change="handleSortByColumn">
@@ -10,7 +15,7 @@
<el-table-column prop="Name" :label="$t('dictionary:signature:attachmentList:FileName')"
sortable="custom" />
<el-table-column prop="OffLine" :label="$t('dictionary:signature:attachmentList:OffLine')"
sortable="custom">
sortable="custom" v-if="!isView">
<template slot-scope="scope">
<el-switch v-model="scope.row.OffLine" @change="(val) => OffLine(scope.row, val)"
:active-value="true" :inactive-value="false" :active-text="$fd('YesOrNo', true)"
@@ -25,14 +30,14 @@
<el-button size="mini" type="text" @click.stop="preview(scope.row)">
{{ $t('common:button:preview') }}
</el-button>
<el-button size="mini" type="text" @click.stop="edit(scope.row)">
<el-button size="mini" type="text" @click.stop="edit(scope.row)" v-if="!isView">
{{ $t('common:button:edit') }}
</el-button>
<!-- <el-button size="mini" type="text" @click.stop="OffLine(scope.row, true)"
:disabled="scope.row.OffLine">
{{ $t('dictionary:signature:attachmentList:OffLine') }}
</el-button> -->
<el-button size="mini" type="text" @click.stop="del(scope.row)">
<el-button size="mini" type="text" @click.stop="del(scope.row)" v-if="!isView">
{{ $t('common:button:delete') }}
</el-button>
</template>
@@ -66,6 +71,7 @@ const defaultSearchData = () => {
PageIndex: 1,
PageSize: 1000,
Asc: false,
OffLine: null,
SortField: null
}
}
@@ -86,6 +92,16 @@ export default {
}
}
},
isView: {
type: Boolean,
default: false
},
rowData: {
type: Object,
default: () => {
return {}
}
},
SystemDocumentId: {
type: String,
default: ''
@@ -147,6 +163,10 @@ export default {
}
},
edit(row) {
this.title = this.$t('dictionary:signature:attachmentForm:title:update')
if (!row.Id) {
this.title = this.$t('dictionary:signature:attachmentForm:title:add')
}
this.rowData = Object.assign({}, row)
this.visible = true
},
@@ -155,6 +175,9 @@ export default {
if (!this.SystemDocumentId) return false
this.loading = true
this.searchData.SystemDocumentId = this.SystemDocumentId
if (this.isView) {
this.searchData.OffLine = false
}
let res = await getSystemDocumentAttachmentList(this.searchData)
this.loading = false
if (res.IsSuccess) {
@@ -226,4 +249,10 @@ export default {
}
}
</script>
</script>
<style lang="scss" scoped>
.name {
display: block;
width: 90%;
}
</style>
@@ -78,7 +78,7 @@ const defaultSearchData = () => {
PageIndex: 1,
PageSize: 1000,
Asc: false,
OffLine: false,
OffLine: null,
SortField: null
}
}
@@ -96,6 +96,10 @@ export default {
visible: {
type: Boolean,
default: false
},
isView: {
type: Boolean,
default: false
}
},
data() {
@@ -137,6 +141,9 @@ export default {
if (!this.SystemDocumentId) return false
this.loading = true
this.searchData.SystemDocumentId = this.SystemDocumentId
if (this.isView) {
this.searchData.OffLine = false
}
let res = await getSystemDocumentAttachmentList(this.searchData)
this.loading = false
if (res.IsSuccess) {
@@ -81,7 +81,15 @@
sortable="custom" min-width="120px" />
<el-table-column prop="Name" :label="$t('dictionary:signature:table:Name')" show-overflow-tooltip
sortable="custom" min-width="120px" />
<el-table-column prop="AttachmentCount" :label="$t('dictionary:signature:table:AttachmentCount')"
show-overflow-tooltip sortable="custom" min-width="150px">
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row, true)">
{{ scope.row.AttachmentCount }}
<i class="el-icon-upload2" />
</el-button>
</template>
</el-table-column>
<el-table-column prop="SignViewMinimumMinutes" :label="$t('dictionary:signature:table:SignViewMinimumMinutes')"
show-overflow-tooltip sortable="custom" min-width="200px" />
<el-table-column prop="CurrentStaffTrainDays" :label="$t('dictionary:signature:table:CurrentStaffTrainDays')"
@@ -123,32 +131,25 @@
{{ $fd('ReadingYesOrNo', Number(scope.row.DocUserSignType)) }}
</template>
</el-table-column>
<el-table-column prop="AttachmentCount" :label="$t('dictionary:signature:table:AttachmentCount')"
show-overflow-tooltip sortable="custom" min-width="150px">
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row)">{{ scope.row.AttachmentCount }}</el-button>
</template>
</el-table-column>
<el-table-column prop="CreateTime" :label="$t('dictionary:signature:table:CreateTime')" show-overflow-tooltip
sortable="custom" min-width="180px" />
<el-table-column :label="$t('common:action:action')" min-width="220px" fixed="right">
<el-table-column :label="$t('common:action:action')" min-width="240px" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handlePreview(scope.row)">
{{ $t('common:button:preview') }}
<el-button circle icon="el-icon-view" @click="handlePreview(scope.row)"
:title="$t('common:button:preview')">
</el-button>
<el-button type="text" @click="handleEdit(scope.row)">
{{ $t('common:button:edit') }}
<el-button circle icon="el-icon-edit-outline" :title="$t('common:button:edit')"
@click="handleEdit(scope.row)">
</el-button>
<el-button :disabled="scope.row.IsPublish || scope.row.IsDeleted" type="text"
@click="publishSystemDocument([scope.row])">
{{ $t('dictionary:signature:button:publish') }}
<el-button :disabled="scope.row.IsPublish" circle icon="el-icon-position"
:title="$t('dictionary:signature:button:publish')" @click="publishSystemDocument([scope.row])">
</el-button>
<el-button :disabled="scope.row.IsDeleted" type="text" @click="handleRepeal([scope.row])">
{{ $t('common:button:revoke') }}
<el-button :disabled="scope.row.IsDeleted" circle icon="el-icon-close" :title="$t('common:button:revoke')"
@click="handleRepeal([scope.row])">
</el-button>
<el-button type="text" @click="handleDelete(scope.row)">
{{ $t('common:button:delete') }}
<el-button circle icon="el-icon-delete" :title="$t('common:button:delete')"
@click="handleDelete(scope.row)">
</el-button>
</template>
</el-table-column>
@@ -163,10 +164,17 @@
<TemplateForm :data="currentRow" @closeDialog="closeDialog" @getList="getList" />
</el-dialog>
<!--附件列表-->
<attachmentList v-if="config.visible" :config="config" :SystemDocumentId="SystemDocumentId" @getList="getList" />
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
@getList="getList" />
<!-- 预览文件 -->
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" :isView="true"
v-if="perview_visible" />
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="$t('common:button:preview')"
:fullscreen="true" append-to-body custom-class="base-dialog-wrapper">
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;font-size: 18px;color:#409EFF"
@click.stop="openAttachment(currentRow)" v-if="currentRow.AttachmentCount">{{
$t('trials:signRecords:table:AttachmentCount') }} ({{
currentRow.AttachmentCount }})</span>
<div class="base-modal-body" style="border: 2px solid #ccc; padding: 10px">
<PreviewFile v-if="previewVisible" :file-path="currentPath" :file-type="currentType" />
</div>
@@ -175,7 +183,7 @@
</BaseContainer>
</template>
<script>
import { getSystemDocumentList, deleteSystemDocument, publishSystemDocument, outLineSystemDocument } from '@/api/dictionary'
import { getSystemDocumentList, deleteSystemDocument, publishSystemDocument, outLineSystemDocument, getSystemDocumentAttachmentList } from '@/api/dictionary'
import { userAbandonDoc, getTrialUserTypeList } from '@/api/trials'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
@@ -183,6 +191,7 @@ import TemplateForm from './TemplateForm'
import attachmentList from './attachmentList'
import PreviewFile from '@/components/PreviewFile/index'
import attachmentPreview from './attachmentPreview'
const searchDataDefault = () => {
return {
FileTypeId: '',
@@ -198,7 +207,7 @@ const searchDataDefault = () => {
}
export default {
name: 'SignatureTemplate',
components: { BaseContainer, Pagination, TemplateForm, PreviewFile, attachmentList },
components: { BaseContainer, Pagination, TemplateForm, PreviewFile, attachmentList, attachmentPreview },
dicts: ['Sys_Document'],
props: {
isSystem: {
@@ -231,7 +240,8 @@ export default {
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
multipleSelection: []
multipleSelection: [],
perview_visible: false
}
},
mounted() {
@@ -240,7 +250,7 @@ export default {
},
methods: {
handleSelectable(row) {
return !row.IsDeleted
return !row.IsDeleted || !row.IsPublish
},
handleSelectionChange(val) {
this.multipleSelection = val;
@@ -253,8 +263,10 @@ export default {
distinguishCancelAndClose: true,
})
if (!confirm) return false
let arr = list.filter(item => !item.IsPublish)
if (arr.length <= 0) return this.getList()
let data = {
ids: list.map(item => item.Id)
ids: arr.map(item => item.Id)
}
this.loading = true
let res = await publishSystemDocument(data)
@@ -267,9 +279,14 @@ export default {
console.log(err)
}
},
openAttachment(row) {
openAttachment(row, isList = false) {
this.SystemDocumentId = row.Id
this.config.visible = true
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
changeTimeList() {
if (this.timeList) {
@@ -319,12 +336,23 @@ export default {
this.editVisible = true
},
// 预览
handlePreview(row) {
async handlePreview(row) {
const { Name, FullFilePath } = row
this.currentPath = FullFilePath
this.currentType = row.Name
? Name.substring(Name.lastIndexOf('.') + 1).toLocaleLowerCase()
: ''
let searchData = {
OffLine: false,
PageIndex: 1,
PageSize: 1000,
Asc: false,
SortField: null,
SystemDocumentId: row.Id
}
this.currentRow = Object.assign({}, row)
let res = await getSystemDocumentAttachmentList(searchData)
this.currentRow.AttachmentCount = res.Result.CurrentPageData.length
this.previewVisible = true
},
// 编辑
@@ -340,8 +368,10 @@ export default {
distinguishCancelAndClose: true,
})
.then(() => {
let arr = row.filter(item => !item.IsDeleted)
if (arr.length <= 0) return this.getList()
let data = {
Ids: row.map(item => item.Id)
Ids: arr.map(item => item.Id)
}
this.loading = true
outLineSystemDocument(data)
@@ -41,7 +41,8 @@
<!-- 签署人 -->
<el-form-item :label="$t('trials:signRecords:table:user')" v-if="!isDoc">
<el-select v-model="searchData.UserId" clearable filterable style="width: 140px">
<el-option v-for="item of userOptions" :key="item.ConfirmUserId" :label="item.FullName" :value="item.ConfirmUserId">
<el-option v-for="item of userOptions" :key="item.ConfirmUserId" :label="item.FullName"
:value="item.ConfirmUserId">
<span style="float: left">{{ item.RealName || item.FullName }}</span>
<span style="
float: right;
@@ -123,9 +124,10 @@
<el-table-column prop="Name" :label="$t('trials:signRecords:table:fileName')" show-overflow-tooltip
sortable="custom" />
<el-table-column :label="$t('trials:signRecords:table:AttachmentCount')" prop="AttachmentCount"
show-overflow-tooltip sortable="custom">
show-overflow-tooltip>
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row)">{{ scope.row.AttachmentCount }}</el-button>
<el-button type="text" @click.stop="openAttachment(scope.row, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<!-- 文件级别 -->
@@ -207,15 +209,18 @@
<!-- 预览文件 -->
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="$t('trials:signRecords:action:preview')"
:fullscreen="true" append-to-body custom-class="base-dialog-wrapper">
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;" @click.stop="openAttachment(currentRow)"
v-if="currentRow.AttachmentCount">{{
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;font-size: 18px;color:#409EFF"
@click.stop="openAttachment(currentRow)" v-if="currentRow.AttachmentCount">{{
$t('trials:signRecords:table:AttachmentCount') }} ({{
currentRow.AttachmentCount }})</span>
<div class="base-modal-body" style="border: 2px solid #ccc; padding: 10px">
<PreviewFile v-if="previewVisible" :file-path="currentPath" :file-type="currentType" @getList="getList" />
</div>
</el-dialog>
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" v-if="perview_visible" />
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
:isView="true" @getList="getList" />
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" :isView="true"
v-if="perview_visible" />
</BaseContainer>
</template>
<script>
@@ -227,11 +232,12 @@ import {
getSysDocumentConfirmList,
getSysDocSignUserList
} from '@/api/trials'
import { pMTrainingRecordList_Export } from '@/api/export'
import { pMTrainingRecordList_Export, getSysDocumentConfirmList_Export } from '@/api/export'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import PreviewFile from '@/components/PreviewFile/index'
import attachmentPreview from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentPreview'
import attachmentList from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentList'
const searchDataDefault = () => {
return {
UserName: null,
@@ -252,7 +258,7 @@ const searchDataDefault = () => {
}
export default {
name: 'AttachmentsManagement',
components: { BaseContainer, Pagination, PreviewFile, attachmentPreview },
components: { BaseContainer, Pagination, PreviewFile, attachmentPreview, attachmentList },
props: {
isDoc: {
type: Boolean,
@@ -291,6 +297,11 @@ export default {
timeListC: [],
SystemDocumentId: null,
perview_visible: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -299,10 +310,15 @@ export default {
this.getUserType()
},
methods: {
openAttachment(row) {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.perview_visible = true
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
changeTimeList() {
if (this.timeList) {
@@ -324,6 +340,15 @@ export default {
},
handleExport() {
this.exportLoading = true
if (this.isSystem) {
return getSysDocumentConfirmList_Export(this.searchData)
.then(() => {
this.exportLoading = false
})
.catch((err) => {
this.exportLoading = false
})
}
pMTrainingRecordList_Export(this.searchData)
.then(() => {
this.exportLoading = false
@@ -49,7 +49,8 @@
<el-table-column :label="$t('trials:self-attachment:table:AttachmentCount')" prop="AttachmentCount"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row)">{{ scope.row.AttachmentCount }}</el-button>
<el-button type="text" @click.stop="openAttachment(scope.row, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<!-- 是否废除 -->
@@ -96,8 +97,8 @@
<!-- 预览文件 -->
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="title" :fullscreen="true" append-to-body
custom-class="base-dialog-wrapper">
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;" @click.stop="openAttachment(currentRow)"
v-if="currentRow.AttachmentCount">{{
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;font-size: 18px;color:#409EFF"
@click.stop="openAttachment(currentRow)" v-if="currentRow.AttachmentCount">{{
$t('trials:self-attachment:table:AttachmentCount') }} ({{
currentRow.AttachmentCount }})</span>
<span v-if="!currentIsConfirm" style="position: fixed;right: 75px;top: 14px;">
@@ -129,7 +130,10 @@
<SignForm :is-system-doc="currentRow.IsSystemDoc" :document-id="currentRow.Id" :file-name="fileName"
:trial-id="trialId" @closeDialog="closeSignDialog" />
</el-dialog>
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" v-if="perview_visible" />
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
:isView="true" @getList="getList" />
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" :isView="true"
v-if="perview_visible" />
</BaseContainer>
</template>
<script>
@@ -140,6 +144,7 @@ import PreviewFile from '@/components/PreviewFile/index'
import SignForm from './components/SignForm'
import store from '@/store'
import attachmentPreview from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentPreview'
import attachmentList from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentList'
const searchDataDefault = () => {
return {
FileTypeId: '',
@@ -153,7 +158,7 @@ const searchDataDefault = () => {
}
export default {
name: 'TrialAttachments',
components: { BaseContainer, Pagination, PreviewFile, SignForm, attachmentPreview },
components: { BaseContainer, Pagination, PreviewFile, SignForm, attachmentPreview, attachmentList },
data() {
return {
searchData: searchDataDefault(),
@@ -176,7 +181,12 @@ export default {
typeOptions: [],
trialId: this.$route.query.trialId,
SystemDocumentId: null,
perview_visible: null
perview_visible: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -193,10 +203,15 @@ export default {
}
},
methods: {
openAttachment(row) {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.perview_visible = true
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
// 获取系统文件数据
getList() {
@@ -729,7 +729,7 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
@@ -766,7 +766,7 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
@@ -801,7 +801,7 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
@@ -1406,7 +1406,7 @@ export default {
)
var token = getToken()
var path = ''
if (row.ReadingTool === 0) {
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${row.TrialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${row.TrialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
@@ -263,7 +263,7 @@ export default {
series: '',
ToolStateManager: null,
renderedMeasured: [],
measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi'],
measuredTools: ['Length', 'Bidirectional', 'ArrowAnnotate', 'RectangleRoi', 'Probe'],
measureData: [],
selectedLesion: null,
activeTool: 0, // 0:enable 1:passive 2:active
@@ -303,8 +303,8 @@ export default {
],
scrollSyncInfo: { offset: 0 },
hideMeasureArr: []
hideMeasureArr: [],
isInitWwwc: true
}
},
computed: {
@@ -565,7 +565,6 @@ export default {
e.stopPropagation()
e.preventDefault()
} else if (this.activeToolName === 'Length' || this.activeToolName === 'Bidirectional' && this.readingTaskState < 2) {
console.log(e.detail.image)
if (!e.detail.image.columnPixelSpacing || !e.detail.image.rowPixelSpacing) {
// ''
this.$confirm(this.$t('trials:reading:warnning:msg56'), '', {
@@ -578,6 +577,13 @@ export default {
e.stopPropagation()
e.preventDefault()
}
} else if (this.CriterionType === 21 && this.activeToolName === 'Probe' && this.readingTaskState < 2) {
if (!(e.detail.image.imageFrame.photometricInterpretation === 'MONOCHROME1' || e.detail.image.imageFrame.photometricInterpretation === 'MONOCHROME2')) {
this.$alert(this.$t('trials:MRIPDFF:message:message5'))
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
}
}
},
pointNearTool(e) {
@@ -607,7 +613,11 @@ export default {
if ((i.LesionType === 0 || i.LesionType === 1 || i.LesionType === 7) && i.IsFirstChangeTask) {
arr.push(i.OrderMarkName)
}
if (i.Id && this.readingTaskState >= 2) {
arr.push(i.OrderMarkName)
}
})
console.log(arr)
return arr
},
getMergeMarks(measureDatas) {
@@ -616,6 +626,9 @@ export default {
if ((i.LesionType === 0) && i.SplitOrMergeType === 1) {
arr.push(i.OrderMarkName)
}
if (i.Id && this.readingTaskState >= 2) {
arr.push(i.OrderMarkName)
}
})
return arr
},
@@ -661,7 +674,7 @@ export default {
},
sliderMousemove(e) {
if (!this.sliderInfo.isMove) return
console.log('sliderMousemove')
// console.log('sliderMousemove')
var PX = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
var boxHeight = this.$refs['sliderBox'].clientHeight
if (PX < 0) return
@@ -692,7 +705,7 @@ export default {
} else if (criterionType === 2) {
this.disabledMarks = this.getMergeMarks(this.visitTaskList[idx].MeasureData)
} else {
this.disabledMarks = []
this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData)
}
return true
},
@@ -785,6 +798,8 @@ export default {
} else if (toolType === 'Length') {
// toolState.data[i].length = this.calculateLenth(toolState.data[i])
}
measureData.largestPixelValue = image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -901,6 +916,9 @@ export default {
}
} else if (this.activeTool === 1 && this.readingTaskState < 2) {
cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
} else if (this.readingTaskState >= 2 && this.isCurrentTask) {
// setToolPassiveForElement
cornerstoneTools.setToolEnabledForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
} else {
cornerstoneTools.setToolEnabledForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
}
@@ -919,6 +937,9 @@ export default {
}
}
})
if (this.readingTaskState >= 2 && this.activeToolName && this.isCurrentTask) {
cornerstoneTools.setToolActiveForElement(element, this.activeToolName, { mouseButtonMask: 1 })
}
},
setMeasureDataVisible() {
if (this.readingTaskState >= 2) return
@@ -986,6 +1007,7 @@ export default {
}
},
mouseClick(e) {
if (this.readingTaskState >= 2) return
const { element, currentPoints, image, viewport } = e.detail
var imageId = image.imageId
const imageInfo = this.getInstanceInfo(imageId)
@@ -1010,6 +1032,8 @@ export default {
var questionInfo = this.measureData[idx]
// const canvas = this.canvas.querySelector('canvas')
// measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
measureData.largestPixelValue = image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -1035,6 +1059,7 @@ export default {
},
loadImageStack(dicomSeries) {
return new Promise(resolve => {
this.isInitWwwc = true
this.isCurrentTask = dicomSeries.isCurrentTask
this.isBaseline = dicomSeries.isBaseLineTask
this.readingTaskState = dicomSeries.readingTaskState
@@ -1073,7 +1098,8 @@ export default {
} else if (criterionType === 2) {
this.disabledMarks = this.getMergeMarks(this.visitTaskList[idx].MeasureData)
} else {
this.disabledMarks = []
// this.disabledMarks = []
this.disabledMarks = this.getDisabledMarks(this.visitTaskList[idx].MeasureData)
}
this.maxVistNum = this.visitTaskList[this.visitTaskList.length - 1].VisitTaskNum
@@ -1202,7 +1228,7 @@ export default {
this.stack.frame = this.stack.isExistMutiFrames ? parseInt(frame) : null
this.stack.instanceId = instanceId
this.height = (this.stack.currentImageIdIndex) * 100 / (this.stack.imageIds.length - 1)
// this.resetWwwc()
resolve()
})
},
@@ -1300,6 +1326,9 @@ export default {
this.scrollSyncInfo.offset = 0
}
this.renderMeasuredData(e)
if (this.isInitWwwc) {
this.resetWwwc()
}
},
getOrientationMarker(element) {
const enabledElement = cornerstone.getEnabledElement(element)
@@ -1336,6 +1365,8 @@ export default {
this.stack.frame = this.stack.isExistMutiFrames ? parseInt(frame) : null
if (e.detail.toolName === 'Length' || e.detail.toolName === 'ArrowAnnotate' || e.detail.toolName === 'RectangleRoi') {
const measureData = {}
measureData.largestPixelValue = element.image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -1353,6 +1384,8 @@ export default {
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
} else if (e.detail.toolName === 'Bidirectional') {
const measureData = {}
measureData.largestPixelValue = element.image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -1369,6 +1402,8 @@ export default {
cornerstoneTools.setToolPassiveForElement(this.canvas, e.detail.toolName)
} else if (e.detail.toolName === 'Probe') {
const measureData = {}
measureData.largestPixelValue = element.image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -1478,6 +1513,8 @@ export default {
var questionInfo = this.measureData[idx]
// const canvas = this.canvas.querySelector('canvas')
// measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
measureData.largestPixelValue = element.image.imageFrame.largestPixelValue
measureData.imageId = imageId
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
@@ -1669,6 +1706,8 @@ export default {
},
resetWwwc() {
// console.log('resetWwwc')
this.isInitWwwc = true
this.toolState.viewportInvert = false
var viewport = cornerstone.getViewport(this.canvas)
// viewport.invert = false
@@ -1679,7 +1718,7 @@ export default {
},
setWwwc(ww, wc) {
// console.log('setWwwc', ww, wc)
this.isInitWwwc = false
var viewport = cornerstone.getViewport(this.canvas)
viewport.voi.windowWidth = ww
viewport.voi.windowCenter = wc
@@ -416,7 +416,7 @@
<dicom-canvas
v-if="canvasW"
:ref="`dicomCanvas${i-1}`"
:style="{width:canvasW,height: canvasH}"
:style="{width: fullScreenIndex === i-1 ? fullScreenWidth : canvasW,height: fullScreenIndex === i-1 ? fullScreenHeight : canvasH}"
:canvas-index="i-1"
:is-active="i-1===currentDicomCanvasIndex"
:is-scroll-sync="isScrollSync"
@@ -989,7 +989,10 @@ export default {
signVisible: false,
signCode: null,
currentUser: zzSessionStorage.getItem('userName'),
tmpData: null
tmpData: null,
fullScreenIndex: -1,
fullScreenWidth: window.innerWidth - 570 + 'px',
fullScreenHeight: window.innerHeight - 130 + 'px'
}
},
@@ -1379,22 +1382,25 @@ export default {
this.personalConfigDialog.visible = true
},
setCornerstoneStyle(i) {
if (this.layoutCol === 1 && this.layoutRow === 1) {
this.fullScreenIndex = -1
return
}
if (this.cornerstoneStyle.position) {
this.cornerstoneStyle = {}
this.setCanvasStyle()
this.fullScreenIndex = -1
} else {
this.cornerstoneStyle = {
position: 'absolute',
top: '72px',
top: '67px',
left: '0px',
right: '350px',
zIndex: 10
zIndex: 10,
}
this.canvasW = window.innerWidth - 570 + 'px'
this.canvasH = window.innerHeight - 130 + 'px'
this.fullScreenIndex = this.currentDicomCanvasIndex
}
this.$nextTick(() => {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].reloadCanvas()
for (var i = 0; i < this.maxCanvas; i++) {
this.$refs[`dicomCanvas${i}`][0].reloadCanvas()
}
@@ -1941,6 +1947,7 @@ export default {
},
//
changeLayout(name) {
this.fullScreenIndex = -1
if (this.activeTool) {
if (this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].isCurrentTask && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].readingTaskState < 2) {
this.$nextTick(() => {
@@ -2043,14 +2050,17 @@ export default {
if (i === -1) return
var isCurrentTask = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].isCurrentTask
var readingTaskState = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].readingTaskState
if (!isCurrentTask || readingTaskState >= 2) {
if (!isCurrentTask) {
this.measuredTools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(toolName)
this.activeTool = ''
}
} else {
} else if (isCurrentTask && readingTaskState >= 2) {
this.measuredTools[i].isDisabled = false
e.target.style.cursor = 'pointer'
} else if (isCurrentTask && readingTaskState < 2) {
// var obj = this.$refs['measurementList'].isCanActiveTool(toolName, true)
var obj = this.$refs['measurementList'].isCanActiveTool(toolName, true)
this.measuredTools[i].disabledReason = obj.reason
@@ -2087,7 +2097,7 @@ export default {
var toolObj = this.measuredTools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
var dicomSeries = this.canvasObj[this.currentDicomCanvasIndex]
if (dicomSeries.isCurrentTask && isMeasuredTool && dicomSeries.readingTaskState < 2) {
if (dicomSeries.isCurrentTask && isMeasuredTool) {
if (this.activeTool) {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
@@ -2248,13 +2258,14 @@ export default {
this.customWwc.visible = false
},
toggleInvert() {
if (this.activeTool === 'reversecolor') {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
this.activeTool = ''
} else {
this.activeTool = 'reversecolor'
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
}
// if (this.activeTool === 'reversecolor') {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
// this.activeTool = ''
// } else {
// this.activeTool = 'reversecolor'
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
// }
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
},
setImageIndexSync() {
this.isScrollSync = !this.isScrollSync
File diff suppressed because it is too large Load Diff
@@ -203,7 +203,6 @@ export default {
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)
@@ -249,8 +248,10 @@ export default {
this.getOrientationMarker()
this.sliderBoxHeight = imageIndex * 100 / (this.seriesInfo.imageMaxLength - 1)
}
var properties = viewport.getProperties()
let properties = viewport.getProperties()
if (this.index === 3) {
properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID`)
}
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
@@ -324,7 +325,12 @@ export default {
viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
var properties = viewport.getProperties()
let properties = viewport.getProperties()
if (this.index === 3) {
// const volumeId = viewport.getVolumeId()
const volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID`
properties = viewport.getProperties(volumeId)
}
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
@@ -562,12 +568,21 @@ export default {
ctx.fillRect(0, 0, rectWidth, rectHeight)
},
setWwWc() {
var properties = viewport.getProperties()
let properties = viewport.getProperties()
if (this.index === 3) {
properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID`)
}
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const windowWidth = upper - lower
const windowCenter = (upper + lower) / 2
// console.log(lower, upper)
// const windowWidth = upper - lower
// const windowCenter = (upper + lower) / 2
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
lower,
upper
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
this.$emit('upperRangeChange', Math.round(windowWidth))
}
},
async getScreenshots() {
@@ -4,7 +4,8 @@ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunc
import { utilities } from '@cornerstonejs/core'
const { getColormap } = utilities.colormap
function setPetColorMapTransferFunctionForVolumeActor(
volumeInfo
volumeInfo,
isPT = false
) {
const { volumeActor, preset } = volumeInfo
const mapper = volumeActor.getMapper()
@@ -22,8 +23,14 @@ function setPetColorMapTransferFunctionForVolumeActor(
// Create scalar opacity function
const ofun = vtkPiecewiseFunction.newInstance()
ofun.addPoint(0, 0.0)
ofun.addPoint(0.1, 0.9)
if (isPT) {
ofun.addPoint(0, 0.0)
ofun.addPoint(0.1, 1.0)
} else {
ofun.addPoint(0, 0.0)
ofun.addPoint(0.1, 0.9)
}
ofun.addPoint(5, 1.0)
volumeActor.getProperty().setScalarOpacity(0, ofun)
@@ -205,6 +205,7 @@
import { saveTableQuestionMark, submitTaskRowInfo, deleteTableQuestionMark, deleteSingleTableQuestionMark } from '@/api/reading'
import DicomEvent from './../DicomEvent'
import store from '@/store'
import * as cornerstone from 'cornerstone-core'
export default {
name: 'MeasurementForm',
props: {
@@ -444,7 +445,12 @@ export default {
//
measureData.data.remark = this.getLesionName(this.orderMark, this.activeQuestionMark)
}
const val = measureData.data.cachedStats.mean / 10
// const val = measureData.data.cachedStats.mean / 10
let val = parseFloat(measureData.data.cachedStats.mean)
// let imagePixelModule = cornerstone.metaData.get('imagePixelModule', measureData.imageId)
if (measureData.largestPixelValue >= 500) {
val = val / 10
}
this.$set(this.questionForm, measureData.tableQuestionId, val.toFixed(this.digitPlaces))
data = {
Id: '',
@@ -574,14 +580,26 @@ export default {
let params = {}
if (i > -1 && this.markList[i].measureData && this.markList[i].measureData.MeasureData) {
const measureData = this.markList[i].measureData.MeasureData
const tableQuestionId = this.markList[i].tableQuestionId
if (this.questionForm[tableQuestionId] > 100) {
const confirm = await this.$confirm(
this.$t('trials:MRIPDFF:message:message4'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
}
//
DicomEvent.$emit('getScreenshots', { questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, visitTaskId: this.visitTaskId, lesionName: measureData.OrderMarkName, lesionType: null, isMarked: !!measureData }, async val => {
params = Object.assign({}, this.markList[i].measureData)
if (val) {
const pictureObj = await this.uploadScreenshots(`${new Date().getTime()}`, val)
params.PicturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
}
const tableQuestionId = this.markList[i].tableQuestionId
params.Answer = this.questionForm[tableQuestionId]
params.MeasureData = JSON.stringify(this.markList[i].measureData.MeasureData)
loading.close()
@@ -264,7 +264,6 @@ export default {
if (item.Type === 'table' && item.Id === obj.questionId) {
var idx = item.TableQuestions.Answers.findIndex(i => i.RowIndex === obj.rowIndex)
item.TableQuestions.Answers[idx].isMeasurable = obj.isMeasurable
console.log(obj.isMeasurable)
item.TableQuestions.Answers[idx].mean = obj.mean
item.TableQuestions.Answers[idx].saveTypeEnum = obj.saveTypeEnum
@@ -556,7 +556,7 @@
<dicom-canvas
v-if="canvasW"
:ref="`dicomCanvas${i - 1}`"
:style="{ width: canvasW, height: canvasH }"
:style="{width: fullScreenIndex === i-1 ? fullScreenWidth : canvasW,height: fullScreenIndex === i-1 ? fullScreenHeight : canvasH}"
:canvas-index="i - 1"
:is-active="i - 1 === currentDicomCanvasIndex"
:is-scroll-sync="isScrollSync"
@@ -895,6 +895,9 @@ export default {
uploadTrialCriterion: {},
uploadStatus: 'upload',
taskId: '',
fullScreenIndex: -1,
fullScreenWidth: window.innerWidth - 570 + 'px',
fullScreenHeight: window.innerHeight - 128 + 'px'
}
},
@@ -1314,22 +1317,25 @@ export default {
this.personalConfigDialog.visible = true
},
setCornerstoneStyle(i) {
if (this.layoutCol === 1 && this.layoutRow === 1) {
this.fullScreenIndex = -1
return
}
if (this.cornerstoneStyle.position) {
this.cornerstoneStyle = {}
this.setCanvasStyle()
this.fullScreenIndex = -1
} else {
this.cornerstoneStyle = {
position: 'absolute',
top: '72px',
top: '70px',
left: '205px',
right: '350px',
zIndex: 10,
}
this.canvasW = window.innerWidth - 570 + 'px'
this.canvasH = window.innerHeight - 130 + 'px'
this.fullScreenIndex = this.currentDicomCanvasIndex
}
this.$nextTick(() => {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].reloadCanvas()
for (var i = 0; i < this.maxCanvas; i++) {
this.$refs[`dicomCanvas${i}`][0].reloadCanvas()
}
@@ -1928,6 +1934,7 @@ export default {
},
//
changeLayout(name) {
this.fullScreenIndex = -1
if (this.activeTool) {
if (
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0]
@@ -2301,15 +2308,16 @@ export default {
this.customWwc.visible = false
},
toggleInvert() {
if (this.activeTool === 'reversecolor') {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
this.activeTool = ''
} else {
this.activeTool = 'reversecolor'
this.$refs[
`dicomCanvas${this.currentDicomCanvasIndex}`
][0].toggleInvert()
}
// if (this.activeTool === 'reversecolor') {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
// this.activeTool = ''
// } else {
// this.activeTool = 'reversecolor'
// this.$refs[
// `dicomCanvas${this.currentDicomCanvasIndex}`
// ][0].toggleInvert()
// }
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
},
setImageIndexSync() {
this.isScrollSync = !this.isScrollSync
@@ -456,7 +456,7 @@ export default {
deep: true,
immediate: true,
handler(v, oldv) {
console.log(v)
// console.log(v)
}
}
},
@@ -1,8 +1,8 @@
<template>
<div ref="container" style="width:100%;height:100%" class="dicom-container">
<!-- 访视阅片 -->
<div v-if="readingCategory=== 1 && (CriterionType === 7)" class="reading-wrapper">
<VisitReview />
<div v-if="readingCategory=== 1 && (CriterionType === 7 || ((CriterionType === 1 || CriterionType === 0) && readingVersionEnum === 1)) " class="reading-wrapper">
<VisitReview :reading-tool="readingTool" />
</div>
<div v-else-if="(isReadingTaskViewInOrder === 1 || ((isReadingTaskViewInOrder !== 1) && isShow)) && readingCategory=== 1 && CriterionType !== 0" class="reading-wrapper">
<el-tabs v-model="activeName" v-loading="loading" :before-leave="beforeLeave">
@@ -222,7 +222,7 @@ export default {
isReadingTaskViewInOrder: null,
firstTaskReadingCategory: null,
criterionType: null,
readingTool: null,
readingTool: 0,
dialogVisible: false,
TrialReadingCriterionId: null,
isNewSubject: null,
@@ -234,7 +234,8 @@ export default {
isFullscreen: false,
tipVisible: false,
closeCDVisible: false,
cdVisitTaskId: ''
cdVisitTaskId: '',
readingVersionEnum: null
}
},
mounted() {
@@ -271,7 +272,7 @@ export default {
this.visitTaskId = this.$router.currentRoute.query.visitTaskId
// this.isReadingTaskViewInOrder = parseInt(this.$router.currentRoute.query.isReadingTaskViewInOrder)
// this.criterionType = this.$router.currentRoute.query.criterionType
// this.readingTool = this.$router.currentRoute.query.readingTool
this.readingTool = this.$router.currentRoute.query.readingTool ? Number(this.$router.currentRoute.query.readingTool) : 0
this.TrialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
// this.isNewSubject = this.$router.currentRoute.query.isNewSubject
@@ -346,6 +347,7 @@ export default {
localStorage.setItem('IsExistUnprocessedFeedback', res.Result.IsExistUnprocessedFeedback)
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
this.readingCategory = res.Result.ReadingCategory
this.readingVersionEnum = res.Result.ReadingVersionEnum
this.questionFormChangeState = false
this.questionFormChangeNum = 0
if (this.isExistsClinicalData && this.isNeedReadClinicalData && !this.isReadClinicalData) {
@@ -594,6 +596,10 @@ export default {
position: relative;
}
}
::v-deep .el-radio__input.is-disabled.is-checked .el-radio__inner {
background-color: #428bca;
border-color: #428bca;
}
.full-dialog-container{
::v-deep.is-fullscreen .el-dialog__body{
@@ -0,0 +1,276 @@
<template>
<el-form ref="fusionForm" :model="fusionForm" label-width="120px" label-position="left">
<!-- 任务名称 -->
<el-form-item :label="$t('trials:lugano:fusionDialog:label:taskBlindName')" prop="taskBlindName">
<el-select v-model="fusionForm.visitTaskId" clearable @change="handleTaskIdChange" disabled>
<el-option v-for="item in taskList" :key="item.VisitTaskId" :label="item.TaskBlindName"
:value="item.VisitTaskId" />
</el-select>
</el-form-item>
<!-- 检查 -->
<el-form-item :label="$t('trials:lugano:fusionDialog:label:study')" prop="studyId" :rules="[
{ required: true, message: $t('common:ruleMessage:select'), trigger: 'blur' }
]">
<el-select v-model="fusionForm.studyId" clearable @change="handleStudyIdChange">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode" :value="item.StudyId" />
</el-select>
</el-form-item>
<!-- 融合图像 -->
<label style="color: #dfdfdf;line-height: 36px;vertical-align: middle;font-size: 14px;">{{
$t('trials:lugano:fusionDialog:label:fusionImages') }}</label>
<div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom:10px;"
class="series-table">
<div style="border: 1px solid #5e5e5e;border-radius: 5px;padding: 0 10px;">
<!-- CT/MRI序列 -->
<h3 style="color:#ddd">{{ $t('trials:lugano:fusionDialog:label:ctSeries') }}</h3>
<el-table ref="ctSeries" :data="ctSeries" :show-header="false" style="width: 380px" size="mini" height="300"
:default-sort="{ prop: 'instanceCount', order: 'descending' }" @selection-change="handleCTSelectionChange"
@select="handleCTSelect">
<el-table-column type="selection" width="30" />
<el-table-column label="" width="100">
<template slot-scope="scope">
<el-image style="width: 60px; height: 60px"
:src="`${OSSclientConfig.basePath}${scope.row.ImageResizePath}`" fit="fit" crossorigin="anonymous" />
</template>
</el-table-column>
<el-table-column label="" min-width="50" show-overflow-tooltip>
<template slot-scope="scope">
<div>#{{ scope.row.SeriesNumber }} </div>
<div>{{ scope.row.Modality }}: {{ scope.row.InstanceCount }} images T: {{
parseFloat(scope.row.SliceThickness).toFixed(digitPlaces) }}</div>
<div>{{ scope.row.Description }}</div>
</template>
</el-table-column>
</el-table>
</div>
<div style="border: 1px solid #5e5e5e;border-radius: 5px;padding: 0 10px;">
<!-- PET序列 -->
<h3 style="color:#ddd">{{ $t('trials:lugano:fusionDialog:label:ptSeries') }}</h3>
<el-table ref="petSeries" :data="petSeries" :show-header="false" style="width: 380px" size="mini" height="300"
@selection-change="handlePTSelectionChange" @select="handlePTSelect">
<el-table-column type="selection" width="30" />
<el-table-column label="" width="100">
<template slot-scope="scope">
<el-image style="width: 60px; height: 60px"
:src="`${OSSclientConfig.basePath}${scope.row.ImageResizePath}`" fit="fit" crossorigin="anonymous" />
</template>
</el-table-column>
<el-table-column label="" min-width="50" show-overflow-tooltip>
<template slot-scope="scope">
<div>#{{ scope.row.SeriesNumber }} </div>
<div>{{ scope.row.Modality }}: {{ scope.row.InstanceCount }} images T: {{
parseFloat(scope.row.SliceThickness).toFixed(digitPlaces) }}</div>
<div>{{ scope.row.Description }}</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<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="handleSubmitFusionSeries">
{{ $t('common:button:confirm') }}</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'FusionForm',
props: {
taskList: {
type: Array,
default: () => {
return []
}
},
activeTaskIndex: {
type: Number,
default: 0
}
},
data() {
return {
fusionForm: {
visitTaskId: '',
studyId: '',
taskBlindName: '',
isBaseLineTask: null,
ctSeriesInfo: {},
petSeriesInfo: {},
defaultStudyId: '',
defaultCTSeriesId: '',
defaultPTSeriesId: ''
},
studyList: [],
ctSeries: [],
petSeries: [],
petctWindow: null,
digitPlaces: 2
}
},
mounted() {
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.initForm()
},
methods: {
initForm() {
if (this.taskList.length === 0) return
this.getStudyList(this.taskList[this.activeTaskIndex])
this.handleStudyIdChange(this.studyList[0].StudyId)
if (this.studyList.length > 0) {
this.fusionForm.studyId = this.studyList[0].StudyId
this.handleStudyIdChange(this.studyList[0].StudyId)
} else {
this.fusionForm.studyId = ''
this.ctSeries = []
this.petSeries = []
}
this.fusionForm.visitTaskId = this.taskList[this.activeTaskIndex].VisitTaskId
this.fusionForm.readingTaskState = this.taskList[this.activeTaskIndex].ReadingTaskState
this.fusionForm.taskBlindName = this.taskList[this.activeTaskIndex].TaskBlindName
this.fusionForm.subjectCode = this.taskList[this.activeTaskIndex].SubjectCode
this.fusionForm.isBaseLineTask = this.taskList[this.activeTaskIndex].IsBaseLineTask
this.fusionForm.isCurrentTask = this.taskList[this.activeTaskIndex].IsCurrentTask
},
getStudyList(obj) {
if (obj) {
var studyList = obj.StudyList || []
studyList = studyList.filter(i => !i.IsCriticalSequence && i.Modalities.indexOf('CT') !== -1 && i.Modalities.indexOf('PT') !== -1)
if (studyList.length === 0) return
this.studyList = studyList
}
},
handleSubmitFusionSeries() {
this.$refs['fusionForm'].validate((valid) => {
if (!valid) return
if (Object.keys(this.fusionForm.ctSeriesInfo).length === 0 || Object.keys(this.fusionForm.petSeriesInfo).length === 0) {
// ''
this.$confirm(this.$t('trials:lugano:fusionDialog:message:checkSeries'), this.$t('trials:lugano:fusionDialog:warning'), {
showCancelButton: false,
type: 'warning'
}).then(() => { }).catch(() => { })
return
}
var count = Math.abs(this.fusionForm.ctSeriesInfo.InstanceCount - this.fusionForm.petSeriesInfo.InstanceCount)
if (count > 10) {
// ', ?'
this.$confirm(this.$t('trials:lugano:fusionDialog:message:validSeries'), this.$t('trials:lugano:fusionDialog:warning'), {
type: 'warning'
}).then(() => {
this.fusion()
}).catch(() => { })
} else {
this.fusion()
}
})
},
fusion() {
this.$emit('fusion', { ct: this.fusionForm.ctSeriesInfo, pt: this.fusionForm.petSeriesInfo })
},
handleCancel() {
this.$emit('close')
},
handleTaskIdChange(v) {
var idx = this.taskList.findIndex(i => i.VisitTaskId === v)
this.studyList = idx > -1 ? this.getStudyList(this.taskList[idx]) : []
this.fusionForm.taskBlindName = idx > -1 ? this.taskList[idx].TaskBlindName : ''
this.fusionForm.isBaseLineTask = idx > -1 ? this.taskList[idx].IsBaseLineTask : null
this.studyList = idx > -1 ? this.taskList[idx].StudyList : null
this.fusionForm.studyId = ''
this.fusionForm.defaultStudyId = idx > -1 ? this.taskList[idx].StudyId : ''
this.fusionForm.defaultCTSeriesId = idx > -1 ? this.taskList[idx].CTSeriesId : ''
this.fusionForm.defaultPTSeriesId = idx > -1 ? this.taskList[idx].PTSeriesId : ''
this.ctSeries = []
this.petSeries = []
},
handleStudyIdChange(v) {
var i = this.studyList.findIndex(i => i.StudyId === v)
if (i === -1) {
this.ctSeries = []
this.petSeries = []
} else {
var seriesList = this.studyList[i].SeriesList
var series = seriesList.filter(series => series.Modality === 'CT')
this.ctSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
series = seriesList.filter(series => series.Modality === 'PT')
this.petSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
}
},
handleCTSelectionChange(rows) {
if (rows.length > 0) {
this.fusionForm.ctSeriesInfo = Object.assign({}, rows[0])
} else {
this.fusionForm.ctSeriesInfo = {}
}
},
handleCTSelect(selection) {
if (selection.length > 1) {
const del_row = selection.shift()
this.$refs.ctSeries.toggleRowSelection(del_row, false)
}
},
handlePTSelectionChange(rows) {
if (rows.length > 0) {
this.fusionForm.petSeriesInfo = Object.assign({}, rows[0])
} else {
this.fusionForm.petSeriesInfo = {}
}
},
handlePTSelect(selection) {
if (selection.length > 1) {
const del_row = selection.shift()
this.$refs.petSeries.toggleRowSelection(del_row, false)
}
}
}
}
</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>
@@ -0,0 +1,970 @@
<template>
<div ref="viewport-fusion" class="viewport-wrapper" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
@mouseleave="sliderMouseleave" :style="{ color: series.Modality === 'PT' || isMip ? '#666' : '#ddd' }">
<div v-if="series && taskInfo" class="left-top-text">
<div v-if="taskInfo.IsExistsClinicalData && !isMip && !isFusion" 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 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="!isMip && !isFusion">{{ series.Modality }}</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>{{ series.Description }}</div>
</div>
<div v-if="series" class="left-bottom-text">
<div v-show="mousePosition.index.length > 0 && !isMip">
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 && !isMip && !isFusion">
HU: {{ mousePosition.value }}
</div>
<div v-else-if="(series.Modality === 'PT' && mousePosition.value) && !isMip && !isFusion">
SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.value.toFixed(3) : mousePosition.value.toFixed(digitPlaces)
}}
</div>
<div v-else-if="mousePosition.value && !isMip && !isFusion">
Density: {{ mousePosition.value }}
</div>
<div v-show="imageInfo.size && !isMip && !isFusion">
W*H: {{ imageInfo.size }}
</div>
</div>
<div v-if="series && !isMip" class="right-bottom-text">
<div v-show="imageInfo.location && !isFusion">Location: {{ `${Number(imageInfo.location).toFixed(digitPlaces)} mm`
}}</div>
<div v-show="series.SliceThickness && !isFusion">Slice Thickness: {{
`${Number(series.SliceThickness).toFixed(digitPlaces)} mm`
}}</div>
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
</div>
<div class="orientation-top" v-if="!isMip">
{{ markers.top }}
</div>
<div class="orientation-right" v-if="!isMip">
{{ markers.right }}
</div>
<div class="orientation-bottom" v-if="!isMip">
{{ markers.bottom }}
</div>
<div class="orientation-left" v-if="!isMip">
{{ markers.left }}
</div>
<div v-if="isFusion" class="color_bar">
<canvas :id="`colorBar_Canvas_${viewportId}`" />
<!-- <div id="colorBar_Canvas" /> -->
</div>
<div v-if="isMip" 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 ref="sliderBox" class="right-slider-box" @click.stop="clickSlider($event)" v-show="!isMip">
<div :style="{ top: sliderInfo.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
@mousedown.stop="sliderMousedown($event)" />
</div>
</div>
</template>
<script>
// import * as cornerstonejs from '@cornerstonejs/core'
import {
// RenderingEngine,
Enums,
// imageLoader,
metaData,
getRenderingEngine,
volumeLoader,
// eventTarget,
utilities as csUtils,
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import { utilities as cstUtils } from '@cornerstonejs/tools';
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
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 setPetTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
const {
ToolGroupManager,
MIPJumpToClickTool,
VolumeRotateTool,
Enums: csToolsEnums,
} = cornerstoneTools
const { BlendModes, OrientationAxis } = Enums;
const { MouseBindings } = csToolsEnums
const { getColormap, getColormapNames } = csUtils.colormap;
const { ViewportColorbar } = cstUtils.voi.colorbar;
const { ColorbarRangeTextPosition } = cstUtils.voi.colorbar.Enums;
import { vec3, mat4 } from 'gl-matrix'
export default {
name: 'ImageViewport',
props: {
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
viewportIndex: {
type: Number,
required: true
},
},
data() {
return {
element: '',
series: {},
ctSeries: {},
petSeries: {},
isFusion: false,
isMip: false,
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
},
digitPlaces: 2,
orientationMarkers: [],
originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' },
playClipState: false,
wwwcIdx: 2,
presetName: '',
volumeId: null,
defaultWindowLevel: {},
Colorbar: null,
rotateAngle: 0,
rotateBarLeft: 0,
rotateBarInfo: {
initX: null,
initLeft: null,
isMove: false
},
ptVolumeId: null,
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.$nextTick(() => {
this.initViewport()
})
},
methods: {
initViewport() {
this.element = this.$refs['viewport-fusion']
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('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()
if (this.isMip) {
this.rotateBarMouseup()
}
})
document.addEventListener('mousemove', (e) => {
this.sliderMousemove(e)
if (this.isMip) {
this.rotateBarMousemove(e)
}
})
// console.log(cornerstoneTools)
// element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
},
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 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.$emit('renderAnnotations', this.series)
let properties = viewport.getProperties()
if (this.isFusion) {
properties = viewport.getProperties(this.ptVolumeId)
}
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)}`
}
// const toolGroupId = `viewport-${this.viewportIndex}`
// const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
// toolGroup.setToolEnabled('ScaleOverlay')
}
},
setFullScreen(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 (this.isFusion) {
properties = viewport.getProperties(this.volumeId)
}
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: false })
} 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.isFusion ? this.ptVolumeId : 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()
})
},
renderColorBar(presetName) {
var colorMap = null
this.Colorbar = null
if (presetName === 'hsv') {
colorMap = vtkColorMaps.getPresetByName(presetName)
} else {
colorMap = getColormap(presetName)
}
if (!colorMap) return false
// let colormaps = getColormapNames().map(name => getColormap(name))
// console.log(colormaps)
// const canvas = document.getElementById('colorBar_Canvas')
// this.Colorbar = new ViewportColorbar({
// id: this.volumeId,
// element: this.element,
// container: canvas,
// colormaps,
// activeColormapName: presetName,
// ticks: {
// position: ColorbarRangeTextPosition.Left,
// style: {
// font: '12px Arial',
// color: '#fff',
// maxNumTicks: 8,
// tickSize: 5,
// tickWidth: 1,
// labelMargin: 3,
// },
// },
// });
const rgbPoints = colorMap.RGBPoints
const canvas = document.getElementById(`colorBar_Canvas_${this.viewportId}`)
if (!canvas) return false
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)
},
setPreset(presetName) {
this.presetName = presetName
},
setColorMap(presetName) {
if (this.Colorbar) {
this.Colorbar.activeColormapName = presetName;
}
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
viewport.setProperties({ colormap: { name: presetName } }, this.volumeId)
viewport.render()
},
async prefetchMetadataInformation(imageIdsToPrefetch) {
let taskPromises = []
for (let i = 0; i < imageIdsToPrefetch.length; i++) {
taskPromises.push(cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]).promise)
if (taskPromises.length >= 6 || i === imageIdsToPrefetch.length - 1) {
await Promise.all(taskPromises)
taskPromises = []
}
}
},
async createImageIdsAndCacheMetaData(obj) {
await createImageIdsAndCacheMetaData({
modality: obj.Modality,
imageIds: obj.ImageIds
})
},
async setSeriesInfo(obj, option = {}) {
try {
let { data, volumeId } = obj
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description) {
data.SliceIndex = this.series.SliceIndex
}
this.ptVolumeId = null
this.series = {}
let { isFusion, isMip, colorMap } = option
this.isFusion = isFusion;
this.isMip = isMip;
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (this.isFusion) {
this.$nextTick(() => {
this.renderColorBar(this.presetName)
})
let { ct, data, ctVolumeId, ptVolumeId, fusionVolumeId } = obj
this.series = { ...data }
this.ctSeries = { ...ct }
this.petSeries = { ...data }
// const maxCount = Math.max(this.ctSeries.ImageIds.length, this.petSeries.ImageIds.length)
// this.$set(this.ctSeries, 'imageMaxLength', maxCount)
// this.$set(this.petSeries, 'imageMaxLength', maxCount)
// await this.createImageIdsAndCacheMetaData(this.ctSeries)
// await this.createImageIdsAndCacheMetaData(this.petSeries)
this.volumeId = ptVolumeId
this.ptVolumeId = fusionVolumeId
await viewport
.setVolumes([
{
volumeId: this.ptVolumeId, callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r)
console.log("融合pet渲染成功");
}
},
{
volumeId: ctVolumeId, callback: (r) => {
setCtTransferFunctionForVolumeActor(r)
console.log("融合ct渲染成功")
}
},
{
volumeId: this.volumeId, callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r)
console.log("融合pet渲染成功");
}
},
]).then(res => {
if (colorMap) {
this.setColorMap(this.presetName)
}
})
} else {
this.series = { ...data }
this.volumeId = volumeId
if (this.isMip) {
let { volume } = obj
const ptVolumeDimensions = volume.dimensions;
const slabThickness = Math.sqrt(
ptVolumeDimensions[0] * ptVolumeDimensions[0] +
ptVolumeDimensions[1] * ptVolumeDimensions[1] +
ptVolumeDimensions[2] * ptVolumeDimensions[2]
);
viewport
.setVolumes([{
volumeId: this.volumeId,
callback: (r) => {
setPetColorMapTransferFunctionForVolumeActor(r)
console.log("mip渲染成功")
},
slabThickness,
blendMode: BlendModes.MAXIMUM_INTENSITY_BLEND,
defaultOptions: {
orientation: OrientationAxis.CORONAL
}
}]).then(res => {
if (colorMap) {
this.setColorMap(this.presetName)
}
})
} else {
viewport
.setVolumes([{
volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT') {
setPetColorMapTransferFunctionForVolumeActor(r, true)
} else {
setCtTransferFunctionForVolumeActor(r)
}
console.log("渲染成功")
}
}]).then(res => {
if (colorMap) {
this.setColorMap(this.presetName)
}
})
}
}
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)
const imageData = viewport.getImageData()
if (!imageData) return
const index = imageData.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
},
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.series.Stack.length * height / 100)
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : 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
},
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.series.Stack.length * height / 100)
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 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
},
handletoolsMouseWheel(e) {
const { viewportId, wheel } = e.detail
if (this.isMip) {
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
}
}
},
rotateBarMouseup(e) {
this.rotateBarInfo.isMove = false
},
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) {
this.rotateBarInfo.initLeft = e.srcElement.offsetLeft
this.rotateBarInfo.initX = e.clientX
this.rotateBarInfo.isMove = true
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
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()
}
}
}
</script>
<style lang="scss" scoped>
.viewport-wrapper {
width: 100%;
height: 100%;
position: relative;
.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>
File diff suppressed because it is too large Load Diff
@@ -208,6 +208,8 @@
:question-form="questionForm"
:reading-task-state="readingTaskState"
:visitTaskId="visitTaskId"
@resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData"
/>
</div>
</template>
@@ -266,6 +268,7 @@ export default {
},
methods: {
formItemChange(v, question) {
console.log('formItemChange')
if (question.Childrens.length > 0) {
this.resetChild(question.Childrens)
} else {
@@ -315,4 +318,8 @@ export default {
::v-deep .el-select.is-disabled .el-input__inner{
background-color: #646464a1;
}
// ::v-deep .el-radio__input.is-disabled.is-checked .el-radio__inner {
// background-color: #428bca;
// border-color: #428bca;
// }
</style>
File diff suppressed because it is too large Load Diff
@@ -245,7 +245,7 @@
:title="additionalAssessmentsDig.title"
width="600px"
>
<AdditionalAssessment
<additional-assessment
:questions="assessmentQuestions"
:visit-task-id="visitTaskId"
@close="additionalAssessmentsDig.visible = false"
@@ -440,7 +440,7 @@ export default {
} else {
filterArr = [3, 4, 5, 6, 2, 8, 10, 7, 1106, 1105]
}
if ((lesionType === 0 || lesionType === 5) && isLymphNodes === 0 && !this.isShowDetail && (this.criterionType === 1 || this.criterionType === 3 || this.criterionType === 17)) {
if ((lesionType === 0 || lesionType === 5) && isLymphNodes === 0 && !this.isShowDetail && (this.criterionType === 1 || this.criterionType === 3 || this.criterionType === 7 || this.criterionType === 17)) {
filterArr.push(1)
}
@@ -502,7 +502,7 @@ export default {
obj.QuestionName = `${obj.QuestionName} (${item.ReportMark})`
}
if (this.criterionType === 1 || this.criterionType === 3 || this.criterionType === 17) {
if (this.criterionType === 1 || this.criterionType === 3 || this.criterionType === 7 || this.criterionType === 17) {
var idxLymphNode = item.Childrens.findIndex(i => i.QuestionMark === 2)
if (idxLymphNode > -1) {
isLymphNodes = item.Childrens[idxLymphNode].Answer[k].Answer ? parseInt(item.Childrens[idxLymphNode].Answer[k].Answer) : null
@@ -673,7 +673,7 @@ export default {
this.signVisible = false
// window.location.reload()
// window.opener.postMessage('refreshTaskList', window.location)
window.opener.postMessage('refreshTaskList', window.location)
//
this.readingTaskState = 2
@@ -65,8 +65,34 @@
/>
</div>
<div class="series-text">
<div v-if="series.IsExistMutiFrames && series.InstanceCount > 1"
style="position: absolute;right: 0;top: 0;">
<el-popover
placement="right"
trigger="hover"
popper-class="instance_frame_wrapper"
>
<div class="frame_list">
<div
v-for="(instance, idx) in series.InstanceInfoList"
:key="instance.Id"
class="frame_content"
:style="{'margin-bottom':idx<series.InstanceInfoList.length-1? '5px':'0px'}"
@click.stop="showMultiFrames(index,series, i, instance)"
>
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ? instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
</el-popover>
</div>
<div v-if="!study.IsCriticalSequence" class="text-desc" :title="series.SeriesNumber">
#{{ series.SeriesNumber }}
</div>
<div v-if="series.Description" class="text-desc" :title="series.Description">
{{ series.Description }}
@@ -85,7 +111,7 @@
</div>
</div>
</div>
<div v-if="series.IsDicom && series.LoadedImageProgress>0 && series.LoadedImageProgress<series.InstanceCount * 100" style="width: 100%;">
<div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount" style="width: 100%;">
<el-progress
:percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))"
/>
@@ -154,6 +180,31 @@ export default {
if (!studyId) return
this.activeStudy(studyId)
},
showMultiFrames(studyIndex, series, seriesIndex, instance) {
let obj = Object.assign({}, series)
this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex
let taskId = this.visitTaskInfo.VisitTaskId
const nFrames = instance.NumberOfFrames || 0
let imageIds = []
const stack = []
if (nFrames === 0) {
//
stack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
} else {
//
for (let i = 0; i < nFrames; i++) {
const newImageId = `wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&frame=${i + 1}`
stack.push(newImageId)
}
}
imageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
obj.Stack = stack
obj.ImageIds = imageIds
obj.SliceIndex = 0
this.$emit('showMultiFrame', obj)
},
getPreviousOrNextSeries(type, series) {
const seriseList = this.studyList.map(s => s.SeriesList).flat()
const i = seriseList.findIndex(i => i.Id === series.Id && i.StudyId === series.StudyId)
@@ -242,6 +293,7 @@ export default {
flex: 1;
padding-left: 5px;
color: #ddd;
position: relative;
.text-desc {
width: 100px;
white-space: nowrap;
@@ -26,7 +26,7 @@
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
</h2>
<div>Series: #{{ series.SeriesNumber }}</div>
<div>Image: #{{ `${series.SliceIndex + 1}/${series.ImageIds.length}` }}</div>
<div v-if="series.Stack">Image: #{{ `${series.SliceIndex + 1}/${series.Stack.length}` }}</div>
<div>{{ series.Modality }}</div>
</div>
<div
@@ -60,13 +60,13 @@
</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] }}
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}
</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) }}
{{mousePosition.modalityUnit}}: {{ digitPlaces === -1 ?mousePosition.value.toFixed(3) :mousePosition.value.toFixed(digitPlaces) }}
</div>
<div v-else-if="mousePosition.value">
Density: {{ mousePosition.value }}
@@ -74,6 +74,9 @@
<div v-show="imageInfo.size">
W*H: {{ imageInfo.size }}
</div>
<div v-show="imageInfo.zoom">
Zoom: {{ imageInfo.zoom }}
</div>
</div>
<div v-if="series" class="right-bottom-text">
<div v-show="imageInfo.location">Location: {{ `${Number(imageInfo.location).toFixed(digitPlaces)} mm` }}</div>
@@ -110,9 +113,13 @@ import {
utilities as csUtils
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
ToolGroupManager
} = cornerstoneTools
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
import {
utilities as csToolsUtils
} from '@cornerstonejs/tools'
import { getPTImageIdInstanceMetadata } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/getPTImageIdInstanceMetadata'
import ptScalingMetaDataProvider from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/ptScalingMetaDataProvider'
import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv'
import { vec3 } from 'gl-matrix'
export default {
name: 'ImageViewport',
@@ -182,11 +189,14 @@ export default {
this.element.oncontextmenu = (e) => e.preventDefault()
resizeObserver.observe(this.element)
this.element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
// this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
// this.element.addEventListener('CORNERSTONE_CAMERA_MODIFIED', this.cameraModified)
this.element.addEventListener('CORNERSTONE_IMAGE_RENDERED', this.imageRendered)
this.element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.cornerstoneToolsMouseMove)
this.element.addEventListener('mouseleave', () => {
this.mousePosition.index = []
this.mousePosition.value = null
})
// console.log(cornerstoneTools)
// element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
@@ -194,7 +204,7 @@ export default {
stackNewImage(e) {
const { detail } = e
this.series.SliceIndex = detail.imageIdIndex
this.sliderInfo.height = detail.imageIdIndex * 100 / (this.series.ImageIds.length - 1)
this.sliderInfo.height = detail.imageIdIndex * 100 / (this.series.Stack.length - 1)
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const zoom = viewport.getZoom()
@@ -207,7 +217,25 @@ export default {
this.$emit('renderAnnotations', this.series)
// const toolGroupId = `viewport-${this.viewportIndex}`
// const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
// toolGroup.setToolEnabled('ScaleOverlay');
// toolGroup.setToolEnabled('ScaleOverlay')
},
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)
},
cameraModified(e) {
console.log(e)
},
voiModified(e) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -221,6 +249,8 @@ export default {
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
const zoom = viewport.getZoom()
this.imageInfo.zoom = zoom.toFixed(4)
},
getOrientationMarker() {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -254,7 +284,6 @@ export default {
},
resetOrientationMarkers() {
if (this.originalMarkers.length > 0) {
console.log(this.originalMarkers)
this.orientationMarkers = [...this.originalMarkers]
this.setMarkers()
}
@@ -310,7 +339,9 @@ export default {
} else if (type === 99999) {
newImageIdIndex = numImages - 1
}
viewport.setImageIdIndex(newImageIdIndex)
// viewport.setImageIdIndex(newImageIdIndex)
csUtils.jumpToSlice(viewport.element, { imageIndex: newImageIdIndex })
cornerstoneTools.utilities.cine.stopClip(viewport.element)
},
setZoom(ratio) {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -327,31 +358,108 @@ export default {
resize(forceFitToWindow) {
console.log('resize: ', forceFitToWindow)
const renderingEngine = getRenderingEngine(this.renderingEngineId)
renderingEngine.resize(true, forceFitToWindow)
const viewport = renderingEngine.getViewport(this.viewportId)
if (!forceFitToWindow) {
viewport.setZoom(0.5)
viewport.render()
} else {
viewport.setZoom(1)
viewport.render()
}
},
async prefetchMetadataInformation(imageIdsToPrefetch, modality) {
let taskPromises = []
for (let i = 0; i < imageIdsToPrefetch.length; i++) {
taskPromises.push(cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]).promise)
if (taskPromises.length >= 6 || i === imageIdsToPrefetch.length - 1) {
let res = await Promise.all(taskPromises)
if (modality === 'PT') {
this.cachePTMetadata(res)
}
taskPromises = []
}
}
},
cachePTMetadata(images) {
images.map(i=>{
const instanceMetadata = getPTImageIdInstanceMetadata(i.imageId)
if (typeof instanceMetadata.CorrectedImage === 'string') {
instanceMetadata.CorrectedImage = instanceMetadata.CorrectedImage.split('\\')
}
if (instanceMetadata) {
const suvScalingFactors = calculateSUVScalingFactors([instanceMetadata])
ptScalingMetaDataProvider.addInstance(i.imageId, suvScalingFactors[0])
}
})
},
async setSeriesInfo(obj, isLocate = false) {
if (this.series && obj.Id === this.series.Id && obj.Description === this.series.Description && !isLocate) {
obj.SliceIndex = this.series.SliceIndex
try {
if (this.series && obj.Id === this.series.Id && obj.Description === this.series.Description && !isLocate) {
obj.SliceIndex = this.series.SliceIndex
}
// if (isLocate && obj.SliceIndex === this.series.SliceIndex) return
if (this.series?.Stack && obj.Stack[obj.SliceIndex] === this.series.Stack[this.series.SliceIndex]) return
this.series = { ...obj }
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const image = await cornerstoneDICOMImageLoader.wadouri.loadImage(obj.ImageIds[obj.SliceIndex]).promise
if (obj.Modality === 'PT') {
this.cachePTMetadata([image])
}
this.prefetchMetadataInformation(obj.ImageIds, obj.Modality)
await viewport.setStack(this.series.Stack, obj.SliceIndex)
viewport.render()
} catch (e) {
console.log(e)
}
this.series = { ...obj }
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
await viewport.setStack(obj.ImageIds, obj.SliceIndex)
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element)
viewport.render()
},
cornerstoneToolsMouseMove(e) {
const { currentPoints } = e.detail
const worldPoint = currentPoints.world
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
const imageData = viewport.getImageData()
if (!imageData) return
const index = imageData.imageData.worldToIndex(worldPoint)
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 = csToolsUtils.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: csToolsUtils.viewport.isViewportPreScaled(viewport, referencedImageId),
isSuvScaled: isSuvScaled,
}
modalityUnit = csToolsUtils.getPixelValueUnits(
modality,
referencedImageId,
pixelUnitsOptions
);
}
this.mousePosition.value = value
this.mousePosition.modalityUnit = modalityUnit
}
},
toggleTask(evt, visitTaskNum, i) {
this.$emit('activeViewport', this.viewportIndex)
@@ -373,13 +481,14 @@ export default {
clickSlider(e) {
const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
this.sliderInfo.height = height
let sliceIdx = Math.trunc(this.series.ImageIds.length * height / 100)
sliceIdx = sliceIdx >= this.series.ImageIds.length ? this.series.ImageIds.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100)
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(
this.viewportId
)
viewport.setImageIdIndex(sliceIdx)
// viewport.setImageIdIndex(sliceIdx)
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
viewport.render()
},
sliderMouseup(e) {
@@ -401,14 +510,15 @@ export default {
if (delta < 0) return
if (delta > boxHeight) return
const height = delta * 100 / boxHeight
let sliceIdx = Math.trunc(this.series.ImageIds.length * height / 100)
sliceIdx = sliceIdx >= this.series.ImageIds.length ? this.series.ImageIds.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100)
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
this.sliderInfo.height = height
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(
this.viewportId
)
viewport.setImageIdIndex(sliceIdx)
// viewport.setImageIdIndex(sliceIdx)
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
viewport.render()
},
sliderMouseleave(e) {
@@ -1,16 +1,9 @@
<template>
<div class="visit-review-container">
<el-tabs
v-model="activeName"
>
<el-tabs v-model="activeName">
<!-- 阅片 -->
<el-tab-pane
v-if="taskInfo"
ref="readPage"
:label="$t('trials:reading:tabTitle:review')"
name="read"
>
<read-page />
<el-tab-pane v-if="taskInfo" :label="$t('trials:reading:tabTitle:review')" name="read">
<read-page ref="readPage" :reading-tool="readingTool" />
</el-tab-pane>
<!-- 报告 -->
<el-tab-pane
@@ -18,8 +11,12 @@
:label="$t('trials:reading:tabTitle:report')"
name="report"
>
<report-page
v-if="activeName === 'report'"
<report-page
v-if="activeName === 'report' && taskInfo.CriterionType !== 0"
@setReadingTaskState="setReadingTaskState"
/>
<customize-report-page
v-if="activeName === 'report' && taskInfo.CriterionType === 0"
@setReadingTaskState="setReadingTaskState"
/>
</el-tab-pane>
@@ -29,11 +26,19 @@
<script>
import ReadPage from './ReadPage'
import ReportPage from './ReportPage'
import CustomizeReportPage from './customize/ReportPage'
export default {
name: 'VisitReview',
components: {
ReadPage,
ReportPage
ReportPage,
CustomizeReportPage
},
props: {
readingTool: {
type: Number,
default: 0
}
},
data() {
return {
@@ -42,6 +47,7 @@ export default {
}
},
mounted() {
console.log(this.readingTool)
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
},
methods: {
@@ -52,37 +58,44 @@ export default {
}
</script>
<style lang="scss" scoped>
.visit-review-container {
height: 100vh;
.visit-review-container {
height: 100%;
display: flex;
flex-direction: column;
background-color: #000;
padding: 5px;
::v-deep .el-tabs {
height: 100%;
display: flex;
flex-direction: column;
background-color: #000;
padding: 5px;
::v-deep .el-tabs {
height: 100%;
display: flex;
flex-direction: column;
.el-tabs__item {
color: #fff;
}
.el-tabs__item.is-active {
color: #428bca;
}
.el-tabs__item:hover {
color: #428bca;
}
.el-tabs__header {
height: 50px;
margin:0px;
}
.el-tabs__content {
flex: 1;
margin:0px;
}
.el-tab-pane {
height: 100%;
}
.el-tabs__item {
color: #fff;
}
.el-tabs__item.is-active {
color: #428bca;
}
.el-tabs__item:hover {
color: #428bca;
}
.el-tabs__header {
height: 50px;
margin: 0px;
}
.el-tabs__content {
flex: 1;
margin: 0px;
}
.el-tab-pane {
height: 100%;
}
}
}
</style>
@@ -0,0 +1,325 @@
<template>
<div class="colorMap">
<div class="item" effect="dark" :title="$t('trials:lugano:button:colormap')">
<div class="colorBar"
style="display:flex;justify-content: flex-start;align-items: center;position: relative;"
@mouseleave="isSlideMoving = false">
<div class="tool-wrapper" style="margin-right:0px" @click.stop="showColorBarPanel($event)"
@mouseleave="handleColorBarMouseout">
<div>
<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>
</div>
</div>
</template>
<script>
import {
utilities as csUtils,
} from '@cornerstonejs/core'
import colormaps from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/colormaps'
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
const { registerColormap, getColormapNames, getColormap } = csUtils.colormap
export default {
name: "colorMap",
data() {
return {
colorMaps: [],
rgbPresetName: 'siemens',
range: 40,
upper: 6,
}
},
mounted() {
colormaps.forEach(colormap => {
registerColormap(colormap)
})
this.colorMaps = getColormapNames()
this.colorMaps.unshift('hsv')
this.$nextTick(() => {
this.renderColorMaps()
this.upperRangeChange(this.range)
this.initSlider()
})
},
methods: {
renderColorMaps() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.colorMaps.forEach((e, index) => {
this.createColorBar(e, `colorBarCanvas${index}`, 180, 15)
})
},
voiChange(v) {
this.$emit('voiChange', v)
},
initSlider() {
var slider = document.getElementById('slider')
var sliderBox = document.getElementById('sliderBox')
var container = document.getElementById('colorBarCanvas')
slider.addEventListener('mousedown', () => {
this.isSlideMoving = true
})
document.addEventListener('mousemove', (e) => {
if (this.isSlideMoving) {
var containerWidth = container.clientWidth
var sliderWidth = sliderBox.clientWidth
var maxLeft = containerWidth - sliderWidth
var left = e.clientX - container.getBoundingClientRect().left
var position = null
position = left
if (left < 0) {
left = 6
position = 0
} else if (left > maxLeft) {
left = maxLeft + 6
position = maxLeft
}
slider.style.left = left + 'px'
var positionValue = document.getElementById('slider-position')
var upper = this.range
position = parseInt((position / maxLeft) * upper)
positionValue.textContent = position
this.upper = position
this.voiChange(position)
}
})
document.addEventListener('mouseup', () => {
this.isSlideMoving = false
})
},
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
},
createColorBar(rgbPresetName, elId, width, height) {
var colorMap = null
if (rgbPresetName === 'hsv') {
colorMap = vtkColorMaps.getPresetByName(rgbPresetName)
} else {
colorMap = getColormap(rgbPresetName)
}
const rgbPoints = colorMap.RGBPoints
const canvas = document.getElementById(elId)
const ctx = canvas.getContext('2d')
const canvasWidth = width
const canvasHeight = height
const rectWidth = width
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)
},
async setColorMap(rgbPresetName) {
this.rgbPresetName = rgbPresetName
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.$emit("setColorMap", rgbPresetName)
},
showColorBarPanel(e) {
e.currentTarget.firstChild.firstChild.lastChild.style.display = 'block'
},
handleColorBarMouseout(e) {
e.currentTarget.firstChild.firstChild.lastChild.style.display = 'none'
},
init() {
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
this.$emit("setColorMap", this.rgbPresetName)
}
}
}
</script>
<style lang="scss" scoped>
.activeLi {
background-color: #919295;
}
.tool-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 30px;
.icon {
padding: 5px;
border: 1px solid #404040;
cursor: pointer;
text-align: center;
.svg-icon {
font-size: 25px;
color: #ddd;
}
}
.text {
position: relative;
font-size: 12px;
margin-top: 5px;
color: #d0d0d0;
display: none;
}
}
.dropdown {
position: relative;
display: inline-block;
.icon-content {
display: flex;
align-items: center;
border: 1px solid #404040;
}
.text {
text-align: center;
}
.tool-icon {
padding: 5px;
cursor: pointer;
text-align: center;
.svg-icon {
font-size: 20px;
color: #ddd;
}
}
.arrow-icon {
cursor: pointer;
padding: 7px 2px 7px 0px;
}
.arrow-icon:hover {
background-color: #607d8b;
}
.icon-content-d:hover {
background-color: #607d8b;
}
.tool-icon-d {
padding: 5px;
.svg-icon {
font-size: 20px;
color: #ddd;
}
}
}
.dropdown-content {
display: none;
position: absolute;
background-color: #383838;
color: #fff;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 9999;
font-size: 12px;
ul {
list-style: none;
margin: 0;
padding: 0;
text-align: center;
li {
a {
display: block;
padding: 5px;
}
}
}
ul li:hover {
background-color: #727272;
cursor: pointer;
}
}
.item {
box-sizing: border-box;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.21);
position: relative;
&_active {
// border: 2px solid #ffeb3b;fff
border: 1px dashed rgb(250, 250, 0);
}
}
.colorBar {
::v-deep .el-input--mini .el-input__inner {
height: 25px;
line-height: 25px;
border: none;
background-color: transparent;
color: #ddd;
}
}
</style>
@@ -0,0 +1,438 @@
<template>
<div v-loading="loading" class="questionList-wrapper">
<div class="container">
<div class="basic-info">
<h3 v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo">
<span v-if="visitInfo.SubjectCode">{{ visitInfo.SubjectCode }} </span>
<span style="margin-left:5px;">{{ visitInfo.TaskBlindName }}</span>
</h3>
<div v-if="readingTaskState < 2">
<el-tooltip class="item" effect="dark" :content="$t('trials:dicomReading:message:confirmReset')" placement="bottom">
<i
class="el-icon-refresh-left"
@click="resetForm"
/>
</el-tooltip>
</div>
</div>
<el-form
v-if="taskInfo && rerender"
ref="questions"
size="small"
:model="questionForm"
class="ecrf-form"
>
<template v-if="rerender">
<QuestionFormItem
v-for="question of questions"
:key="question.Id"
:visit-task-id="visitTaskId"
:question="question"
:question-form="questionForm"
:reading-task-state="readingTaskState"
:criterion-id="criterionId"
:calculation-list="calculationList"
:is-baseline="isBaseLineTask"
@resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData"
/>
</template>
<el-form-item v-if="readingTaskState < 2">
<div style="text-align:center;">
<el-button v-if="taskInfo && taskInfo.IseCRFShowInDicomReading" type="primary" @click="skipTask">
{{ $t('trials:readingReport:button:skip') }}
</el-button>
<el-button type="primary" @click="handleSave">
{{ $t('common:button:save') }}
</el-button>
<el-button v-if="taskInfo && taskInfo.IseCRFShowInDicomReading" type="primary" @click="handleSubmit">
{{ $t('common:button:submit') }}
</el-button>
</div>
</el-form-item>
</el-form>
</div>
<!-- 签名框 -->
<el-dialog
v-if="signVisible"
:visible.sync="signVisible"
:close-on-click-modal="false"
width="600px"
custom-class="base-dialog-wrapper"
>
<div slot="title">
<span style="font-size:18px;">{{ $t('common:dialogTitle:sign') }}</span>
<span style="font-size:12px;margin-left:5px">{{ `(${$t('common:label:sign')}${ currentUser })` }}</span>
</div>
<SignForm ref="signForm" :sign-code-enum="signCode" @closeDialog="closeSignDialog" />
</el-dialog>
</div>
</template>
<script>
import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, getQuestionCalculateRelation } from '@/api/trials'
import { setSkipReadingCache, resetReadingTask } from '@/api/reading'
import const_ from '@/const/sign-code'
import QuestionFormItem from '@/views/trials/trials-panel/reading/dicoms/customize/CustomizeQuestionFormItem'
import SignForm from '@/views/trials/components/newSignForm'
export default {
name: 'EcrfList',
components: {
QuestionFormItem,
SignForm
},
props: {
visitInfo: {
type: Object,
required: true
},
readingTaskState: {
type: Number,
required: true
},
},
data() {
return {
trialId: '',
criterionId: '',
visitTaskId: '',
loading: false,
questions: [],
questionForm: {},
publicQuestions: [],
signVisible: false,
signCode: null,
currentUser: zzSessionStorage.getItem('userName'),
activeName: 0,
classArr: [],
calculationList: [],
taskInfo: null,
isBaseLineTask: false,
rerender: true
}
},
mounted() {
this.trialId = this.$route.query.trialId
this.visitTaskId = this.visitInfo.VisitTaskId
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.isBaseLineTask = this.taskInfo.IsBaseLine
this.criterionType = this.taskInfo.CriterionType
this.getQuestionCalculateRelation()
this.getQuestions()
},
methods: {
async getQuestions() {
this.loading = true
try {
const param = {
visitTaskId: this.visitTaskId
}
const res = await getCustomTableQuestionAnswer(param)
if (res.IsSuccess) {
// this.readingTaskState = res.OtherInfo.ReadingTaskState
res.Result.SinglePage.map((v) => {
if (v.Type === 'group' && v.Childrens.length === 0) return
if (!v.IsPage && v.Type !== 'group' && v.Type !== 'summary' && v.Type !== 'table' && v.Type !== 'basicTable' && v.Type !== 'number') {
this.$set(this.questionForm, v.Id, v.Answer)
}
if (v.Type === 'table' || v.Type === 'basicTable') {
this.$set(this.questionForm, v.Id, v.TableQuestions.Answers)
}
if (v.Type === 'class') {
this.classArr.push({ triggerId: v.ClassifyQuestionId, classId: v.Id, classifyAlgorithms: v.ClassifyAlgorithms, classifyType: v.ClassifyType })
}
if (v.Type === 'number') {
this.$set(this.questionForm, v.Id, v.Answer === '' ? '' : parseFloat(v.Answer).toFixed(this.digitPlaces))
}
if (v.Childrens.length > 0) {
this.setChild(v.Childrens)
}
})
this.questions = res.Result.SinglePage
this.loading = false
}
} catch (e) {
console.log(e)
this.loading = false
}
},
setChild(obj) {
obj.forEach(i => {
if (i.Type !== 'group' && i.Type !== 'summary' && i.Id && i.Type !== 'table' && i.Type !== 'basicTable') {
this.$set(this.questionForm, i.Id, i.Answer)
}
if (i.Type === 'table' || i.Type === 'basicTable') {
i.TableQuestions.Questions.forEach(o => {
if (o.Type === 'number') {
i.TableQuestions.Answers.forEach((ite, index) => {
this.$set(i.TableQuestions.Answers[index], o.Id, i.TableQuestions.Answers[index][o.Id] ? parseFloat(i.TableQuestions.Answers[index][o.Id]).toFixed(this.digitPlaces) : null)
})
}
})
this.$set(this.questionForm, i.Id, i.TableQuestions.Answers)
}
if (i.Type === 'class') {
this.classArr.push({ triggerId: i.ClassifyQuestionId, classId: i.Id, classifyAlgorithms: i.ClassifyAlgorithms, classifyType: i.ClassifyType })
}
if (i.Type === 'number') {
this.$set(this.questionForm, i.Id, i.Answer === '' ? '' : parseFloat(i.Answer).toFixed(this.digitPlaces))
}
if (i.Childrens && i.Childrens.length > 0) {
this.setChild(i.Childrens)
}
})
},
async getQuestionCalculateRelation() {
try {
const res = await getQuestionCalculateRelation({ TrialReadingCriterionId: this.criterionId })
this.calculationList = res.Result
} catch (e) {
console.log(e)
}
},
async handleSave(isMsg) {
const valid = await this.$refs['questions'].validate()
if (!valid) return
this.loading = true
try {
var answers = []
for (const k in this.questionForm) {
if (this.questionForm[k] instanceof Array) {} else {
answers.push({ id: k, answer: this.questionForm[k] })
}
}
var params = {
visitTaskId: this.visitTaskId,
answers: answers
}
const res = await changeDicomReadingQuestionAnswer(params)
if (res.IsSuccess) {
if (isMsg) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.loading = false
}
} catch (e) {
this.loading = false
}
},
async handleSubmit() {
const valid = await this.$refs['questions'].validate()
if (!valid) return
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
},
//
closeSignDialog(isSign, signInfo) {
if (isSign) {
this.signConfirm(signInfo)
} else {
this.signVisible = false
}
},
//
async signConfirm(signInfo) {
this.loading = true
var answers = []
for (const k in this.questionForm) {
answers.push({ readingQuestionTrialId: k, answer: this.questionForm[k] })
}
var params = {
data: {
trialId: this.trialId,
visitTaskId: this.visitTaskId,
readingQuestionCriterionTrialId: this.criterionId,
answerList: answers
},
signInfo: signInfo
}
try {
const res = await submitVisitTaskQuestionsInDto(params)
this.loading = false
if (res.IsSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
this.isEdit = false
this.$refs['signForm'].btnLoading = false
this.signVisible = false
this.$emit('setReadingTaskState', 2)
window.opener.postMessage('refreshTaskList', window.location)
const confirm = await this.$confirm(
this.$t('trials:noneDicoms:message:msg1'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
window.location.reload()
}
} catch (e) {
this.loading = false
this.$refs['signForm'].btnLoading = false
}
},
async skipTask() {
try {
//
const confirm = await this.$confirm(
this.$t('trials:readingReport:message:skipConfirm'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
this.loading = true
const res = await setSkipReadingCache({ visitTaskId: this.visitTaskId })
this.loading = false
if (res.IsSuccess) {
window.location.reload()
}
} catch (e) {
this.loading = false
console.log(e)
}
},
resetFormItemData(v) {
this.questionForm[v] = null
},
setFormItemData(obj) {
this.$set(this.questionForm, obj.key, JSON.parse(JSON.stringify(obj.val)))
this.classArr.map(i => {
if (i.triggerId === obj.key) {
let answer = null
const list = JSON.parse(i.classifyAlgorithms)
if (i.classifyType === 0) {
const o = list.find(v => {
return (
parseFloat(obj.val) >= parseFloat(v.gt) &&
parseFloat(obj.val) < parseFloat(v.lt)
)
})
answer = o ? o.label : null
} else if (i.classifyType === 1) {
const o = list.find(v => {
return v.val.includes(obj.val)
})
answer = o ? o.label : null
}
this.$set(this.questionForm, i.classId, answer)
}
})
},
async resetForm() {
const confirm = await this.$confirm(
this.$t('trials:dicomReading:message:confirmReset1'),
this.$t('trials:dicomReading:message:confirmReset2'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
const loading = this.$loading({ fullscreen: true })
try {
const res = await resetReadingTask({ visitTaskId: this.visitTaskId })
if (res.IsSuccess) {
this.rerender = false
await this.getQuestionCalculateRelation()
await this.getQuestions()
this.$emit('resetAnnotations', this.visitTaskId)
this.$nextTick(()=>{
this.rerender = true
})
}
loading.close()
} catch (e) {
loading.close()
console.log(e)
}
},
}
}
</script>
<style lang="scss" scoped>
.questionList-wrapper{
height: 100%;
overflow-y: auto;
.container{
padding: 10px;
.basic-info{
display: flex;
justify-content: space-between;
align-items: center;
h3{
color: #ddd;
padding: 5px 0px;
margin: 0;
}
i{
color: #fff;
font-size: 22px;
font-weight: bold;
cursor: pointer;
}
}
.ecrf-form{
::v-deep .el-form-item__label{
color: #ddd;
}
}
::v-deep .el-table,
.el-table__expanded-cell {
background-color: #000;
color: #fff;
border-color: #444444;
}
::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-dialog{
background: #1e1e1e;
border: 1px solid #ddd;
color: #ddd;
.el-dialog__title{
color:#fff;
}
.el-input .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
.el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
.el-form-item__label{
color: #dfdfdf
}
}
}
}
// .ecrf-list-container{
// min-height:400px;
// color: #ddd;
// }
</style>
@@ -0,0 +1,855 @@
<template>
<div class="report-wrapper">
<el-card v-loading="loading" shadow="never" style="display:flex;flex-direction: column;">
<div slot="header" class="clearfix report-header">
<h3 style="margin:0;padding:0;">{{ $t('trials:readingReport:title:eicrf') }}</h3>
<div style="margin-left:auto">
<el-button
v-if="readingTaskState<2"
type="primary"
size="small"
@click="skipTask"
>
<!-- 跳过 -->
{{ $t('trials:readingReport:button:skip') }}
</el-button>
<el-button v-if="readingTaskState<2" type="primary" size="small" @click="getReportInfo">{{ $t('trials:readingReport:button:refresh') }}</el-button>
<el-button v-if="readingTaskState<2" type="primary" size="small" @click="handleSave(true)">{{ $t('common:button:save') }}</el-button>
<el-button v-if="readingTaskState<2" type="primary" size="small" @click="handleConfirm">{{ $t('common:button:submit') }}</el-button>
</div>
</div>
<div style="flex: 1">
<el-table
v-if="taskQuestions.length > 0"
ref="reportList"
v-adaptive="{bottomOffset:0}"
:data="taskQuestions"
row-key="Id"
border
default-expand-all
height="100"
:tree-props="{children: 'Childrens', hasChildren: 'hasChildren'}"
size="mini"
>
<el-table-column
prop=""
label=""
show-overflow-tooltip
width="350px"
>
<template slot-scope="scope">
<span v-if="scope.row.QuestionName">{{ scope.row.BlindName ? scope.row.QuestionName : scope.row.QuestionName }}</span>
<span
v-else
style="font-weight: bold;font-size: 16px;color: #f44336;"
>
{{ scope.row.GroupName }}
</span>
</template>
</el-table-column>
<el-table-column
v-for="task in visitTaskList"
:key="task.VisitTaskId"
prop="date"
show-overflow-tooltip
width="200px"
>
<template slot="header">
<div v-if="task.IsCurrentTask">
{{ task.BlindName }}
</div>
<div v-else>
<div>
{{ task.BlindName }}
<el-button type="text" size="small" @click="previewDicoms(task)">
<span class="el-icon-view" />
</el-button>
</div>
</div>
</template>
<template slot-scope="scope">
<template v-if="readingTaskState<2 && task.VisitTaskId === visitTaskId && (scope.row.Type==='input' || scope.row.Type==='number' || scope.row.Type==='select' || scope.row.Type==='textarea' || scope.row.Type==='radio')">
<!-- 输入框 -->
<div>
<template v-if="!((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)" />
<el-input
v-else-if="questionForm[scope.row.QuestionId] instanceof Array && (scope.row.Type==='input' || scope.row.Type==='textarea') && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]"
size="mini"
/>
<span v-else-if="questionForm[scope.row.QuestionId] instanceof Array && (scope.row.Type==='input' || scope.row.Type==='textarea')">
{{ questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId] }}
</span>
<el-input
v-else-if="(scope.row.Type==='input' || scope.row.Type==='textarea') && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId]"
size="mini"
/>
<span v-else-if="scope.row.Type==='input' || scope.row.Type==='textarea'">
{{ questionForm[scope.row.QuestionId] }}
</span>
<el-select
v-else-if="questionForm[scope.row.QuestionId] instanceof Array && (scope.row.Type==='select' || scope.row.Type==='radio') && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]"
size="mini"
clearable
>
<template>
<el-option
v-for="val in scope.row.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<span v-else-if="questionForm[scope.row.QuestionId] instanceof Array && scope.row.Type==='select'">
{{ questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId] }}
</span>
<el-select
v-else-if="(scope.row.Type==='select' || scope.row.Type==='radio') && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId]"
size="mini"
clearable
>
<template>
<el-option
v-for="val in scope.row.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<span v-else-if="scope.row.Type==='select' || scope.row.Type==='radio'">
{{ questionForm[scope.row.QuestionId] }}
</span>
<el-input
v-else-if="scope.row.DataSource !== 1 && questionForm[scope.row.QuestionId] instanceof Array && scope.row.Type==='number' && (scope.row.xfIndex || scope.row.xfIndex === 0) && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]"
:disabled="scope.row.DataSource === 1"
size="mini"
@blur="limitBlur(questionForm[scope.row.QuestionId][scope.row.xfIndex], scope.row.TableQuestionId, scope.row.ValueType)"
@focus="() => {questionId = scope.row.QuestionId}"
>
<template v-if="scope.row.Unit !== 0" slot="append">{{ scope.row.Unit !== 4 ? $fd('ValueUnit', scope.row.Unit) : scope.row.CustomUnit }}</template>
<template v-else-if="scope.row.ValueType === 2" slot="append">%</template>
</el-input>
<span v-else-if="questionForm[scope.row.QuestionId] instanceof Array && scope.row.Type==='number' && (scope.row.xfIndex || scope.row.xfIndex === 0)">
<template v-if="(scope.row.ValueType === 0 || scope.row.ValueType === 1) && scope.row.Unit">
{{ isNaN(parseInt(questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]))? questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]:`${questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]} ${scope.row.Unit !== 4 ? $fd('ValueUnit',scope.row.Unit) : scope.row.CustomUnit}` }}
</template>
<template v-else-if="scope.row.ValueType === 2">
{{ isNaN(parseInt(questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId])) ? questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]:`${questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]} %` }}
</template>
<template v-else>
{{ isNaN(parseInt(questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId])) ? questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]:`${questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]}` }}
</template>
</span>
<el-input
v-else-if="scope.row.DataSource !== 1 && scope.row.Type==='number' && !scope.row.IsShowInDicom && ((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)"
v-model="questionForm[scope.row.QuestionId]"
:disabled="scope.row.DataSource === 1"
size="mini"
@blur="limitBlur(questionForm, scope.row.QuestionId, scope.row.ValueType)"
@focus="() => {questionId = scope.row.QuestionId}"
>
<template v-if="scope.row.Unit !== 0" slot="append">{{ scope.row.Unit !== 4 ? $fd('ValueUnit', scope.row.Unit) : scope.row.CustomUnit }}</template>
<template v-else-if="scope.row.ValueType === 2" slot="append">%</template>
</el-input>
<span v-else-if="scope.row.Type==='number'">
<template v-if="(scope.row.ValueType === 0 || scope.row.ValueType === 1) && scope.row.Unit">
{{ isNaN(parseInt(questionForm[scope.row.QuestionId]))? questionForm[scope.row.QuestionId]:`${questionForm[scope.row.QuestionId]} ${scope.row.Unit !== 4 ? $fd('ValueUnit',scope.row.Unit) : scope.row.CustomUnit}` }}
</template>
<template v-else-if="scope.row.ValueType === 2">
{{ isNaN(parseInt(questionForm[scope.row.QuestionId])) ? questionForm[scope.row.QuestionId]:`${questionForm[scope.row.QuestionId]} %` }}
</template>
<template v-else>
{{ isNaN(parseInt(questionForm[scope.row.QuestionId])) ? questionForm[scope.row.QuestionId] : questionForm[scope.row.QuestionId] }}
</template>
</span>
</div>
</template>
<template v-else-if="scope.row.Type==='upload' && scope.row.Answers[task.VisitTaskId]">
<span v-for="(url,index) in scope.row.Answers[task.VisitTaskId].split('|')" :key="url" style="margin-left: 5px;">
<el-button v-if="scope.row.Answers[task.VisitTaskId]" type="text" @click="preview(url)">
{{ `${$t('trials:noneDicom:title:attachment')}${index + 1}` }}
</el-button>
</span>
</template>
<template v-else-if="scope.row.DictionaryCode">
{{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}
</template>
<template v-else-if="criterionType === 10">
{{ isNaN(parseInt(scope.row.Answers[task.VisitTaskId]))?scope.row.Answers[task.VisitTaskId]:`${scope.row.Answers[task.VisitTaskId]}` }}
</template>
<template v-else-if="!((task.IsBaseLine && scope.row.LimitEdit === 1) || (!task.IsBaseLine && scope.row.LimitEdit === 2) || scope.row.LimitEdit === 0)" />
<template v-else-if="(scope.row.ValueType === 0 || scope.row.ValueType === 1) && scope.row.Unit">
{{ isNaN(parseInt(scope.row.Answers[task.VisitTaskId]))?scope.row.Answers[task.VisitTaskId]:`${scope.row.Answers[task.VisitTaskId]} ${scope.row.Unit !== 4 ? $fd('ValueUnit',scope.row.Unit) : scope.row.CustomUnit}` }}
</template>
<template v-else-if="scope.row.ValueType === 2">
{{ isNaN(parseInt(scope.row.Answers[task.VisitTaskId])) ? scope.row.Answers[task.VisitTaskId]:`${scope.row.Answers[task.VisitTaskId]} %` }}
</template>
<template v-else-if="scope.row.Answers && scope.row.Answers.hasOwnProperty(task.VisitTaskId)">
{{ scope.row.Answers[task.VisitTaskId] }}
</template>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<!-- 签名框 -->
<el-dialog
v-if="signVisible"
:visible.sync="signVisible"
:close-on-click-modal="false"
width="600px"
custom-class="base-dialog-wrapper"
>
<div slot="title">
<span style="font-size:18px;">{{ $t('common:dialogTitle:sign') }}</span>
<span style="font-size:12px;margin-left:5px">{{ `(${$t('common:label:sign')}${ currentUser })` }}</span>
</div>
<SignForm ref="signForm" :sign-code-enum="signCode" @closeDialog="closeSignDialog" />
</el-dialog>
<!-- 预览文件 -->
<el-dialog
v-if="previewVisible"
:visible.sync="previewVisible"
:title="$t('common:button:preview')"
:fullscreen="true"
append-to-body
custom-class="base-dialog-wrapper"
>
<div
class="base-modal-body"
style="border: 2px solid #ccc; padding: 10px"
>
<PreviewFile
v-if="previewVisible"
:file-path="currentPath"
:file-type="currentType"
/>
</div>
</el-dialog>
</div>
</template>
<script>
import { changeCalculationAnswer, getReadingReportEvaluation, submitDicomVisitTask, verifyVisitTaskQuestions, getQuestionCalculateRelation } from '@/api/trials'
import { setSkipReadingCache } from '@/api/reading'
import { getAutoCutNextTask } from '@/api/user'
import const_ from '@/const/sign-code'
import SignForm from '@/views/trials/components/newSignForm'
import PreviewFile from '@/components/PreviewFile/index'
import { getToken } from '@/utils/auth'
import store from '@/store'
export default {
name: 'CustomizeReportPage',
components: { SignForm, PreviewFile },
data() {
return {
trialId: '',
visitTaskId: '',
subjectId: '',
readingTool: 1,
criterionType: null,
isReadingTaskViewInOrder: false,
currentUser: zzSessionStorage.getItem('userName'),
signVisible: false,
signCode: null,
visitTaskList: [],
taskQuestions: [],
loading: false,
answers: [],
readingTaskState: 2,
answerArr: [],
questions: [],
CalculationList: [],
TrialReadingCriterionId: null,
tableAnswers: {},
questionForm: {},
questionId: null,
taskInfo: null,
previewVisible: false,
currentPath: '',
currentType: ''
}
},
watch: {
questionForm: {
deep: true,
immediate: true,
handler(v, oldv) {
try {
if (!v[this.questionId] || !oldv[this.questionId]) return
} catch (e) {
}
this.formItemNumberChange(this.questionId, false)
}
}
},
async mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.trialId = this.$route.query.trialId
this.visitTaskId = this.taskInfo.VisitTaskId
this.subjectId = this.taskInfo.SubjectId
this.criterionType = this.taskInfo.CriterionType
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.TrialReadingCriterionId = this.taskInfo.TrialReadingCriterionId
window.addEventListener('resize', () => {
this.handleResize()
})
await this.getQuestionCalculateRelation()
this.getReportInfo()
},
methods: {
limitBlur(questionForm, id, valueType) {
const value = questionForm[id]
if (valueType === 0) {
this.$set(questionForm, id, parseInt(value))
} else if (valueType === 3) {
this.$set(questionForm, id, parseFloat(value))
} else {
this.$set(questionForm, id, parseFloat(value).toFixed(this.digitPlaces))
}
},
setImageUrl(qid, index, tqid, url, RowId) {
if (index || index === 0) {
//
this.$set(this.questionForm[qid][index], tqid, url)
this.$set(this.questionForm[qid][index], tqid + '_RowId', RowId)
// this.questionForm[qid][index][tqid] = url
} else {
//
this.questionForm[qid] = url
}
},
getTagterAnswers(list, questionId) {
list.forEach(v => {
if (v.QuestionId === questionId) {
return Object.assign({}, v.Answers)
} else if (v.Childrens.length > 0) {
return this.getTagterAnswers(v.Childrens, questionId)
}
})
},
formItemNumberChange(questionId, isTable) {
if (isTable) {
this.CalculationList.forEach((v, i) => {
var find = v.CalculateQuestionList.filter(o => {
return o.QuestionId === questionId
})
// findnumber
if (find) {
var num = this.logic(v)
if (num !== false) {
this.$set(this.questionForm, v.QuestionId, num)
// this.$emit('setFormItemData', { key: v.QuestionId, val: num })
}
}
})
} else {
this.CalculationList.forEach(v => {
var find = v.CalculateQuestionList.filter(o => {
return o.TableQuestionId === questionId
})
// findnumber
if (find) {
var num = this.logic(v)
if (num !== false) {
this.$set(this.questionForm, v.QuestionId, num)
// this.$emit('setFormItemData', { key: v.QuestionId, val: num })
}
}
})
}
// this.$emit('formItemNumberChange')
},
getTableAnswers(QuestionId, list) {
var arr = []
window.xfList = list
list.forEach((v, i) => {
var obj = {}
v.Childrens.forEach((o) => {
this.$set(o, 'xfIndex', i)
obj[o.TableQuestionId + '_RowId'] = o.RowId
obj[o.TableQuestionId] = o.Answers[this.visitTaskId]
})
arr.push(obj)
})
return arr
},
InitVisitTaskQuestionForm() {
this.taskQuestions.map((v, i) => {
if (v.Type === 'group' && v.Childrens.length === 0 && v.Type !== 'table' && v.Type !== 'basicTable') return
if (!v.IsPage && v.Type !== 'group' && v.Type !== 'summary' && v.Type !== 'table' && v.Type !== 'basicTable' && v.Type !== 'number') {
this.$set(this.questionForm, v.QuestionId, v.Answers[this.visitTaskId])
}
if (v.Type === 'table' || v.Type === 'basicTable') {
var tableAnswers = this.getTableAnswers(v.QuestionId, v.Childrens, i)
this.$set(this.questionForm, v.QuestionId, tableAnswers)
// this.$set(v, 'xfIndex', i)
}
if (v.Type === 'number') {
let val = null
if (v.ValueType === 0) {
val = parseInt(v.Answers[this.visitTaskId])
} else if (v.ValueType === 3) {
val = v.Answers[this.visitTaskId]
} else {
val = v.Answers[this.visitTaskId] === '' ? null : v.Answers[this.visitTaskId]
}
this.$set(this.questionForm, v.QuestionId, val)
}
if (v.Childrens.length > 0) {
this.setChild(v.Childrens)
}
})
this.formItemNumberChange(this.questionId, false)
},
setChild(obj) {
obj.forEach((i, index) => {
if (i.Type !== 'group' && i.Type !== 'summary' && i.Id && i.Type !== 'table' && i.Type !== 'basicTable') {
this.$set(this.questionForm, i.QuestionId, i.Answers[this.visitTaskId])
}
if (i.Type === 'table' || i.Type === 'basicTable') {
var tableAnswers = this.getTableAnswers(i.QuestionId, i.Childrens, index)
this.$set(this.questionForm, i.QuestionId, tableAnswers)
}
if (i.Type === 'number') {
let val = null
if (i.ValueType === 0) {
val = parseInt(i.Answers[this.visitTaskId])
} else if (i.ValueType === 3) {
val = i.Answers[this.visitTaskId]
} else {
val = i.Answers[this.visitTaskId] === '' ? null : i.Answers[this.visitTaskId]
}
this.$set(this.questionForm, i.QuestionId, val)
}
if (i.Childrens && i.Childrens.length > 0 && i.Type !== 'table' && i.Type !== 'basicTable') {
this.setChild(i.Childrens)
}
})
},
getQuestionCalculateRelation() {
return new Promise(resolve => {
getQuestionCalculateRelation({
TrialReadingCriterionId: this.TrialReadingCriterionId
}).then(res => {
this.CalculationList = res.Result
resolve()
})
})
},
logic(rules, num = 0) {
try {
if (rules.CalculateQuestionList.length === 0) {
return false
}
rules.CalculateQuestionList.forEach((o, i) => {
if (i === 0) {
if (rules.CustomCalculateMark > 4) {
switch (rules.CustomCalculateMark) {
case 5:
this.questionForm[o.QuestionId].forEach((q, qi) => {
if (qi === 0) {
num = parseFloat(q[o.TableQuestionId])
} else {
num *= parseFloat(q[o.TableQuestionId])
}
})
break
case 6:
this.questionForm[o.QuestionId].forEach((q, qi) => {
if (qi === 0) {
num = isNaN(parseFloat(q[o.TableQuestionId])) ? null : parseFloat(q[o.TableQuestionId])
} else {
num += isNaN(parseFloat(q[o.TableQuestionId])) ? null : parseFloat(q[o.TableQuestionId])
}
})
break
case 7:
this.questionForm[o.QuestionId].forEach((q, qi) => {
if (qi === 0) {
num = parseFloat(q[o.TableQuestionId])
} else {
num += parseFloat(q[o.TableQuestionId])
}
})
num = this.questionForm[o.QuestionId].length === 0 ? 0 : num / this.questionForm[o.QuestionId].length
break
case 8:
const arr = []
this.questionForm[o.QuestionId].forEach(q => {
arr.push(q[o.TableQuestionId])
})
num = arr.length === 0 ? 0 : Math.max(...arr)
break
case 9:
const arr1 = []
this.questionForm[o.QuestionId].forEach(q => {
arr1.push(q[o.TableQuestionId])
})
num = arr1.length === 0 ? 0 : Math.min(...arr1)
break
}
} else {
num = parseFloat(this.questionForm[o.TableQuestionId])
}
} else {
switch (rules.CustomCalculateMark) {
case 1:
num += parseFloat(this.questionForm[o.TableQuestionId])
break
case 2:
num -= parseFloat(this.questionForm[o.TableQuestionId])
break
case 3:
num *= parseFloat(this.questionForm[o.TableQuestionId])
break
case 4:
num /= parseFloat(this.questionForm[o.TableQuestionId])
// num /= parseFloat(this.questionForm[o.TableQuestionId])
break
}
}
})
} catch (e) {
console.log(e)
}
if (rules.ValueType === 2) {
num = num * 100
}
return isNaN(num) ? '' : isFinite(num) ? num.toFixed(this.digitPlaces) : '∞'
},
getReportInfo() {
this.loading = true
var params = {
visitTaskId: this.visitTaskId,
trialId: this.trialId
}
this.taskQuestions = []
getReadingReportEvaluation(params).then(res => {
this.readingTaskState = res.Result.ReadingTaskState
this.answerArr = []
this.questions = res.Result.TaskQuestions.concat()
var taskQuestions = this.getQuestions(res.Result.TaskQuestions)
taskQuestions.forEach(item => {
this.$set(this.taskQuestions, this.taskQuestions.length, item)
})
this.visitTaskList = res.Result.VisitTaskList
this.InitVisitTaskQuestionForm()
this.handleResize()
this.loading = false
}).catch(() => { this.loading = false })
},
getQuestions(questions) {
const arr = []
if (questions.length !== 0) {
questions.forEach((item) => {
const obj = item
this.$set(obj, 'Answers', {})
item.Answer.forEach(i => {
if (item.DictionaryCode) {
this.$set(obj.Answers, i.VisitTaskId, i.Answer ? parseInt(i.Answer) : null)
// obj.Answers[i.VisitTaskId] = i.Answer ? parseInt(i.Answer) : null
} else {
if (item.Type === 'number') {
let val = null
if (item.ValueType === 0) {
val = parseInt(i.Answer)
} else if (item.ValueType === 3) {
val = i.Answer
} else {
val = isNaN(parseFloat(i.Answer)) ? i.Answer : parseFloat(i.Answer).toFixed(this.digitPlaces)
}
this.$set(obj.Answers, i.VisitTaskId, val)
} else {
this.$set(obj.Answers, i.VisitTaskId, i.Answer)
}
// obj.Answers[i.VisitTaskId] = i.Answer
}
})
if (item.Childrens.length >= 1) {
obj.Childrens = this.getQuestions(item.Childrens)
}
arr.push(obj)
})
}
return arr
},
handleShowDetail(val) {
this.getReportInfo()
// this.taskQuestions = this.getQuestions(res.Result.TaskQuestions, !this.isShowDetail, null)
},
async handleConfirm() {
await this.handleSave(false)
await this.verifyVisitTaskQuestions()
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
},
verifyVisitTaskQuestions() {
return new Promise((resolve, reject) => {
this.loading = true
verifyVisitTaskQuestions({ visitTaskId: this.visitTaskId }).then(res => {
this.loading = false
resolve()
}).catch(() => {
this.loading = false
reject()
})
})
},
handleResize() {
this.$nextTick(() => {
this.$refs.reportList ? this.$refs.reportList.doLayout() : ''
})
},
//
closeSignDialog(isSign, signInfo) {
if (isSign) {
this.signConfirm(signInfo)
} else {
this.signVisible = false
}
},
//
async signConfirm(signInfo) {
this.loading = true
try {
var params = {
data: {
visitTaskId: this.visitTaskId
},
signInfo: signInfo
}
const res = await submitDicomVisitTask(params)
if (res.IsSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (this.$refs['signForm']) {
this.$refs['signForm'].btnLoading = false
}
this.signVisible = false
//
this.readingTaskState = 2
this.$emit('setReadingTaskState', 2)
window.opener.postMessage('refreshTaskList', window.location)
const res = await getAutoCutNextTask()
const isAutoTask = res.Result.AutoCutNextTask
if (isAutoTask) {
window.location.reload()
} else {
// ''
this.$confirm(this.$t('trials:readingReport:message:msg4'), {
type: 'warning',
distinguishCancelAndClose: true
})
.then(() => {
window.location.reload()
})
.catch(action => {
// changeURLStatic('visitTaskId', this.visitTaskId)
})
}
}
this.loading = false
} catch (e) {
console.log(e)
this.loading = false
if (this.$refs['signForm'] && this.$refs['signForm'].btnLoading) {
this.$refs['signForm'].btnLoading = false
}
}
},
previewDicoms(task) {
if (this.openWindow) {
this.openWindow.close()
}
var token = getToken()
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
// var subjectCode = this.$router.currentRoute.query.subjectCode
var subjectCode = localStorage.getItem('subjectCode')
var subjectId = this.$router.currentRoute.query.subjectId
var trialId = this.$router.currentRoute.query.trialId
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var criterionType = this.$router.currentRoute.query.criterionType
var readingTool = this.$router.currentRoute.query.readingTool
var path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${subjectCode}&subjectId=${subjectId}&visitTaskId=${task.VisitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
const routeData = this.$router.resolve({ path })
this.openWindow = window.open(routeData.href, '_blank')
},
handleSave(isPrompt) {
return new Promise((resolve, reject) => {
this.loading = true
var answers = []
var tableQuestionAnswer = []
for (const k in this.questionForm) {
if (this.questionForm[k] instanceof Array) {
this.questionForm[k].forEach((v, i) => {
Object.keys(v).forEach(o => {
if (o.indexOf('_RowId') === -1) {
tableQuestionAnswer.push({
questionId: k,
answer: v[o],
tableQuestionId: o,
rowId: v[o + '_RowId']
})
}
})
})
// tableQuestionAnswer.push({})
} else {
answers.push({ questionId: k, answer: this.questionForm[k] })
}
}
var params = {
visitTaskId: this.visitTaskId,
questionAnswer: answers,
tableQuestionAnswer: tableQuestionAnswer
}
changeCalculationAnswer(params).then(res => {
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
// DicomEvent.$emit('refreshQuestionAnswer')
this.loading = false
resolve()
}).catch(() => {
this.loading = false
reject()
})
})
},
async skipTask() {
try {
//
const confirm = await this.$confirm(
this.$t('trials:readingReport:message:skipConfirm'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
this.loading = true
const res = await setSkipReadingCache({ visitTaskId: this.visitTaskId })
this.loading = false
if (res.IsSuccess) {
window.location.reload()
}
} catch (e) {
this.loading = false
console.log(e)
}
},
//
preview(path) {
this.currentPath = path
const arr = path.split('.')
this.currentType = arr[arr.length - 1]
this.previewVisible = true
}
}
}
</script>
<style lang="scss" scoped>
.report-wrapper{
height: 100%;
// background-color: #fff;
background-color: #000;
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
// background: #d0d0d0;
}
.report-header{
display: flex;
}
.el-card{
background-color: #000;
color: #ffffff;
border:none;
}
::v-deep .el-table--border th.gutter:last-of-type{
border: none;
}
::v-deep .el-table, .el-table__expanded-cell {
background-color: #000;
color: #fff;
border-color:#444444;
}
::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;
}
::v-deep .el-upload-list--picture-card .el-upload-list__item{
width: 30px;
height: 30px;
}
::v-deep .el-upload--picture-card{
width: 30px;
height: 30px;
line-height: 40px;
}
::v-deep .el-dialog{
background: #1e1e1e;
border: 1px solid #ddd;
color: #ddd;
.el-dialog__title{
color:#fff;
}
.el-input .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
.el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
.el-form-item__label{
color: #dfdfdf
}
}
}
::v-deep .el-switch__label{
color:#fff;
}
::v-deep .el-switch__label.is-active{
color: #428bca;
}
.uploadWrapper{
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>
@@ -208,6 +208,8 @@
:question-form="questionForm"
:reading-task-state="readingTaskState"
:visitTaskId="visitTaskId"
@resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData"
/>
</div>
</template>
@@ -315,4 +317,8 @@ export default {
::v-deep .el-select.is-disabled .el-input__inner{
background-color: #646464a1;
}
// ::v-deep .el-radio__input.is-disabled.is-checked .el-radio__inner {
// background-color: #428bca;
// border-color: #428bca;
// }
</style>
@@ -31,6 +31,8 @@
:question-form="outerForm"
:reading-task-state="readingTaskState"
:visit-task-id="visitTaskId"
@resetFormItemData="resetOuterFormItemData"
@setFormItemData="setOuterFormItemData"
/>
<el-form-item v-if="readingTaskState < 2">
<div class="outer_form-footer">
@@ -88,7 +90,8 @@
<template slot="title">
<div
style="width:340px;position: relative;"
:style="{color:(activeName===table.Id+answer.RowIndex?'#ffeb3b':'#fff')}"
:style="{color:innerFormData[`${table.Id}_${answer.RowIndex}`].IntrahepaticLesion ?'#5fb4ff':'#fff'}"
:title="innerFormData[`${table.Id}_${answer.RowIndex}`].IntrahepaticLesion ? $t('trials:mRecist:text:text1') : ''"
>
{{ getLesionName(table.OrderMark,answer.RowIndex) }}
<!-- 未保存 -->
@@ -99,7 +102,7 @@
<el-tooltip v-if="readingTaskState<2 && parseInt(innerFormData[`${table.Id}_${answer.RowIndex}`].SaveTypeEnum) ===1" class="item" effect="dark" :content="$t('trials:reading:button:incompleteInfor')" placement="bottom">
<i class="el-icon-warning" style="color:#ff9800" />
</el-tooltip>
<div style="position: absolute;left: 50px;top: 2px;">
<div :style="{position: 'absolute', left: '50px', top: '2px', color: innerFormData[`${table.Id}_${answer.RowIndex}`].IntrahepaticLesion ? '#5fb4ff' : '#fff'}">
<div style="font-size: 11px;width:220px;height: 30px;">
<div
v-if="innerFormData[`${table.Id}_${answer.RowIndex}`].LesionPart"
@@ -337,6 +340,12 @@ export default {
}
})
return formData
},
resetOuterFormItemData(v) {
this.$set(this.outerForm, v, null)
},
setOuterFormItemData(obj) {
},
//
async getTableQuestions() {
@@ -350,6 +359,7 @@ export default {
const res = await getReadingQuestionAndAnswer(params)
if (res.IsSuccess) {
this.tableQuestions = res.Result.SinglePage
this.innerFormData = {}
res.Result.SinglePage.forEach(group => {
group.Childrens.forEach(table => {
const tableKey = table.Id
@@ -445,12 +455,12 @@ export default {
const res = await saveTaskQuestion(0, params)
if (res.IsSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (!this.isBaseLineTask && (this.criterionType === 1 || this.criterionType === 2 || this.criterionType === 3 || this.criterionType === 17)) {
if (!this.isBaseLineTask) {
if (parseInt(imageQuality) === 2) {
this.$confirm(this.$t('trials:reading:warnning:unableEvaluate'), '', {
type: 'warning'
}).then(() => {
this.changeLesionState(2)
this.changeLesionState()
}).catch(() => {
})
@@ -534,7 +544,7 @@ export default {
}
// 线<10mm<10mm
if (!this.isBaseLineTask && ((obj.toolName === 'Length' && length < 10) || (obj.toolName === 'Bidirectional' && short < 10))) {
const stateId = this.getQuestionId(7)
const stateId = this.getQuestionId(7, targetTable.TableQuestions.Questions)
if (innerForm.IsLymphNodes) {
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], 'LesionState', 3)
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], stateId, 3)
@@ -545,11 +555,11 @@ export default {
}
}
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], 'SaveTypeEnum', innerForm.RowId ? 1 : 0)
this.$emit('setMarkName', {
annotationUID: annotation.annotationUID,
name: this.innerFormData[`${tableId}_${rowIndex}`].LesionName
}
)
// this.$emit('setMarkName', {
// annotationUID: annotation.annotationUID,
// name: this.innerFormData[`${tableId}_${rowIndex}`].LesionName
// })
this.setMarkName(this.innerFormData[`${tableId}_${rowIndex}`].LesionType, this.innerFormData[`${tableId}_${rowIndex}`][stateId], this.innerFormData[`${tableId}_${rowIndex}`].LesionName, annotation)
}
} else {
if (this.isBaseLineTask) {
@@ -568,7 +578,6 @@ export default {
}
},
modifyAnnotation(obj) {
console.log('modifyAnnotation')
const { annotation, toolName } = obj
const formData = Object.values(this.innerFormData).find(
i => i.LesionName === annotation.data.label
@@ -625,7 +634,7 @@ export default {
}
// 线<10mm<10mm
if (!this.isBaseLineTask && ((toolName === 'Length' && length < 10) || (toolName === 'Bidirectional' && short < 10))) {
const stateId = this.getQuestionId(7)
const stateId = this.getQuestionId(7, targetTable.TableQuestions.Questions)
if (isLymphNodes) {
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], 'LesionState', 3)
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], stateId, 3)
@@ -635,9 +644,24 @@ export default {
}
}
}
this.setMarkName(this.innerFormData[`${tableId}_${rowIndex}`].LesionType, this.innerFormData[`${tableId}_${rowIndex}`][stateId], this.innerFormData[`${tableId}_${rowIndex}`].LesionName, annotation)
this.$set(this.innerFormData[`${tableId}_${rowIndex}`], 'SaveTypeEnum', formData.RowId ? 1 : 0)
this.activeName = `${tableId}_${rowIndex}`
},
setMarkName(lesionType, lesionState, lesionName, annotation) {
let status = ''
if (lesionType === 0 && lesionState === 1) {
status = 'T'
}
if ((lesionType === 0 && lesionState === 3) || (lesionType === 1 && lesionState === 3) || (lesionType === 2 && lesionState === 3)) {
status = 'D'
}
this.$emit('setMarkName', {
annotationUID: annotation.annotationUID,
name: lesionName,
status
})
},
//
addTarget(tableInfo, annotation) {
//
@@ -712,8 +736,7 @@ export default {
this.$emit('setMarkName', {
annotationUID: annotation.annotationUID,
name: answer.LesionName
}
)
})
}
this.$set(this.innerFormData, `${tableInfo.Id}_${answer.RowIndex}`, {})
for (const key in answer) {
@@ -805,7 +828,7 @@ export default {
this.$emit('removeAnnotation', obj)
}
//
this.getTableQuestions()
await this.getTableQuestions()
this.$emit('getAnnotations', this.visitTaskId)
// ''
this.$message.success(this.$t('common:message:deletedSuccessfully'))
@@ -832,9 +855,26 @@ export default {
async saveLesion(table, rowIndex) {
const valid = await this.$refs[`${table.Id}_${rowIndex}`][0].validate()
if (!valid) return
const loading = this.$loading({ fullscreen: true })
let loading = null
try {
const innerForm = this.innerFormData[`${table.Id}_${rowIndex}`]
if (table.LesionType === 0 && this.isBaseLineTask && !innerForm.IntrahepaticLesion) {
const arr = Object.values(this.innerFormData).filter(
obj => obj.LesionType === 0 && obj.LesionName !== innerForm.LesionName
)
if (arr.length === 0 && !innerForm.RowId) {
const confirm = await this.$confirm(
this.$t('trials:mRecist:warnning:msg7'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
}
}
loading = this.$loading({ fullscreen: true })
const measureData = innerForm.MeasureData
const lesionState = innerForm.LesionState
let lesionLength = innerForm.LesionLength
@@ -842,12 +882,12 @@ export default {
const lymphNodes = innerForm.IsLymphNodes
const lesionType = table.LesionType
const lesionPart = innerForm.LesionPart
const intrahepaticLesion = innerForm.IntrahepaticLesion
if (this.isBaseLineTask) {
// 线
// 0
if (lesionType === 0 && lesionState === 0) {
if (lymphNodes === 1) {
console.log('靶病灶且状态为存在')
// 使,15mm
if (!(measureData && measureData.markTool === 'Bidirectional' && lesionShort >= 15)) {
// 使15mm
@@ -888,7 +928,7 @@ export default {
if (lesionType === 1 && lesionState === 0) {
if (lymphNodes === 1) {
// 使
if (!(measureData && (measureData.markTool === 'Bidirectional' || measureData.markTool === 'RectangleRo'))) {
if (!(measureData && (measureData.markTool === 'Bidirectional' || measureData.markTool === 'RectangleROI'))) {
// 使
this.$confirm(this.$t('trials:reading:warnning:msg21'), {
type: 'warning',
@@ -1175,6 +1215,16 @@ export default {
}
} else {
//
if (intrahepaticLesion && !(measureData && measureData.markTool === 'Length')) {
// 使
this.$confirm(this.$t('trials:mRecist:warnning:msg6'), {
type: 'warning',
showCancelButton: false,
callback: action => {}
})
loading.close()
return
}
if (!(measureData && (measureData.markTool === 'Length' || measureData.markTool === 'ArrowAnnotate'))) {
// 使
this.$confirm(this.$t('trials:reading:warnning:msg40'), {
@@ -1213,6 +1263,16 @@ export default {
return
}
} else {
if (innerForm.IntrahepaticLesion && !(measureData && (measureData.markTool === 'Length'))) {
// `使`
this.$confirm(this.$t('trials:mRecist:warnning:msg6'), {
type: 'warning',
showCancelButton: false,
callback: action => {}
})
loading.close()
return
}
//
if (!(measureData && (measureData.markTool === 'Length' || measureData.markTool === 'ArrowAnnotate'))) {
// `使`
@@ -1326,7 +1386,7 @@ export default {
studyId: measureData ? measureData.studyId : '',
seriesId: measureData ? measureData.seriesId : '',
instanceId: measureData ? measureData.instanceId : '',
numberOfFrames: measureData ? measureData.frame : null,
numberOfFrames: measureData ? measureData.numberOfFrames : null,
picturePath: picturePath,
organInfoId: innerForm.OrganInfoId,
markTool: measureData ? measureData.markTool : '',
@@ -1357,7 +1417,9 @@ export default {
loading.close()
} catch (e) {
console.log(e)
loading.close()
if (loading) {
loading.close()
}
}
},
//
@@ -1414,6 +1476,10 @@ export default {
if (obj.questionMark === 7) {
this.$set(this.innerFormData[`${obj.tableId}_${obj.rowIndex}`], 'LesionState', obj.val)
let annotation = this.innerFormData[`${obj.tableId}_${obj.rowIndex}`].MeasureData
if (annotation) {
this.setMarkName(this.innerFormData[`${obj.tableId}_${obj.rowIndex}`].LesionType, obj.val, this.innerFormData[`${obj.tableId}_${obj.rowIndex}`].LesionName, annotation)
}
}
if (obj.questionMark === 8) {
this.$set(this.innerFormData[`${obj.tableId}_${obj.rowIndex}`], 'LesionPart', obj.val)
@@ -1705,9 +1771,17 @@ export default {
let lesionLength = null
let lesionShort = null
if (innerForm.MeasureData) {
//
lesionLength = innerForm.LesionLength ? innerForm.LesionLength : null
lesionShort = innerForm.LesionShort ? innerForm.LesionShort : null
if (innerForm.MeasureData.metadata.toolName === 'Length') {
const referencedImageId = innerForm.MeasureData.metadata.referencedImageId
const length = innerForm.MeasureData.data.cachedStats[`imageId:${referencedImageId}`].length
lesionLength = length ? parseFloat(length).toFixed(this.digitPlaces) : length
} else if (innerForm.MeasureData.metadata.toolName === 'Bidirectional') {
const referencedImageId = innerForm.MeasureData.metadata.referencedImageId
const length = innerForm.MeasureData.data.cachedStats[`imageId:${referencedImageId}`].length
lesionLength = length ? parseFloat(length).toFixed(this.digitPlaces) : length
const short = innerForm.MeasureData.data.cachedStats[`imageId:${referencedImageId}`].width
lesionShort = short ? parseFloat(short).toFixed(this.digitPlaces) : short
}
this.$emit('setMarkName', {
annotationUID: innerForm.MeasureData.annotationUID,
name: answer.LesionName
@@ -1756,9 +1830,16 @@ export default {
this.activeName = ''
}
},
changeLesionState(state) {
changeLesionState() {
Object.values(this.innerFormData).forEach(i => {
console.log(i)
if (isNaN(parseInt(i.LesionState)) && i.IsCurrentTaskAdd === 'False') {
i.LesionState = 2
const targetTable = this.tableQuestions[0].Childrens.find(
child => child.Id === i.TableId
)
const stateId = this.getQuestionId(7, targetTable.TableQuestions.Questions)
i[stateId] = 2
}
})
},
removeAnswer(tableId, rowIndex) {
@@ -1817,6 +1898,7 @@ export default {
const rowIndex = this.activeName.split('_')[1]
const innerForm = this.innerFormData[`${tableId}_${rowIndex}`]
const isLymphNodes = innerForm.IsLymphNodes
const intrahepaticLesion = innerForm.IntrahepaticLesion
if (!innerForm.MeasureData) {
if (this.isBaseLineTask) {
if (innerForm.LesionType === 0 && isLymphNodes === 1 && toolName !== 'Bidirectional') {
@@ -1852,6 +1934,10 @@ export default {
} else if (innerForm.LesionType === 2 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'ArrowAnnotate') {
// 使
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg12') }
} else if (innerForm.LesionType === 2 && isLymphNodes === 0 && toolName !== 'Length' && intrahepaticLesion) {
// 使
// 使
return { isCanActiveTool: false, reason: this.$t('trials:mRecist:warnning:msg5') }
} else if (innerForm.LesionType === 2 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'ArrowAnnotate') {
// 使
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg13') }
@@ -1,237 +1,283 @@
const config = {
"standards": [
'standards': [
{
"type": 1,
"name": "RECIST 1.1",
"tools": [
{
"name": "直径测量工具",
"icon": "length",
"toolName": "Length",
"i18nKey": "trials:reading:button:length",
"isDisabled": false,
"disabledReason": ''
'type': 1,
'name': 'RECIST 1.1',
'tools': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "长短径测量工具",
"icon": "bidirection",
"toolName": "Bidirectional",
"i18nKey": "trials:reading:button:bidirectional",
"isDisabled": false,
"disabledReason": ''
{
'name': '长短径测量工具',
'icon': 'bidirection',
'toolName': 'Bidirectional',
'i18nKey': 'trials:reading:button:bidirectional',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 2,
"name": "Lugano 2014",
"tools": [
{
"name": "直径测量工具",
"icon": "length",
"toolName": "Length",
"i18nKey": "trials:reading:button:length",
"isDisabled": false,
"disabledReason": ''
'type': 2,
'name': 'Lugano 2014',
'tools': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "长短径测量工具",
"icon": "bidirection",
"toolName": "Bidirectional",
"i18nKey": "trials:reading:button:bidirectional",
"isDisabled": false,
"disabledReason": ''
{
'name': '长短径测量工具',
'icon': 'bidirection',
'toolName': 'Bidirectional',
'i18nKey': 'trials:reading:button:bidirectional',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 3,
"name": "iRECIST",
"tools": [
{
"name": "直径测量工具",
"icon": "length",
"toolName": "Length",
"i18nKey": "trials:reading:button:length",
"isDisabled": false,
"disabledReason": ''
'type': 3,
'name': 'iRECIST',
'tools': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "长短径测量工具",
"icon": "bidirection",
"toolName": "Bidirectional",
"i18nKey": "trials:reading:button:bidirectional",
"isDisabled": false,
"disabledReason": ''
{
'name': '长短径测量工具',
'icon': 'bidirection',
'toolName': 'Bidirectional',
'i18nKey': 'trials:reading:button:bidirectional',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 7,
"name": "mRECIST HCC",
"tools": [
{
"name": "直径测量工具",
"icon": "length",
"toolName": "Length",
"i18nKey": "trials:reading:button:length",
"isDisabled": false,
"disabledReason": ''
'type': 7,
'name': 'mRECIST HCC',
'tools': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "长短径测量工具",
"icon": "bidirection",
"toolName": "Bidirectional",
"i18nKey": "trials:reading:button:bidirectional",
"isDisabled": false,
"disabledReason": ''
{
'name': '长短径测量工具',
'icon': 'bidirection',
'toolName': 'Bidirectional',
'i18nKey': 'trials:reading:button:bidirectional',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 10,
"name": "PCWG3",
"tools": [
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
'type': 10,
'name': 'PCWG3',
'tools': [
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 17,
"name": "PCWG3",
"tools": [
{
"name": "直径测量工具",
"icon": "length",
"toolName": "Length",
"i18nKey": "trials:reading:button:length",
"isDisabled": false,
"disabledReason": ''
'type': 17,
'name': 'PCWG3',
'tools': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "矩形工具",
"icon": "rectangle",
"toolName": "RectangleROI",
"i18nKey": "trials:reading:button:rectangle",
"isDisabled": false,
"disabledReason": ''
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
"name": "箭头工具",
"icon": "arrow",
"toolName": "ArrowAnnotate",
"i18nKey": "trials:reading:button:arrowAnnotate",
"isDisabled": false,
"disabledReason": ''
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
"type": 19,
"name": "IVUS定量评估",
"tools": []
'type': 19,
'name': 'IVUS定量评估',
'tools': []
},
{
"type": 20,
"name": "OCT定量评估",
"tools": []
'type': 20,
'name': 'OCT定量评估',
'tools': []
},
{
"type": 21,
"name": "MRI-PDFF",
"tools": [
{
"name": "圆形测量",
"icon": "oval",
"toolName": "Probe",
"i18nKey": "trials:reading:button:circle",
"isDisabled": false,
"disabledReason": ''
'type': 21,
'name': 'MRI-PDFF',
'tools': [
{
'name': '圆形测量',
'icon': 'oval',
'toolName': 'Probe',
'i18nKey': 'trials:reading:button:circle',
'isDisabled': false,
'disabledReason': ''
}
]
}
],
'customizeStandards': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '长短径测量工具',
'icon': 'bidirection',
'toolName': 'Bidirectional',
'i18nKey': 'trials:reading:button:bidirectional',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '椭圆工具',
'icon': 'oval',
'toolName': 'CircleROI',
'i18nKey': 'trials:reading:button:Circle',
'isDisabled': false,
'disabledReason': ''
}
]
}
const getTools = (criterionType) => {
const standard = config.standards.find(s => s.type === criterionType);
return standard?.tools || [];
};
export {config, getTools}
const standard = config.standards.find(s => s.type === criterionType)
return standard?.tools || []
}
const getCustomizeStandardsTools = (toolNames) => {
const filteredTools = config.customizeStandards.filter(item => toolNames.includes(item.toolName))
return filteredTools || []
}
export { config, getTools, getCustomizeStandardsTools }
@@ -0,0 +1,305 @@
import {
VolumeViewport,
utilities as csUtils,
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
utilities,
annotation,
drawing
} = cornerstoneTools
const { getAnnotations } = annotation.state
const { isAnnotationVisible } = annotation.visibility
const { isAnnotationLocked } = annotation.locking
const drawHandlesSvg = drawing.drawHandles
const drawRectSvg = drawing.drawRectByCoordinates
const drawLinkedTextBoxSvg = drawing.drawLinkedTextBox
const { getTextBoxCoordsCanvas } = utilities.drawing
/**
* RectangleROIAnnotation let you draw annotations that measures the statistics
* such as area, max, mean and stdDev of a Rectangular region of interest.
* You can use RectangleROIAnnotation in all perpendicular views (axial, sagittal, coronal).
* Note: annotation tools in cornerstone3DTools exists in the exact location
* in the physical 3d space, as a result, by default, all annotations that are
* drawing in the same frameOfReference will get shared between viewports that
* are in the same frameOfReference. RectangleROI tool's text box lines are dynamically
* generated based on the viewport's underlying Modality. For instance, if
* the viewport is displaying CT, the text box will shown the statistics in Hounsfield units,
* and if the viewport is displaying PET, the text box will show the statistics in
* SUV units.
*
* The resulting annotation's data (statistics) and metadata (the
* state of the viewport while drawing was happening) will get added to the
* ToolState manager and can be accessed from the ToolState by calling getAnnotations
* or similar methods.
*
* ```js
* cornerstoneTools.addTool(RectangleROITool)
*
* const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
*
* toolGroup.addTool(RectangleROITool.toolName)
*
* toolGroup.addViewport('viewportId', 'renderingEngineId')
*
* toolGroup.setToolActive(RectangleROITool.toolName, {
* bindings: [
* {
* mouseButton: MouseBindings.Primary, // Left Click
* },
* ],
* })
* ```
*
* Read more in the Docs section of the website.
*/
class RectangleROITool extends cornerstoneTools.RectangleROITool {
constructor(
toolProps,
defaultToolProps
) {
super(toolProps, defaultToolProps);
}
/**
* it is used to draw the rectangleROI annotation in each
* request animation frame. It calculates the updated cached statistics if
* data is invalidated and cache it.
*
* @param enabledElement - The Cornerstone's enabledElement.
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
*/
renderAnnotation = (
enabledElement,
svgDrawingHelper
) => {
let renderStatus = false;
const { viewport } = enabledElement;
const { element } = viewport;
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 { color, lineWidth, lineDash } = this.getAnnotationStyle({
annotation,
styleSpecifier,
});
const { viewPlaneNormal, viewUp } = viewport.getCamera();
// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
// force to recalculate the stats from the points
if (
!data.cachedStats[targetId] ||
data.cachedStats[targetId].areaUnit == null
) {
data.cachedStats[targetId] = {
Modality: null,
area: null,
max: null,
mean: null,
stdDev: null,
areaUnit: null,
};
this._calculateCachedStats(
annotation,
viewPlaneNormal,
viewUp,
renderingEngine,
enabledElement
);
} else if (annotation.invalidated) {
this._throttledCalculateCachedStats(
annotation,
viewPlaneNormal,
viewUp,
renderingEngine,
enabledElement
);
// If the invalidated data is as a result of volumeViewport manipulation
// of the tools, we need to invalidate the related stackViewports data if
// they are not at the referencedImageId, so that
// when scrolling to the related slice in which the tool were manipulated
// we re-render the correct tool position. This is due to stackViewport
// which doesn't have the full volume at each time, and we are only working
// on one slice at a time.
if (viewport instanceof VolumeViewport) {
const { referencedImageId } = annotation.metadata;
// invalidate all the relevant stackViewports if they are not
// at the referencedImageId
for (const targetId in data.cachedStats) {
if (targetId.startsWith('imageId')) {
const viewports = renderingEngine.getStackViewports();
const invalidatedStack = viewports.find((vp) => {
// The stack viewport that contains the imageId but is not
// showing it currently
const referencedImageURI =
csUtils.imageIdToURI(referencedImageId);
const hasImageURI = vp.hasImageURI(referencedImageURI);
const currentImageURI = csUtils.imageIdToURI(
vp.getCurrentImageId()
);
return hasImageURI && currentImageURI !== referencedImageURI;
});
if (invalidatedStack) {
delete data.cachedStats[targetId];
}
}
}
}
}
// If rendering engine has been destroyed while rendering
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 && activeHandleIndex !== undefined
) {
// Not locked or creating and hovering over handle, so render handle.
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
}
if (activeHandleCanvasCoords) {
const handleGroupUID = '0';
drawHandlesSvg(
svgDrawingHelper,
annotationUID,
handleGroupUID,
activeHandleCanvasCoords,
{
color,
}
);
}
const dataId = `${annotationUID}-rect`;
const rectangleUID = '0';
drawRectSvg(
svgDrawingHelper,
annotationUID,
rectangleUID,
canvasCoordinates,
{
color,
lineDash,
lineWidth,
},
dataId
);
renderStatus = true;
const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
if (!options.visibility) {
data.handles.textBox = {
hasMoved: false,
worldPosition: [0, 0, 0],
worldBoundingBox: {
topLeft: [0, 0, 0],
topRight: [0, 0, 0],
bottomLeft: [0, 0, 0],
bottomRight: [0, 0, 0],
},
};
continue;
}
const textLines = this.configuration.getTextLines(data, targetId);
if (!textLines || textLines.length === 0) {
continue;
}
if (!data.handles.textBox.hasMoved) {
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
data.handles.textBox.worldPosition =
viewport.canvasToWorld(canvasTextBoxCoords);
}
const textBoxPosition = viewport.worldToCanvas(
data.handles.textBox.worldPosition
);
const textBoxUID = '1';
const boundingBox = drawLinkedTextBoxSvg(
svgDrawingHelper,
annotationUID,
textBoxUID,
textLines,
textBoxPosition,
canvasCoordinates,
{},
options
);
const { x: left, y: top, width, height } = boundingBox;
data.handles.textBox.worldBoundingBox = {
topLeft: viewport.canvasToWorld([left, top]),
topRight: viewport.canvasToWorld([left + width, top]),
bottomLeft: viewport.canvasToWorld([left, top + height]),
bottomRight: viewport.canvasToWorld([left + width, top + height]),
};
}
return renderStatus;
};
}
export default RectangleROITool;
@@ -302,7 +302,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -358,7 +358,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -362,7 +362,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -349,7 +349,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -355,7 +355,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -349,7 +349,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -471,7 +471,7 @@ export default {
readingTool = isNaN(parseInt(readingTool)) ? null : parseInt(readingTool)
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -328,7 +328,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId;
var path = "";
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${
this.rowData.SubjectCode
}&subjectId=${
@@ -355,7 +355,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId;
var path = "";
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${
this.rowData.SubjectCode
}&subjectId=${this.rowData.SubjectId}&visitTaskId=${
@@ -320,7 +320,7 @@ export default {
var isReadingTaskViewInOrder = this.rowData.IsReadingTaskViewInOrder
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
@@ -201,7 +201,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
@@ -178,7 +178,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
@@ -787,7 +787,7 @@ export default {
var trialReadingCriterionId = row.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
@@ -550,7 +550,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0) {
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@@ -1112,7 +1112,7 @@ export default {
lookReadingResults(row) {
var token = getToken()
var path
if (row.ReadingTool === 0) {
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
path = `/readingDicoms?trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
@@ -521,7 +521,7 @@ export default {
window.localStorage.setItem('TrialReadingCriterionId', this.TrialReadingCriterionId)
var token = getToken()
var path = ''
if (row.ReadingTool === 0) {
if (row.ReadingTool === 0 || row.ReadingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
@@ -32,7 +32,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var criterionType = this.$router.currentRoute.query.criterionType
var path = ''
if (this.readingTool === 0) {
if (this.readingTool === 0 || this.readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${TrialReadingCriterionId}&trialId=${trialId}&subjectCode=${subjectCode}&subjectId=${subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${this.readingTool}&TokenKey=${this.token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${TrialReadingCriterionId}&trialId=${trialId}&subjectCode=${subjectCode}&subjectId=${subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${this.readingTool}&TokenKey=${this.token}`
@@ -436,7 +436,7 @@ export default {
)
var token = getToken()
var path = ''
if (this.readingTool === 0) {
if (this.readingTool === 0 || this.readingTool === 2) {
if (this.criterionType === 0 && this.trialId === '08dd28b3-6843-fc05-0242-ac1301000000') {
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 {
@@ -468,7 +468,7 @@ export default {
)
var token = getToken()
var path = ''
if (this.readingTool === 0) {
if (this.readingTool === 0 || this.readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
@@ -614,7 +614,7 @@ export default {
}
var token = getToken()
var path
if (row.ReadingTool === 0) {
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
@@ -601,10 +601,29 @@
</el-dialog>
<!-- 退回影响任务列表/已影响任务列表 -->
<el-dialog v-if="ReReadingOrBackVisible" :title="opentype === 'read'
? $t('trials:reviewTrack:dialog:backImpactList')
? $t('trials:reviewTrack:action:back')
: $t('trials:reviewTrack:dialog:impactList')
" :visible.sync="ReReadingOrBackVisible" width="1460px" append-to-body :close-on-click-modal="false"
custom-class="base-dialog-wrapper">
<el-form ref="backReasonForm" :rules="rules" :model="backforReasonForm" class="demo-ruleForm" size="small"
label-width="180px">
<!-- 申请原因 -->
<el-divider content-position="left">
{{ $t('trials:reviewTrack:applyReread:title:backReason') }}
</el-divider>
<!-- 申请原因 -->
<el-form-item :label="$t('trials:reviewTrack:applyReread:title:backReason')" prop="pmBackReason" :rules="[
{ required: true, message: $t('common:ruleMessage:specify') },
]">
<el-input v-model="backforReasonForm.pmBackReason" style="width: 400px" type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }" :placeholder="$t('common:ruleMessage:specify')" maxlength="500"
show-word-limit />
</el-form-item>
</el-form>
<!-- 影响的任务列表 -->
<el-divider content-position="left">{{
$t('trials:reviewTrack:dialog:backImpactList')
}}</el-divider>
<div class="base-dialog-body">
<el-table v-loading="loading" :data="InfluenceTaskList" stripe height="100" style="min-height: 400px">
<!-- 任务编号 -->
@@ -733,7 +752,8 @@
</div>
<div slot="footer" class="dialog-footer" v-if="opentype === 'read'">
<!-- 取消 -->
<el-button :disabled="btnLoading" size="small" type="primary" @click="ReReadingOrBackVisible = false">
<el-button :disabled="btnLoading" size="small" type="primary"
@click="ReReadingOrBackVisible = false, backforReasonForm = { pmBackReason: null }">
{{ $t('common:button:cancel') }}
</el-button>
<!-- 确定 -->
@@ -928,6 +948,9 @@ export default {
Type: null,
RequestReReadingReason: null,
},
backforReasonForm: {
pmBackReason: null
},
rules: {
Type: [
{
@@ -943,6 +966,13 @@ export default {
trigger: 'blur',
},
],
pmBackReason: [
{
required: true,
message: this.$t('common:ruleMessage:specify'),
trigger: 'blur',
},
],
},
RefereeAllocationVisible: false,
trialId: this.$route.query.trialId,
@@ -1075,10 +1105,16 @@ export default {
this.loading = false
})
},
PMSetTaskBack() {
this.loading = true
PMSetTaskBack(this.trialId, this.rowData.Id)
.then((res) => {
async PMSetTaskBack() {
try {
let validate = await this.$refs.backReasonForm.validate()
console.log(validate, 'validate')
if (!validate) return false
// let data = Object.assign({}, this.backReasonForm)
this.loading = true
let res = await PMSetTaskBack(this.trialId, this.rowData.Id, this.backforReasonForm.pmBackReason)
this.loading = false
if (res.IsSuccess) {
this.loading = false
this.ReReadingOrBackVisible = false
this.getList()
@@ -1086,10 +1122,25 @@ export default {
this.$message.success(
this.$t('trials:reviewTrack:message:returnedSuccessfully')
)
})
.catch(() => {
this.loading = false
})
}
} catch (err) {
this.loading = false
console.log(err)
}
// this.loading = true
// PMSetTaskBack(this.trialId, this.rowData.Id)
// .then((res) => {
// this.loading = false
// this.ReReadingOrBackVisible = false
// this.getList()
// // '退'
// this.$message.success(
// this.$t('trials:reviewTrack:message:returnedSuccessfully')
// )
// })
// .catch(() => {
// this.loading = false
// })
},
openReReadingOrBackList(row) {
this.loading = true
@@ -1099,6 +1150,9 @@ export default {
.then((res) => {
this.InfluenceTaskList = res.Result
this.ReReadingOtherInfo = res.OtherInfo
this.backforReasonForm = {
pmBackReason: null
}
this.ReReadingOrBackVisible = true
this.loading = false
})
@@ -1113,7 +1167,7 @@ export default {
}
var token = getToken()
var path
if (row.ReadingTool === 0) {
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
@@ -16,7 +16,8 @@
<!-- 级别: -->
<el-form-item :label="$t('trials:qcCfg:table:level')">
<el-select v-model="searchData.IsDefeaultViewParent" style="width:120px;">
<el-option v-for="item of $d.IsDefeaultViewParentLevel" :key="item.id" :value="item.value" :label="item.label" />
<el-option v-for="item of $d.IsDefeaultViewParentLevel" :key="item.id" :value="item.value"
:label="item.label" />
</el-select>
</el-form-item>
<el-form-item>
@@ -25,87 +26,50 @@
</el-form-item>
</el-form>
<span style="margin-left:auto">
<el-button type="primary" size="mini" :disabled="selectArr.length<=0" @click="handleSubmit">
<el-button type="primary" size="mini" :disabled="selectArr.length <= 0" @click="handleSubmit">
{{ $t('common:button:submit') }}
</el-button>
</span>
</div>
<el-table
ref="qsList"
:data="list"
stripe
size="small"
height="400"
@selection-change="handleSelectChange"
@select="handleselect"
>
<el-table ref="qsList" :data="list" stripe size="small" height="400" @selection-change="handleSelectChange"
@select="handleselect" @select-all="handleselectAll">
<el-table-column type="selection" align="left" width="45" :selectable="handleSelectTable" />
<!-- 序号 -->
<el-table-column
prop="ShowOrder"
:label="$t('trials:qcCfg:table:order')"
width="60"
/>
<el-table-column prop="ShowOrder" :label="$t('trials:qcCfg:table:order')" width="60" />
<!-- 审核问题 -->
<el-table-column
prop="QuestionName"
:label="$t('trials:qcCfg:table:questionName')"
show-overflow-tooltip
/>
<el-table-column prop="QuestionName" :label="$t('trials:qcCfg:table:questionName')" show-overflow-tooltip />
<!-- 类型 -->
<el-table-column
prop="Type"
:label="$t('trials:qcCfg:table:type')"
show-overflow-tooltip
>
<el-table-column prop="Type" :label="$t('trials:qcCfg:table:type')" show-overflow-tooltip>
<template slot-scope="scope">
{{ $fd('QcType', scope.row.Type) }}
</template>
</el-table-column>
<!-- 选项 -->
<el-table-column
prop="TypeValue"
:label="$t('trials:qcCfg:table:typeValue')"
show-overflow-tooltip
min-width="110"
/>
<el-table-column prop="TypeValue" :label="$t('trials:qcCfg:table:typeValue')" show-overflow-tooltip
min-width="110" />
<!-- 父问题 -->
<el-table-column
prop="ParentShowOrder"
:label="$t('trials:qcCfg:table:parentQs')"
show-overflow-tooltip
width="120"
/>
<el-table-column prop="ParentShowOrder" :label="$t('trials:qcCfg:table:parentQs')" show-overflow-tooltip
width="120" />
<!-- 父问题触发值 -->
<el-table-column
prop="ParentTriggerValue"
:label="$t('trials:qcCfg:table:parentTriggerValue')"
show-overflow-tooltip
/>
<el-table-column prop="ParentTriggerValue" :label="$t('trials:qcCfg:table:parentTriggerValue')"
show-overflow-tooltip />
<!-- 是否必填 -->
<el-table-column
prop="IsRequired"
:label="$t('trials:qcCfg:table:isRequired')"
min-width="90"
>
<el-table-column prop="IsRequired" :label="$t('trials:qcCfg:table:isRequired')" min-width="90">
<template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsRequired) }}
</template>
</el-table-column>
<!-- 启用状态 -->
<el-table-column
prop="IsEnable"
:label="$t('trials:qcCfg:table:isEnable')"
min-width="120"
>
<el-table-column prop="IsEnable" :label="$t('trials:qcCfg:table:isEnable')" min-width="120">
<template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsEnable) }}
</template>
</el-table-column>
</el-table>
<Pagination style="margin-top: 10px" class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize" @pagination="getList" />
<Pagination style="margin-top: 10px" class="page" :total="total" :page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize" @pagination="getList" />
</box-content>
</template>
<script>
@@ -144,7 +108,7 @@ export default {
computed: {
...mapGetters(['language'])
},
created(){
created() {
this.trialId = this.$route.query.trialId;
this.getList();
},
@@ -165,7 +129,13 @@ export default {
}).catch(() => { this.loading = false })
},
handleSelectChange(val) {
this.selectArr = val
// this.selectArr = val
},
handleselectAll(selection) {
this.selectArr = []
if (selection.length > 0) {
this.selectArr = this.list
}
},
handleselect(selection, row) {
const selected = selection.length && selection.indexOf(row) !== -1
@@ -175,6 +145,22 @@ export default {
arr.map(item => {
this.$refs.qsList.toggleRowSelection(item, selected)
})
let list = [...selection, ...arr]
if (selected) {
list.forEach(item => {
if (!this.selectArr.find(d => d.Id === item.Id)) {
this.selectArr.push(item)
}
})
} else {
list = [row, ...arr]
list.forEach(item => {
let index = this.selectArr.indexOf(d => d.Id === item.Id)
if (!~index) {
this.selectArr.splice(index, 1)
}
})
}
},
handleSubmit() {
this.loading = true
@@ -64,6 +64,28 @@
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 测量工具 -->
<el-form-item
v-if="CriterionType === 0 && (form.ReadingTool === 0 || form.ReadingTool === 2 ) && form.ReadingVersionEnum === 1"
:label="$t('trials:readingUnit:readingRules:title:measureTool')"
>
<el-checkbox-group
v-model="form.ReadingToolList"
:disabled="
isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
"
>
<el-checkbox
v-for="tool in tools"
:key="tool.toolName"
:label="tool.toolName"
name="ReadingToolList"
>
{{ $t(`${tool.i18nKey}`) }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!--检查类型筛选-->
<el-form-item
:label="$t('trials:processCfg:form:IsImageFilter')"
@@ -593,6 +615,7 @@
</template>
<script>
import { getCriterionReadingInfo, setCriterionReadingInfo } from '@/api/trials'
import { config } from '@/views/trials/trials-panel/reading/dicoms3D/components/toolConfig'
export default {
name: 'ReadingRules',
props: {
@@ -614,6 +637,8 @@ export default {
TrialId: '',
ImagePlatform: null,
ReadingTool: 0,
ReadingVersionEnum: null,
ReadingToolList: [],
ReadingTaskViewEnum: null,
IsImageLabeled: null,
IsReadingShowSubjectInfo: null,
@@ -807,9 +832,14 @@ export default {
modalityList: [],
CriterionModalitys: [],
modalityIsCheck: false, //
tools: []
}
},
mounted() {
if (this.CriterionType === 0) {
this.tools = [...config.customizeStandards]
}
this.initPage()
},
computed: {
@@ -116,7 +116,7 @@
</el-form-item>
<!-- 模板 -->
<el-form-item
v-if="form.ClinicalUploadType === 1"
v-if="form.ClinicalUploadType === 1 && !isEN "
:label="$t('trials:processCfg:title:module')"
>
<div class="upload-container">
@@ -147,7 +147,7 @@
</el-form-item>
<!-- 英文模板 -->
<el-form-item
v-if="form.ClinicalUploadType === 1"
v-if="form.ClinicalUploadType === 1 && isEN "
:label="$t('trials:processCfg:title:enModule')"
>
<div class="upload-container">
@@ -222,7 +222,11 @@ export default {
required: true,
},
},
computed:{
isEN() {
return this.$i18n.locale !== 'zh'
},
},
data() {
return {
form: {
@@ -1248,7 +1248,7 @@ export default {
})
//
this.model_cfg2.title = `${this.$t('trials:auditRecord:title:viewRelationOperations')}--${this.$i18n.locale === 'en' ? this.currentRow.Description : this.currentRow.DescriptionCN}`
this.model_cfg2.title = `${n ? this.$t('trials:auditRecord:title:viewRelationOperations2') : this.$t('trials:auditRecord:title:viewRelationOperations')}--${this.$i18n.locale === 'en' ? this.currentRow.Description : this.currentRow.DescriptionCN}`
this.loading = true
getInspectionList(searchData).then((res) => {
this.list2 = res.CurrentPageData
@@ -359,7 +359,6 @@ export default {
status: 'add',
upload: null,
},
DATA: {},
doctorList: [],
}
},
@@ -632,12 +631,6 @@ export default {
},
immediate: true,
},
rowData: {
handler() {
this.DATA = Object.assign({}, this.rowData)
},
immediate: true,
},
},
created() {
let typeArr = ['', 'Report', 'Doc', 'Record', 'Reviewer', 'Template']
@@ -1,11 +1,11 @@
<template>
<div v-loading="loading" class="chat-wrapper">
<div v-if="recordContent.length>0" class="chat-content">
<div v-for="(record,index) in recordContent" :key="`${record.CreateTime}${index}`">
<div v-if="recordContent.length > 0" class="chat-content">
<div v-for="(record, index) in recordContent" :key="`${record.CreateTime}${index}`">
<div v-if="!record.IsCurrentUser" class="word">
<img v-if="record.UserTypeEnum*1 === 8" :src="adminAvatar" alt="Admin">
<img v-else-if="record.UserTypeEnum*1 === 1" :src="pmAvatar" alt="PM">
<img v-else-if="record.UserTypeEnum*1 === 2" :src="crcAvatar" alt="CRC">
<img v-if="record.UserTypeEnum * 1 === 8" :src="adminAvatar" alt="Admin">
<img v-else-if="record.UserTypeEnum * 1 === 1" :src="pmAvatar" alt="PM">
<img v-else-if="record.UserTypeEnum * 1 === 2" :src="crcAvatar" alt="CRC">
<div class="info">
<p class="user-info">
<span style="font-weight:700;">{{ record.CreateUserName }} </span>
@@ -24,9 +24,9 @@
<div class="info-content" v-html="record.TalkContent" />
</div>
<!-- <img :src="record.headUrl"> -->
<img v-if="record.UserTypeEnum*1 === 8" :src="adminAvatar" alt="Admin">
<img v-else-if="record.UserTypeEnum*1 === 1" :src="pmAvatar" alt="PM">
<img v-else-if="record.UserTypeEnum*1 === 2" :src="crcAvatar" alt="CRC">
<img v-if="record.UserTypeEnum * 1 === 8" :src="adminAvatar" alt="Admin">
<img v-else-if="record.UserTypeEnum * 1 === 1" :src="pmAvatar" alt="PM">
<img v-else-if="record.UserTypeEnum * 1 === 2" :src="crcAvatar" alt="CRC">
</div>
</div>
</div>
@@ -36,83 +36,50 @@
</div>
<div v-if="checkChallengeState !== 3 && checkState !== 11" class="chat-message">
<div class="message">
<el-input
v-model="newMessage"
v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']"
type="textarea"
:rows="2"
/>
<el-input v-model="newMessage" v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']"
type="textarea" :rows="2" />
</div>
<div class="function">
<!-- 申请回退 -->
<el-button
v-hasPermi="['trials:trials-panel:visit:consistency-check:apply-fallback']"
type="primary"
<el-button v-hasPermi="['trials:trials-panel:visit:consistency-check:apply-fallback']" type="primary"
:disabled="!(checkState === 10 && (requestBackState === 0 || requestBackState === 3))"
@click="handleApplyBack"
>
@click="handleApplyBack">
{{ $t('trials:consistencyCheck:action:applyFallback') }}
</el-button>
<!-- 拒绝回退 -->
<el-button
v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']"
type="primary"
:disabled="!(requestBackState === 1)"
@click="handleRejectBack"
>
<el-button v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']" type="primary"
:disabled="!(requestBackState === 1)" @click="handleRejectBack">
{{ $t('trials:consistencyCheck:button:rejectBack') }}
</el-button>
<!-- 回退 -->
<el-button
v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']"
type="primary"
:disabled="!(requestBackState === 1)"
@click="handleBack"
>
<el-button v-hasPermi="['trials:trials-panel:visit:consistency-check:fallback']" type="primary"
:disabled="!(requestBackState === 1)" @click="handleBack">
{{ $t('trials:consistencyCheck:action:fallback') }}
</el-button>
<!-- 发送 -->
<el-button
v-hasPermi="['role:pm']"
:disabled="!(checkState === 10 && checkChallengeState !== 3) || newMessage === ''"
type="primary"
:loading="btnLoading"
@click="handleReply()"
>
<el-button v-hasPermi="['role:pm']"
:disabled="!(checkState === 10 && checkChallengeState !== 3) || newMessage === ''" type="primary"
:loading="btnLoading" @click="handleReply()">
{{ $t('trials:consistencyCheck:button:sendMessage') }}
</el-button>
<!-- 回复 -->
<el-button
v-hasPermi="['role:crc']"
<el-button v-hasPermi="['role:crc']" type="primary"
:disabled="!(checkState === 10 && checkChallengeState !== 3) || (recordContent && recordContent.length > 0 ? recordContent[recordContent.length - 1].UserTypeEnum * 1 === 2 : false)"
type="primary"
:loading="btnLoading"
@click="handleCRCReply"
>
:loading="btnLoading" @click="handleCRCReply">
{{ $t('trials:consistencyCheck:title:reply') }}
</el-button>
<!-- 关闭 -->
<el-button
type="primary"
:loading="btnLoading"
@click="close"
>
<el-button type="primary" :loading="btnLoading" @click="close">
{{ $t('trials:consistencyCheck:title:close') }}
</el-button>
</div>
</div>
<!-- 回复一致性核查 -->
<el-dialog
v-if="sendMessageCRCVisible"
v-dialogDrag
:visible.sync="sendMessageCRCVisible"
:close-on-click-modal="false"
append-to-body
custom-class="base-dialog-wrapper"
:width="'600px'"
:title="$t('trials:consistencyCheck:title:replyConsistency')"
>
<crcSendMessage :crc-message-info="crcMessageInfo" @sendMessage="handleReply" />
<el-dialog v-if="sendMessageCRCVisible" v-dialogDrag :visible.sync="sendMessageCRCVisible"
:close-on-click-modal="false" append-to-body custom-class="base-dialog-wrapper" :width="'600px'"
:title="$t('trials:consistencyCheck:title:replyConsistency')">
<crcSendMessage :crc-message-info="crcMessageInfo" :visible.sync="sendMessageCRCVisible"
@sendMessage="handleReply" />
</el-dialog>
</div>
</template>
@@ -277,46 +244,54 @@ export default {
}
</script>
<style lang="scss">
.chat-wrapper{
::-webkit-scrollbar {
.chat-wrapper {
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.chat-content{
width:100%;
.chat-content {
width: 100%;
padding: 20px;
height: 500px;
overflow-y: auto;
.word{
.word {
display: flex;
margin-bottom: 20px;
img{
width:40px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info{
.info {
margin-left: 10px;
.user-info{
.user-info {
font-size: 12px;
color:rgba(51,51,51,0.8);
margin:0;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content{
.info-content {
padding: 10px;
font-size: 14px;
background-color: #ebeef5;
position: relative;
margin-top: 8px;
}
.info-content::before{
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
@@ -327,29 +302,34 @@ export default {
}
}
}
.word-my{
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img{
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info{
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.user-info{
.user-info {
font-size: 12px;
color: rgba(51,51,51,0.8);
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content{
.info-content {
position: relative;
max-width: 70%;
padding: 10px;
@@ -361,7 +341,8 @@ export default {
color: #fff;
text-align: left;
}
.info-content::after{
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
@@ -373,9 +354,11 @@ export default {
}
}
}
.chat-message{
.chat-message {
padding: 0 50px;
.function{
.function {
margin-top: 10px;
text-align: right;
}
@@ -6,98 +6,63 @@
<div>
<div style="padding: 10px;background: #f3f3f3;border-radius: 10px;margin-top: 10px">
<div style="display:flex;margin-bottom: 10px;justify-content: space-between;align-items: center">
<div>
<div style="font-size: 18px;font-weight: blod;color: #409EFF">
<!-- 请准确核实后录入当前访视的实际影像检查记录 -->
{{ $t('trials:consistencyCheck:title:message20') }}
</div>
<!-- 添加 -->
<el-button type="primary" size="mini" @click="addMsg">{{ $t('trials:consistencyCheck:title:add') }}</el-button>
<el-button type="primary" size="mini" @click="addMsg">{{ $t('trials:consistencyCheck:title:add')
}}</el-button>
</div>
<el-table
:data="msgList"
>
<el-table :data="msgList">
<el-table-column type="index" width="40" />
<!-- 检查日期 -->
<el-table-column
prop=""
:label="$t('trials:consistencyCheck:title:studyDate')"
show-overflow-tooltip
>
<el-table-column prop="" :label="$t('trials:consistencyCheck:title:studyDate')" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.StudyDate }}
</template>
</el-table-column>
<!-- 检查类型 -->
<el-table-column
prop=""
:label="$t('trials:consistencyCheck:title:studyModality')"
show-overflow-tooltip
>
<el-table-column prop="" :label="$t('trials:consistencyCheck:title:studyModality')" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.Modality }}
</template>
</el-table-column>
<!-- 已提交 -->
<el-table-column
prop=""
:label="$t('trials:consistencyCheck:title:submitted')"
show-overflow-tooltip
>
<el-table-column prop="" :label="$t('trials:consistencyCheck:title:submitted')" show-overflow-tooltip>
<template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsJoin) }}
</template>
</el-table-column>
<!-- 实际存在 -->
<el-table-column
prop=""
:label="$t('trials:consistencyCheck:title:existed')"
show-overflow-tooltip
>
<el-table-column prop="" :label="$t('trials:consistencyCheck:title:existed')" show-overflow-tooltip
min-width="120px">
<template slot-scope="scope">
<el-switch
v-model="scope.row.IsCheck"
:disabled="!scope.row.IsJoin"
@change="compareStudy"
/>
<el-switch v-model="scope.row.IsCheck" :active-text="$fd('YesOrNo', true)"
:inactive-text="$fd('YesOrNo', false)" :disabled="!scope.row.IsJoin" @change="compareStudy" />
</template>
</el-table-column>
<el-table-column
prop=""
width="100px"
:label="$t('common:action:action')"
fixed="right"
>
<el-table-column prop="" width="100px" :label="$t('common:action:action')" fixed="right">
<template slot-scope="scope">
<!-- 删除 -->
<el-button
:disabled="scope.row.IsJoin"
size="small"
type="text"
@click="moveMsg(scope.$index)"
>
{{ $t('trials:consistencyCheck:title:delete') }}
<el-button :disabled="scope.row.IsJoin" size="small" icon="el-icon-delete"
:title="$t('trials:consistencyCheck:title:delete')" circle @click="moveMsg(scope.$index)">
</el-button>
</template>
</el-table-column>
</el-table>
<el-form
ref="revenusForm"
size="small"
label-width="400px"
:label-position="'top'"
:rules="rules"
:model="form"
>
<el-form ref="revenusForm" size="small" label-width="400px" :label-position="'top'" :rules="rules" :model="form"
v-if="false">
<!-- IRC与实际情况影像检查不一致的原因为 -->
<el-form-item v-if="isLackOf" :label="$t('trials:consistencyCheck:title:title1')" prop="reason">
<el-checkbox-group v-model="form.reason" style="display: flex;flex-direction: column">
<el-checkbox style="display: block" :label="$t('trials:consistencyCheck:title:message9')">
<!-- <el-checkbox style="display: block" :label="$t('trials:consistencyCheck:title:message9')">
{{ $t('trials:consistencyCheck:title:message9') }}
</el-checkbox>
</el-checkbox> -->
<el-checkbox style="display: block" :label="$t('trials:consistencyCheck:title:message10')">
{{ $t('trials:consistencyCheck:title:message10') }}
</el-checkbox>
<el-checkbox style="display: block" :label="$t('trials:consistencyCheck:title:message11')">
<el-checkbox disabled style="display: block" :label="$t('trials:consistencyCheck:title:message11')">
{{ $t('trials:consistencyCheck:title:message11') }}
</el-checkbox>
</el-checkbox-group>
@@ -106,43 +71,29 @@
</div>
</div>
<div class="function">
<!-- 取消 -->
<el-button @click="close">
{{ $t('common:button:cancel') }}
</el-button>
<!-- 发送 -->
<el-button
type="primary"
@click="handleReply"
>
<el-button type="primary" @click="handleReply">
{{ $t('trials:consistencyCheck:button:sendMessage') }}
</el-button>
</div>
<!-- 添加检查 -->
<el-dialog
v-if="studyVisible"
v-dialogDrag
:visible.sync="studyVisible"
:close-on-click-modal="false"
append-to-body
custom-class="base-dialog-wrapper"
:width="'500px'"
:title="$t('trials:consistencyCheck:title:addStudy')"
>
<el-dialog v-if="studyVisible" v-dialogDrag :visible.sync="studyVisible" :close-on-click-modal="false"
append-to-body custom-class="base-dialog-wrapper" :width="'500px'"
:title="$t('trials:consistencyCheck:title:addStudy')">
<el-form>
<div class="base-dialog-body">
<!-- 检查日期 -->
<el-form-item label-width="80px" :label="$t('trials:consistencyCheck:title:studyDate')">
<el-date-picker
v-model="typeInfo.StudyDate"
style="width: 100%"
type="date"
value-format="yyyy-MM-dd"
/>
<el-date-picker v-model="typeInfo.StudyDate" style="width: 100%" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
<!-- 检查类型 -->
<el-form-item label-width="80px" :label="$t('trials:consistencyCheck:title:studyModality')">
<el-select
v-model="typeInfo.Modality"
style="width: 100%"
>
<el-option v-for="item of $d.Modality" :label="item.raw.Value" :value="item.value" :key="item.id"/>
<el-select v-model="typeInfo.Modality" style="width: 100%">
<el-option v-for="item of $d.Modality" :label="item.raw.Value" :value="item.value" :key="item.id" />
</el-select>
</el-form-item>
</div>
@@ -171,6 +122,10 @@ export default {
TalkContent: null
}
}
},
visible: {
type: Boolean,
default: false
}
},
data() {
@@ -188,7 +143,7 @@ export default {
EDCList: [],
IRCList: [],
form: {
reason: []
reason: [this.$t('trials:consistencyCheck:title:message11')]
},
rules: {
reason: [{ required: true, message: this.$t('common:ruleMessage:select'), trigger: ['blur', 'change'] }]
@@ -196,12 +151,15 @@ export default {
}
},
mounted() {
this.crcMessageInfo.ParamInfoList.forEach(v => {
this.msgList.push({ ...v, IsJoin: true, Ischeck: false })
this.crcMessageInfo.ParamInfoList.forEach((v, i) => {
this.msgList.push({ ...v, IsJoin: true, IsCheck: false, Id: this.getGuid(String(Date.now() + i)) })
})
this.compareStudy()
},
methods: {
close() {
this.$emit('update:visible', false)
},
compareStudy() {
var isLackOf = false
this.IRCList = []
@@ -217,7 +175,9 @@ export default {
this.studyVisible = false
this.typeInfo = {
StudyDate: null,
Modality: null
Modality: null,
IsJoin: false,
IsCheck: true
}
},
handleSave() {
@@ -225,7 +185,7 @@ export default {
this.$alert(this.$t('trials:consistencyCheck:title:title2'))// ''
return
}
this.msgList.push({ ...this.typeInfo })
this.msgList.push({ ...this.typeInfo, Id: this.getGuid(String(Date.now())) })
this.compareStudy()
this.studyVisible = false
this.typeInfo = {
@@ -248,14 +208,22 @@ export default {
this.compareStudy()
})
},
handleReply() {
if (this.msgList.length === 0) {
// ''
this.$alert(this.$t('trials:consistencyCheck:title:title4'))
return
}
this.$refs.revenusForm.validate((valid) => {
if (!valid) return
async handleReply() {
try {
if (this.msgList.length === 0) {
// ''
this.$alert(this.$t('trials:consistencyCheck:title:title4'))
return
}
// let valid = await this.$refs.revenusForm.validate()
// if (!valid) return
let confirm = true
if (this.msgList.every(item => !item.IsCheck)) {
confirm = await this.$confirm(this.$t('trials:consistencyCheck:title:confirm'), '', {
showConfirmButton: false
})
}
if (!confirm) return false
var TalkContent = ''
// '访<br>'
TalkContent += `${this.$t('trials:consistencyCheck:title:title5')}<br>`
@@ -263,44 +231,61 @@ export default {
// `${i + 1}.${v.StudyDate}${v.Modality}<br>`
let msg = this.$t('trials:consistencyCheck:title:title6').replace('yyyy', v.StudyDate)
msg = msg.replace('xxx', v.Modality)
TalkContent += `${i + 1}. ${msg}<br>`
let message = `${i + 1}. ${msg}`
let index = this.IRCList.findIndex(item => item.Id === v.Id)
if (!!~index) {
let data = this.IRCList[index]
message += `${data.IsJoin && !data.IsCheck ? this.$t('trials:consistencyCheck:title:title8') : this.$t('trials:consistencyCheck:title:title9')}<br>`
} else {
message += `${this.$t('trials:consistencyCheck:title:title12')}<br>`
}
TalkContent += message
})
if (this.isLackOf) {
TalkContent += '<br>'
// 'IRC<br>'
TalkContent += `${this.$t('trials:consistencyCheck:title:title7')}<br>`
this.IRCList.forEach((v, i) => {
let msg = this.$t('trials:consistencyCheck:title:title6').replace('yyyy', v.StudyDate)
msg = msg.replace('xxx', v.Modality)
TalkContent += `${i + 1}.${msg}${v.IsJoin && !v.IsCheck ? this.$t('trials:consistencyCheck:title:title8') : this.$t('trials:consistencyCheck:title:title9')}<br>`
})
if (this.form.reason.length > 0) {
TalkContent += '<br>'
// 'IRC<br>'
TalkContent += `${this.$t('trials:consistencyCheck:title:title10')}<br>`
TalkContent += `<br>${this.$t('trials:consistencyCheck:title:title13')}`
// TalkContent += '<br>'
// // 'IRC<br>'
// TalkContent += `${this.$t('trials:consistencyCheck:title:title7')}<br>`
// this.IRCList.forEach((v, i) => {
// let msg = this.$t('trials:consistencyCheck:title:title6').replace('yyyy', v.StudyDate)
// msg = msg.replace('xxx', v.Modality)
// TalkContent += `${i + 1}.${msg}${v.IsJoin && !v.IsCheck ? this.$t('trials:consistencyCheck:title:title8') : this.$t('trials:consistencyCheck:title:title9')}<br>`
// })
this.form.reason.forEach((v, i) => {
TalkContent += `${i + 1}.${v}<br>`
})
}
// if (this.form.reason.length > 0) {
// TalkContent += '<br>'
// // 'IRC<br>'
// TalkContent += `${this.$t('trials:consistencyCheck:title:title10')}<br>`
// this.form.reason.forEach((v, i) => {
// TalkContent += `${i + 1}.${v}<br>`
// })
// }
} else {
// `<br>IRC`
TalkContent += `<br>${this.$t('trials:consistencyCheck:title:title11')}`
// TalkContent += '<br>'
// TalkContent += `${this.$t('trials:consistencyCheck:title:title10')}<br>`
// TalkContent += `1.${this.$t('trials:consistencyCheck:title:message10')}<br>`
}
console.log(TalkContent)
this.$emit('sendMessage', TalkContent, this.closeLoading)
})
} catch (err) {
console.log(err)
}
}
}
}
</script>
<style scoped>
.function{
.function {
margin-top: 10px;
text-align: right;
}
::v-deep .el-form--label-top .el-form-item__label{
::v-deep .el-form--label-top .el-form-item__label {
padding-bottom: 0;
padding-top: 20px;
}
@@ -287,7 +287,7 @@
<el-table-column
v-if="hasPermi(['trials:trials-panel:visit:crc-question:send'])"
:label="$t('common:action:action')"
width="100"
min-width="100"
fixed="right"
>
<template slot-scope="scope">
@@ -1459,6 +1459,11 @@ export default {
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
if (
Math.abs(dicomInfo.uploadFileSize - dicomInfo.fileSize) < 5000
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
}
)
if (!res || !res.url) {
@@ -320,7 +320,7 @@
scope.row.IsLostVisit
" circle :title="$t('trials:crcUpload:action:submit')" @click="handleSubmit(scope.row)" />
<!-- 退回 -->
<el-button :disabled="scope.row.SubmitState * 1 < 2 || scope.row.IsImageBackApplying
<el-button :disabled="scope.row.SubmitState * 1 < 2 || scope.row.IsImageBackApplying || scope.row.ChallengeState * 1 === 2
" v-hasPermi="['trials:trials-panel:visit:crc-upload:back']" icon="el-icon-back" circle
:title="$t('trials:crcUpload:action:back')" @click="imageBack(scope.row)" />
<!-- 编辑 -->
@@ -1007,15 +1007,30 @@ export default {
// 退
async imageBack(row) {
try {
let confirm = await this.$confirm(this.$t("trials:crcUpload:confirmMessage:imageBack"))
if (!confirm) return false
let params = {
SubjectVisitId: row.Id
}
let res = await requestImageBack(params)
if (res.IsSuccess) {
this.getList()
}
this.$prompt(`<span style="color:#F56C6C">*</span><span>${this.$t("trials:crcUpload:confirmMessage:ApplyReason")}</span>`, this.$t("trials:crcUpload:confirmMessage:imageBack"), {
confirmButtonText: this.$t("common:button:save"),
cancelButtonText: this.$t("common:button:cancel"),
dangerouslyUseHTMLString: true,
inputValidator: (val) => {
if (!val) return this.$t("common:ruleMessage:specify")
},
inputErrorMessage: this.$t("common:ruleMessage:specify")
}).then(async ({ value }) => {
let params = {
SubjectVisitId: row.Id,
ApplyReason: value
}
// return console.log(params)
let res = await requestImageBack(params)
if (res.IsSuccess) {
this.getList()
}
}).catch(() => {
});
// let confirm = await this.$confirm(this.$t("trials:crcUpload:confirmMessage:imageBack"))
// if (!confirm) return false
} catch (err) {
console.log(err)
}
@@ -1758,7 +1758,7 @@ export default {
let obj = {
name: `${data.SubjectCode}_${data.VisitName}_${this.$fd(
'IsDicom',
true
false
)}/${study.StudyCode}_${study.ImageDate.split(' ')[0]}_${
study.Modality
}/${item.FileName}`,
@@ -345,14 +345,14 @@
<el-button v-hasPermi="['trials:trials-panel:visit:qc-check:receive']"
:title="$t('trials:qcCheck:button:receive')" circle icon="el-icon-lock" :disabled="scope.row.PreliminaryAuditUserId === userId ||
scope.row.IsTake ||
scope.row.AuditState > 6
scope.row.AuditState > 6 || scope.row.IsImageBackApplying
" @click="handleReciveTask(scope.row, true)" />
<!-- 释放当前领取人才能释放 -->
<!-- v-if="(scope.row.IsTake && (scope.row.AuditState*1 === 4 || scope.row.AuditState*1 === 6))" -->
<el-button v-hasPermi="['trials:trials-panel:visit:qc-check:release']"
:title="$t('trials:qcCheck:button:release')" circle icon="el-icon-unlock" :disabled="!(scope.row.CurrentActionUserId === userId) ||
!scope.row.IsTake ||
scope.row.AuditState * 1 > 6
scope.row.AuditState * 1 > 6 || scope.row.IsImageBackApplying
" @click="handleReciveTask(scope.row, false)" />
<!-- 替换1针对同一访视质控初审人不能替换复审人2当前领取人不能替换自己 -->
<!-- v-if="(scope.row.IsTake && (scope.row.AuditState*1 === 4 || scope.row.AuditState*1 === 6))" -->
@@ -360,7 +360,7 @@
:title="$t('trials:crcQuestion:button:replaceTask')" circle icon="el-icon-refresh" :disabled="scope.row.CurrentActionUserId === userId ||
scope.row.PreliminaryAuditUserId === userId ||
!scope.row.IsTake ||
scope.row.AuditState * 1 > 6
scope.row.AuditState * 1 > 6 || scope.row.IsImageBackApplying
" @click="handleReplaceTask(scope.row)" />
<!-- 审核 -->
<!-- v-if="scope.row.QCProcessEnum === 1 && scope.row.AuditState*1 === 4 && scope.row.IsTake" -->
@@ -369,7 +369,7 @@
? scope.row.CurrentActionUserId !== userId
: false) ||
!scope.row.IsTake ||
scope.row.AuditState * 1 > 6
scope.row.AuditState * 1 > 6 || scope.row.IsImageBackApplying
" @click="handlePrimaryQC(scope.row)" />
<!-- 初审 -->
<!-- v-if="scope.row.QCProcessEnum === 2 && scope.row.AuditState*1 === 4 && scope.row.IsTake" -->
@@ -380,7 +380,7 @@
? scope.row.CurrentActionUserId !== userId
: false) ||
!scope.row.IsTake ||
scope.row.AuditState * 1 > 6
scope.row.AuditState * 1 > 6 || scope.row.IsImageBackApplying
" @click="handlePrimaryQC(scope.row)" />
<!-- 复审 -->
<!-- v-if="scope.row.QCProcessEnum === 2 && scope.row.AuditState*1 === 6 && scope.row.IsTake && scope.row.PreliminaryAuditUserId!==userId" -->
@@ -391,9 +391,10 @@
!scope.row.IsTake ||
scope.row.AuditState * 1 > 6 ||
scope.row.CurrentActionUserId !== userId
" @click="handleSecondaryQC(scope.row)" />
|| scope.row.IsImageBackApplying" @click="handleSecondaryQC(scope.row)" />
<!-- 退回 -->
<el-button :disabled="scope.row.SubmitState * 1 < 2 || scope.row.IsImageBackApplying"
<el-button
:disabled="scope.row.SubmitState * 1 < 2 || scope.row.IsImageBackApplying || scope.row.ChallengeState * 1 === 2"
v-hasPermi="['trials:trials-panel:visit:qc-check:back']" icon="el-icon-back" circle
:title="$t('trials:qcCheck:button:back')" @click="imageBack(scope.row)" />
</div>
@@ -697,15 +698,30 @@ export default {
// 退
async imageBack(row) {
try {
let confirm = await this.$confirm(this.$t("trials:qcCheck:confirmMessage:imageBack"))
if (!confirm) return false
let params = {
SubjectVisitId: row.Id
}
let res = await requestImageBack(params)
if (res.IsSuccess) {
this.getList()
}
this.$prompt(`<span style="color:#F56C6C">*</span><span>${this.$t("trials:crcUpload:confirmMessage:ApplyReason")}</span>`, this.$t("trials:crcUpload:confirmMessage:imageBack"), {
confirmButtonText: this.$t("common:button:save"),
cancelButtonText: this.$t("common:button:cancel"),
dangerouslyUseHTMLString: true,
inputValidator: (val) => {
if (!val) return this.$t("common:ruleMessage:specify")
},
inputErrorMessage: this.$t("common:ruleMessage:specify")
}).then(async ({ value }) => {
let params = {
SubjectVisitId: row.Id,
ApplyReason: value
}
// return console.log(params)
let res = await requestImageBack(params)
if (res.IsSuccess) {
this.getList()
}
}).catch(() => {
});
// let confirm = await this.$confirm(this.$t("trials:crcUpload:confirmMessage:imageBack"))
// if (!confirm) return false
} catch (err) {
console.log(err)
}
@@ -108,17 +108,17 @@
</el-table-column>
<!-- 中心编号 -->
<el-table-column prop="TrialSiteCode" :label="$t('trials:reuploadAudit:table:siteId')"
show-overflow-tooltip sortable="custom" />
show-overflow-tooltip sortable="custom" min-width="120px" />
<!-- 受试者编号 -->
<el-table-column prop="SubjectCode" :label="$t('trials:reuploadAudit:table:subjectId')"
show-overflow-tooltip sortable="custom" width="120px" />
show-overflow-tooltip sortable="custom" min-width="120px" />
<!-- 访视名称 -->
<el-table-column prop="VisitName" :label="$t('trials:reuploadAudit:table:visitName')"
show-overflow-tooltip sortable="custom">
show-overflow-tooltip sortable="custom" min-width="120px">
</el-table-column>
<!-- 最晚拍片日期 -->
<el-table-column prop="LatestScanDate" :label="$t('trials:reuploadAudit:table:lScanDate')"
show-overflow-tooltip width="170" sortable="custom">
show-overflow-tooltip min-width="170" sortable="custom">
<template slot-scope="scope">
<el-tooltip class="item" effect="dark" :content="`${scope.row.EarliestScanDate
? moment(scope.row.EarliestScanDate).format('YYYY-MM-DD')
@@ -139,40 +139,49 @@
</el-table-column>
<!-- 提交时间 -->
<el-table-column prop="SubmitTime" :label="$t('trials:reuploadAudit:table:SubmitTime')"
show-overflow-tooltip sortable="custom" width="150px" />
show-overflow-tooltip sortable="custom" min-width="150px" />
<!-- 申请人 -->
<el-table-column prop="CreateUserFullName" :label="$t('trials:reuploadAudit:table:createUserFullName')"
show-overflow-tooltip sortable="custom" />
show-overflow-tooltip sortable="custom" min-width="120px" />
<!-- 申请人角色 -->
<el-table-column prop="ApplyUserRole" :label="$t('trials:reuploadAudit:table:applyUserRole')"
show-overflow-tooltip sortable="custom" width="120px">
show-overflow-tooltip sortable="custom" min-width="120px">
<template slot-scope="scope">
<span>{{ $fd('ImageBackApplyEnum',
scope.row.ApplyUserRole) }}</span>
</template>
</el-table-column>
<!-- 申请原因 -->
<el-table-column prop="ApplyReason" :label="$t('trials:reuploadAudit:table:ApplyReason')"
show-overflow-tooltip sortable="custom" min-width="120px" />
<!-- 申请时间 -->
<el-table-column prop="CreateTime" :label="$t('trials:reuploadAudit:table:createTime')"
show-overflow-tooltip sortable="custom" width="150px" />
show-overflow-tooltip sortable="custom" min-width="150px" />
<!-- 审核状态 -->
<el-table-column prop="ImageBackState" :label="$t('trials:reuploadAudit:table:imageBackState')"
show-overflow-tooltip sortable="custom">
show-overflow-tooltip sortable="custom" min-width="120px">
<template slot-scope="scope">
<el-tag :type="['warning', '', 'danger'][scope.row.ImageBackState]">{{ $fd('ImageBackstateEnum',
scope.row.ImageBackState) }}</el-tag>
</template>
</el-table-column>
<!-- 备注 -->
<el-table-column prop="ResultRemark" :label="$t('trials:reuploadAudit:table:ResultRemark')"
show-overflow-tooltip sortable="custom" min-width="120px" />
<!-- 审核时间 -->
<el-table-column prop="AuditTime" :label="$t('trials:reuploadAudit:table:auditTime')"
show-overflow-tooltip sortable="custom" width="150px" />
<el-table-column :label="$t('common:action:action')" fixed="right">
show-overflow-tooltip sortable="custom" min-width="150px" />
<el-table-column :label="$t('common:action:action')" fixed="right"
v-if="hasPermi(['trials:trials-panel:visit:reupload-audit:agree'])" min-width="120px">
<template slot-scope="scope">
<el-button :title="$t('trials:reuploadAudit:button:auditYes')" circle
:disabled="scope.row.ImageBackState > 0" icon="el-icon-check" size="mini"
@click="audit(scope.row, 1)" />
<el-button :title="$t('trials:reuploadAudit:button:auditNo')"
<el-button :title="$t('trials:reuploadAudit:button:imageBack')"
v-hasPermi="['trials:trials-panel:visit:reupload-audit:agree']" circle
:disabled="scope.row.ImageBackState > 0" icon="el-icon-edit-outline" size="mini"
@click="audit(scope.row)" />
<!-- <el-button :title="$t('trials:reuploadAudit:button:auditNo')"
v-hasPermi="['trials:trials-panel:visit:reupload-audit:refuse']"
:disabled="scope.row.ImageBackState > 0" circle icon="el-icon-close" size="mini"
@click="audit(scope.row, 2)" />
@click="audit(scope.row, 2)" /> -->
</template>
</el-table-column>
</el-table>
@@ -181,6 +190,38 @@
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
<base-model v-if="config.visible" :config="config">
<template slot="dialog-body">
<el-form ref="imageBackform" :model="form" class="demo-form-inline" :rules="rules"
:label-width="isEN ? '150px' : '100px'">
<el-form-item :label="$t('trials:reuploadAudit:table:Matters')" prop="Matters">
<span>{{ rowData.title }}</span>
</el-form-item>
<el-form-item :label="$t('trials:reuploadAudit:form:ApplyReason')" prop="ApplyReason">
<span>{{ rowData.ApplyReason }}</span>
</el-form-item>
<el-form-item :label="$t('trials:reuploadAudit:form:IsAgree')" prop="IsAgree">
<el-radio-group v-model="form.IsAgree">
<el-radio :label="true">{{ $t('trials:reuploadAudit:button:auditYes') }}</el-radio>
<el-radio :label="false">{{ $t('trials:reuploadAudit:button:auditNo') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('trials:reuploadAudit:form:ResultRemark')" prop="ResultRemark">
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" placeholder=""
v-model="form.ResultRemark">
</el-input>
</el-form-item>
</el-form>
</template>
<template slot="dialog-footer">
<el-button type="primary" @click="auditImageBack" :loading="loading">
{{ $t('common:button:confirm') }}
</el-button>
<el-button @click="config.visible = false" :loading="loading">
{{ $t('common:button:cancel') }}
</el-button>
</template>
</base-model>
</BaseContainer>
</template>
<script>
@@ -194,6 +235,7 @@ import {
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import moment from 'moment'
import baseModel from '@/components/BaseModel'
const searchDataDefault = () => {
return {
@@ -219,7 +261,8 @@ export default {
name: 'QcCheck',
components: {
BaseContainer,
Pagination
Pagination,
baseModel
},
data() {
return {
@@ -233,9 +276,34 @@ export default {
timeListC: [],
timeList: [],
trialId: null,
moment
moment,
rowData: {},
config: {
visible: false,
title: this.$t("trials:reuploadAudit:confirmMessage:imageBack"),
width: '500px',
appendToBody: true,
},
form: {
IsAgree: null,
ResultRemark: null
},
rules: {
IsAgree: [
{
required: true,
message: this.$t('common:ruleMessage:select'),
trigger: ['blur', 'change'],
},
],
}
}
},
computed: {
isEN() {
return this.$i18n.locale !== 'zh'
},
},
watch: {
list(val) {
this.doLayout()
@@ -249,26 +317,39 @@ export default {
this.getVisitPlanOptions()
},
methods: {
async audit(row, state) {
async audit(row) {
try {
let message = this.$t("trials:reuploadAudit:confirmMessage:imageBack").replace("xxx", this.$fd("ImageBackstateEnum", state))
let confirm = await this.$confirm(message)
if (!confirm) return false
let params = {
IamgeBackRecordId: row.Id,
IsAgree: state === 1 ? true : false
}
this.loading = true
let res = await auditImageBack(params)
this.loading = false
if (res.IsSuccess) {
this.getList()
}
this.rowData = Object.assign({}, row)
this.rowData.title = `${row.CreateUserFullName} (${this.$fd('ImageBackApplyEnum', row.ApplyUserRole)}) ${this.$t('trials:reuploadAudit:title:imageBack').replace('xxx', row.SubjectCode).replace('yyy', row.VisitName)}`
Object.keys(this.form).forEach(key => {
this.form[key] = null
})
this.config.visible = true
} catch (err) {
console.log(err)
this.loading = false
}
},
async auditImageBack() {
try {
let validate = await this.$refs.imageBackform.validate()
if (!validate) return false
let params = Object.assign({}, this.form)
params.IamgeBackRecordId = this.rowData.Id
// return console.log(params)
this.loading = true
let res = await auditImageBack(params)
this.loading = false
if (res.IsSuccess) {
this.config.visible = false
this.getList()
}
} catch (err) {
this.loading = false
console.log(err)
}
},
getList() {
this.loading = true
this.searchData.TrialId = this.trialId
@@ -15,15 +15,21 @@
<div class="line"></div>
<div class="contextmenu__item" @click="handleMenu('copy')">
<i class="icon icon_copy" />
<span>{{ $t('trials:trials-workbench:auditDocument:menu:copy') }}</span>
<span>{{ $t('trials:trials-workbench:auditDocument:menu:copy') }}
<span class="shortcut">{{ $t('trials:trials-workbench:auditDocument:shortcut:copy') }}</span>
</span>
</div>
<div class="contextmenu__item" @click="handleMenu('shear')">
<i class="icon icon_shear" />
<span>{{ $t('trials:trials-workbench:auditDocument:menu:shear') }}</span>
<span>{{ $t('trials:trials-workbench:auditDocument:menu:shear') }}
<span class="shortcut">{{ $t('trials:trials-workbench:auditDocument:shortcut:shear') }}</span>
</span>
</div>
<div class="contextmenu__item" @click="handleMenu('stickup')" v-if="checkList.length <= 1 && isCopy">
<i class="icon icon_stickup" />
<span>{{ $t('trials:trials-workbench:auditDocument:menu:stickup') }}</span>
<span>{{ $t('trials:trials-workbench:auditDocument:menu:stickup') }}
<span class="shortcut">{{ $t('trials:trials-workbench:auditDocument:shortcut:stickup') }}</span>
</span>
</div>
<div class="contextmenu__item" @click="handleMenu('rename')" v-show="checkList.length <= 1">
<i class="icon icon_rename" />
@@ -197,6 +203,21 @@ export default {
margin-right: 8px;
}
.shortcut {
display: inline-block;
font-size: 12px;
zoom: .95;
font-weight: 200;
color: #888;
padding-left: 10px;
position: absolute;
right: 10px;
width: 65px;
min-width: 50px;
text-align: right;
overflow: hidden;
}
/*打开*/
.icon_open {
color: #1890ff;
@@ -83,9 +83,13 @@
@click="addRenameId(scope.row)"
:title="$t('trials:trials-workbench:auditDocument:icon:rename')" />
</span>
<i :class="{ 'el-icon-circle-check': true, 'icon_check': true, isCheck: checkList.includes(scope.row.Id) }"
<i :class="{ 'el-icon-success': true, 'icon_check': true, isCheck: checkList.includes(scope.row.Id) }"
@click.stop="addCheck(scope.row)"
v-if="(hoverId === scope.row.Id || checkList.includes(scope.row.Id)) && isManage" />
<span class="version"
v-if="(hoverId !== scope.row.Id && !checkList.includes(scope.row.Id)) && isManage && scope.row.HistoricalVersionsCount > 0">
<i class="el-icon-warning-outline"></i>{{ scope.row.HistoricalVersionsCount }}
</span>
</div>
<el-input v-model="scope.row.Name" :ref="`renameInp_${scope.row.Id}`" :autofocus="true"
class="renameInp" @blur="rename(scope.row)" v-else />
@@ -381,24 +385,25 @@ export default {
async downLoad(list, row = {}) {
if (!this.isManage) return false
try {
if (row.Id) {
if (row.Id && row.AuditDocumentTypeEnum) {
let name = row.Name.split('.')[1] ? row.Name : `${row.Name}.${row.FileFormat}`
return await downLoadFile(
this.OSSclientConfig.basePath + row.FilePath,
row.Name
name
)
}
let arr = this.filterExternalIds(this.tableData, list)
if (arr && arr.length === 1) {
let data = this.getData(this.tableData, arr[0])
if (data.AuditDocumentTypeEnum) {
let name = data.Name.split('.')[1] ? data.Name : `${data.Name}.${data.FileFormat}`
return await downLoadFile(
this.OSSclientConfig.basePath + data.FilePath,
data.Name
name
)
} else {
let dataArray = this.getDataPath([data])
let files = this.formatDownloadFile(dataArray)
console.log(files, 'files')
let name = `${data.Name}_${new Date().getTime()}.zip`
return await downLoadFile(files, name, 'zip')
}
@@ -484,7 +489,6 @@ export default {
},
getDataPath(list, str = '', ARRAY = []) {
for (const item of list) {
console.log(item.AuditDocumentTypeEnum, str)
if (!str) {
item.Path = item.Name
} else {
@@ -692,6 +696,8 @@ export default {
async delData(Ids) {
if (!this.isManage) return false
try {
let confirm = await this.$confirm(this.$t('trials:trials-workbench:auditDocument:delData'))
if (!confirm) return false
let data = { Ids }
let res = await deleteAuditDocument(data)
if (res.IsSuccess) {
@@ -736,7 +742,6 @@ export default {
//
async uplaodFile(list) {
if (!this.isManage) return false
console.log(list, 'list')
let data = []
if (this.config.isVersion) {
data.push({
@@ -809,7 +814,6 @@ export default {
strKey.push(str)
})
console.log(obj, 'obj')
obj.children.push({
ParentId: null,
Name: item.FileName,
@@ -964,6 +968,9 @@ export default {
},
//
handleRowDblclick(row, column, event) {
if (this.$refs.contextmenu) {
this.$refs.contextmenu.foo()
}
//
let isExpandButton = event.target.closest('.el-table__expand-icon');
if (isExpandButton) return; //
@@ -1022,7 +1029,7 @@ export default {
handleMenu(data) {
if (!this.isManage) return false
let { key, row } = data
console.log(key, row)
// console.log(key, row)
this.type = key;
if (key === 'rename') {
// this.renameId = this.checkList[0]
@@ -1104,19 +1111,25 @@ export default {
e.preventDefault();
}
if (e.ctrlKey && e.key === 'c') {
e.preventDefault();
this.type = 'copy'
this.copy()
// e.preventDefault();
if (this.checkList.length > 0 && !this.renameId) {
this.type = 'copy'
this.copy()
}
}
if (e.ctrlKey && e.key === 'v') {
e.preventDefault();
// e.preventDefault();
this.type = 'stickup'
this.stickup()
}
if (e.ctrlKey && e.key === 'x') {
e.preventDefault();
this.type = 'shear'
this.shear()
// e.preventDefault();
if (this.checkList.length > 0 && !this.renameId) {
this.type = 'shear'
this.shear()
}
}
},
//
@@ -1131,8 +1144,8 @@ export default {
mounted() {
document.addEventListener('keydown', this.keydown);
document.addEventListener('keyup', this.keyup);
let table = document.getElementById("auditDocumentTable")
table.addEventListener('click', this.docClick);
// let table = document.getElementById("auditDocumentTable")
document.addEventListener('click', this.docClick);
this.getList()
this.getBreadcrumbData()
},
@@ -1185,9 +1198,9 @@ export default {
}
.last_catalogue {
width: 24px;
height: 24px;
line-height: 24px;
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
border: 1px solid #ddd;
// cursor: pointer;
@@ -1207,8 +1220,8 @@ export default {
border-left: none;
padding: 0 10px;
border-radius: 0 4px 4px 0;
height: 24px;
line-height: 24px;
height: 36px;
line-height: 36px;
}
.catalogue_name {
@@ -1226,6 +1239,7 @@ export default {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
.name_box {
display: flex;
@@ -1241,6 +1255,22 @@ export default {
text-overflow: ellipsis;
}
}
.version {
position: absolute;
right: 0;
top: 2px;
background: #ff980021;
color: #ff9800;
font-size: 12px;
padding: 0 3px;
border-radius: 2px;
z-index: 1;
i {
margin-right: 3px;
}
}
}
.icon_edit {
@@ -1254,13 +1284,15 @@ export default {
}
.icon_check {
font-size: 18px;
font-size: 22px;
cursor: pointer;
color: rgba(0, 0, 0, 0.3);
&:hover {
color: rgba(0, 0, 0, 0.5);
}
z-index: 9;
}
.isCheck {
@@ -1271,6 +1303,20 @@ export default {
background-color: #e5f3ff
}
::v-deep .el-table {
.caret-wrapper {
height: 24px;
}
.sort-caret.ascending {
top: 0;
}
.sort-caret.descending {
bottom: 3px;
}
}
.icon_image {
max-height: 20px;
max-width: 20px;
@@ -33,7 +33,8 @@
<el-table-column :label="$t('trials:sysDocBeSigned:table:AttachmentCount')" prop="AttachmentCount"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row)">{{ scope.row.AttachmentCount }}</el-button>
<el-button type="text" @click.stop="openAttachment(scope.row, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('trials:sysDocBeSigned:table:uploadTime')" prop="CreateTime" show-overflow-tooltip
@@ -55,8 +56,8 @@
<!-- 预览/签署文件 -->
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="title" :fullscreen="true" append-to-body
custom-class="base-dialog-wrapper">
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;" @click.stop="openAttachment(currentRow)"
v-if="currentRow.AttachmentCount">{{
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;font-size: 18px;color:#409EFF"
@click.stop="openAttachment(currentRow)" v-if="currentRow.AttachmentCount">{{
$t('trials:sysDocBeSigned:table:AttachmentCount') }} ({{
currentRow.AttachmentCount }})</span>
<span v-if="!currentIsConfirm" style="position: fixed; right: 75px; top: 14px">
@@ -91,7 +92,10 @@
<SignForm :is-system-doc="currentRow.IsSystemDoc" :document-id="currentRow.Id" :file-name="fileName"
@closeDialog="closeSignDialog" />
</el-dialog>
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" v-if="perview_visible" />
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
:isView="true" @getList="getList" />
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" :isView="true"
v-if="perview_visible" />
</div>
</template>
<script>
@@ -104,6 +108,7 @@ import Pagination from '@/components/Pagination'
import PreviewFile from '@/components/PreviewFile/index'
import SignForm from '@/views/trials/trials-panel/attachments/self-attachment/components/SignForm'
import attachmentPreview from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentPreview'
import attachmentList from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentList'
import { mapGetters } from 'vuex'
const searchDataDefault = () => {
return {
@@ -115,7 +120,7 @@ const searchDataDefault = () => {
}
export default {
name: 'NeedSignSysDoc',
components: { Pagination, PreviewFile, SignForm, attachmentPreview },
components: { Pagination, PreviewFile, SignForm, attachmentPreview, attachmentList },
props: {
isSigned: {
type: Boolean,
@@ -143,7 +148,12 @@ export default {
currentUser: zzSessionStorage.getItem('userName'),
currentRow: {},
SystemDocumentId: null,
perview_visible: false
perview_visible: false,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -158,10 +168,15 @@ export default {
...mapGetters(['IsFirstSysDocNeedSign']),
},
methods: {
openAttachment(row) {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.perview_visible = true
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
//
handlePreview(row) {
@@ -28,7 +28,8 @@
<el-table-column :label="$t('trials:sysDocBeSigned:table:AttachmentCount')" prop="AttachmentCount"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row)">{{ scope.row.AttachmentCount }}</el-button>
<el-button type="text" @click.stop="openAttachment(scope.row, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('trials:sysDocBeSigned:table:uploadTime')" prop="CreateTime" show-overflow-tooltip
@@ -49,8 +50,8 @@
<!-- 预览/签署文件 -->
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="title" :fullscreen="true" append-to-body
custom-class="base-dialog-wrapper">
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;" @click.stop="openAttachment(currentRow)"
v-if="currentRow.AttachmentCount">{{
<span style="position: fixed; left: 16px; top: 45px;cursor: pointer;font-size: 18px;color:#409EFF"
@click.stop="openAttachment(currentRow)" v-if="currentRow.AttachmentCount">{{
$t('trials:sysDocBeSigned:table:AttachmentCount') }} ({{
currentRow.AttachmentCount }})</span>
<span v-if="!currentIsConfirm" style="position: fixed;right: 75px;top: 14px;">
@@ -81,7 +82,10 @@
<SignForm :is-system-doc="currentRow.IsSystemDoc" :document-id="currentRow.Id" :file-name="fileName"
@closeDialog="closeSignDialog" />
</el-dialog>
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" v-if="perview_visible" />
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
:isView="true" @getList="getList" />
<attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible" :isView="true"
v-if="perview_visible" />
</div>
</template>
<script>
@@ -90,6 +94,7 @@ import Pagination from '@/components/Pagination'
import PreviewFile from '@/components/PreviewFile/index'
import SignForm from '@/views/trials/trials-panel/attachments/self-attachment/components/SignForm'
import attachmentPreview from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentPreview'
import attachmentList from '@/views/dictionary/attachment/components/SignatureTemplate/attachmentList'
const searchDataDefault = () => {
return {
pageIndex: 1,
@@ -100,7 +105,7 @@ const searchDataDefault = () => {
}
export default {
name: 'NeedSignSysDoc',
components: { Pagination, PreviewFile, SignForm, attachmentPreview },
components: { Pagination, PreviewFile, SignForm, attachmentPreview, attachmentList },
props: {
isSigned: {
type: Boolean,
@@ -126,7 +131,12 @@ export default {
currentUser: zzSessionStorage.getItem('userName'),
currentRow: {},
perview_visible: false,
SystemDocumentId: null
SystemDocumentId: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -138,10 +148,15 @@ export default {
}
},
methods: {
openAttachment(row) {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.perview_visible = true
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
//
handlePreview(row) {
+28 -16
View File
@@ -12,8 +12,8 @@
</style>
<title></title>
<!-- 引入外部JavaScript文件 -->
<script type="text/javascript"
src="https://onlyoffice.test.extimaging.com/web-apps/apps/api/documents/api.js"></script>
<!-- <script type="text/javascript"
src="https://onlyoffice.test.extimaging.com/web-apps/apps/api/documents/api.js"></script> -->
</head>
<body>
@@ -28,22 +28,34 @@
var arr = item.split('=')
obj[arr[0]] = window.decodeURIComponent(arr[1])
})
var docEditor = new window.DocsAPI.DocEditor("placeholder", {
type: "embedded",
"document": {
"fileType": obj.type,
"key": "Khirz6zTPdfd7" + Date.now(),
"title": obj.title,
"url": url,
},
// editorConfig: {
// lang:'zh'
function loadScript() {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = `${obj.onlyOffice_url}/web-apps/apps/api/documents/api.js`
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
loadScript().then(() => {
console.log('JS 加载完成')
var docEditor = new window.DocsAPI.DocEditor("placeholder", {
type: "embedded",
"document": {
"fileType": obj.type,
"key": "Khirz6zTPdfd7" + Date.now(),
"title": obj.title,
"url": url,
},
// editorConfig: {
// lang:'zh'
// },
"documentType": obj.documentType,
"height": "100%",
// },
"documentType": obj.documentType,
"height": "100%",
});
});
})
</script>
</body>
+1 -1
View File
@@ -97,7 +97,7 @@ module.exports = defineConfig({
]
}),
// new BundleAnalyzerPlugin(),
process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'prod' || process.env.NODE_ENV === 'production' || process.env.VUE_APP_OSS_CONFIG_BUCKET === 'zyypacs-usa' ? function () { }
process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'production' || process.env.VUE_APP_OSS_CONFIG_BUCKET === 'zyypacs-usa' ? function () { }
: new WebpackAliyunOss({
from: ['./dist/**'],
dist: process.env.VUE_APP_OSS_PATH + distDate,