240 Commits

Author SHA1 Message Date
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
caiyiling aea6a3f8c8 病灶对齐逻辑更改
continuous-integration/drone/push Build is passing
2025-04-18 17:02:52 +08:00
caiyiling c657bb8783 标注序列图标维护
continuous-integration/drone/push Build is passing
2025-04-18 16:46:27 +08:00
caiyiling b0ae3cbd27 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-18 16:01:28 +08:00
caiyiling 10487640cd 视图工具维护 2025-04-18 16:01:00 +08:00
wangxiaoshuang 83fa937537 通用培训文档新增发布功能
continuous-integration/drone/push Build is passing
2025-04-18 14:23:29 +08:00
wangxiaoshuang dd1fb94562 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-18 09:55:17 +08:00
wangxiaoshuang 116402f865 【账号登录日志】去除用户类型查询条件 2025-04-18 09:55:06 +08:00
caiyiling 76fe08ed23 重置视口时重置窗宽窗位
continuous-integration/drone/push Build is passing
2025-04-18 09:43:21 +08:00
caiyiling 993d97e205 测量时病灶默认状态维护
continuous-integration/drone/push Build is passing
2025-04-18 09:21:47 +08:00
caiyiling 11471525af 任务切换或者单个病灶保存时前端缓存当前状态不刷新ecrf
continuous-integration/drone/push Build is passing
2025-04-17 17:36:35 +08:00
caiyiling 600abc3ce1 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-17 16:02:57 +08:00
caiyiling 9609768e5e 全局阅片更改 2025-04-17 16:02:39 +08:00
wangxiaoshuang ce565e00ff Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-17 15:14:00 +08:00
wangxiaoshuang e49fe23396 通用培训问题修复 2025-04-17 15:13:56 +08:00
caiyiling 85463ad240 典型肝内病灶提示更改
continuous-integration/drone/push Build is passing
2025-04-17 14:59:19 +08:00
caiyiling fc228622e1 病灶保存更改
continuous-integration/drone/push Build is passing
2025-04-17 14:20:00 +08:00
caiyiling 8840ddc77a 病灶清除修复
continuous-integration/drone/push Build is passing
2025-04-17 11:40:34 +08:00
caiyiling 3b31132616 mRecist表单更改
continuous-integration/drone/push Build is passing
2025-04-17 10:22:28 +08:00
caiyiling c7df59e564 1
continuous-integration/drone/push Build is passing
2025-04-17 09:29:23 +08:00
caiyiling aeef8eaacf 清除病灶信息bug修复
continuous-integration/drone/push Build is passing
2025-04-17 09:07:46 +08:00
caiyiling b0c4cfd0bf 阅片页面矩形工具补充
continuous-integration/drone/push Build is passing
2025-04-16 17:26:48 +08:00
caiyiling 38c06595aa 病灶保存bug修复
continuous-integration/drone/push Build is passing
2025-04-16 17:10:15 +08:00
caiyiling a0c5c7d304 病灶验证更改
continuous-integration/drone/push Build is passing
2025-04-16 16:43:30 +08:00
caiyiling 2323cbdd27 病灶保存成功时关闭窗口
continuous-integration/drone/push Build is passing
2025-04-16 15:57:33 +08:00
caiyiling c2f3e02ef1 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is running
2025-04-16 15:55:44 +08:00
caiyiling b069c881f2 mRecist病灶验证更改 2025-04-16 15:55:13 +08:00
wangxiaoshuang faaaf8f28f 附件数量为0时不打开预览弹框
continuous-integration/drone/push Build is passing
2025-04-15 17:39:54 +08:00
wangxiaoshuang 1e5bd46a40 附件预览筛选是否下线
continuous-integration/drone/push Build is passing
2025-04-15 17:27:54 +08:00
wangxiaoshuang 7d2af46845 通用培训问题解决
continuous-integration/drone/push Build is passing
2025-04-15 16:29:56 +08:00
wangxiaoshuang e1c41d2327 通用培训文档优化:增加附件、培训时限
continuous-integration/drone/push Build is passing
2025-04-15 15:49:22 +08:00
wangxiaoshuang d4ae366fa6 非dicom上传去除限制
continuous-integration/drone/push Build is passing
2025-04-15 11:13:52 +08:00
wangxiaoshuang 26963b0f94 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-15 10:01:10 +08:00
wangxiaoshuang c391e3e2e7 通用培训新增附件 2025-04-15 10:00:59 +08:00
caiyiling 23feb84e30 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-14 16:46:00 +08:00
caiyiling d5ae075aa8 多帧图像渲染更改 2025-04-14 16:45:40 +08:00
wangxiaoshuang b22374db9e Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-14 14:04:37 +08:00
wangxiaoshuang ba17d51f72 文档模板需要区分Elevate和Extensive的需要。 2025-04-14 14:04:28 +08:00
caiyiling 0f9963d939 箭头工具更改
continuous-integration/drone/push Build is passing
2025-04-14 13:41:42 +08:00
caiyiling ca0d406e45 recist标准更改
continuous-integration/drone/push Build is passing
2025-04-14 13:11:00 +08:00
caiyiling 8e0ad5ca9d mRecist标准更改
continuous-integration/drone/push Build is passing
2025-04-14 11:39:53 +08:00
wangxiaoshuang e0321bae72 影像预览问题解决
continuous-integration/drone/push Build encountered an error
2025-04-12 12:04:20 +08:00
wangxiaoshuang 10c1fb0bbc 重传审批添加默认查询条件
continuous-integration/drone/push Build is passing
2025-04-11 15:42:24 +08:00
wangxiaoshuang 3ab9b48842 工作台增加通用培训
continuous-integration/drone/push Build is passing
2025-04-11 15:17:05 +08:00
wangxiaoshuang 58a590bbb7 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-10 17:46:47 +08:00
wangxiaoshuang b331b43b28 表格双击、授权按钮区分 2025-04-10 17:46:35 +08:00
caiyiling 162b649c3c Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-10 17:12:06 +08:00
caiyiling fcc5398bcb 阅片更改 2025-04-10 17:11:47 +08:00
wangxiaoshuang 9df1444e3d 版本记录删除与下载问题
continuous-integration/drone/push Build is passing
2025-04-10 16:23:45 +08:00
wangxiaoshuang 0f944343b3 历史版本打开有误
continuous-integration/drone/push Build is passing
2025-04-10 15:46:14 +08:00
wangxiaoshuang adeecf0b07 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-04-10 15:41:23 +08:00
wangxiaoshuang 1965abc9fd 设置忽略异地登录 2025-04-10 15:41:13 +08:00
caiyiling 1c19301f28 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-10 15:26:31 +08:00
caiyiling 42711dadb5 dicom阅片更改 2025-04-10 15:26:11 +08:00
wangxiaoshuang d9d2a2169e 表格双击、展开行事件区分
continuous-integration/drone/push Build is passing
2025-04-10 14:53:16 +08:00
wangxiaoshuang 9775ef24c8 1
continuous-integration/drone/push Build is failing
2025-04-10 14:42:10 +08:00
wangxiaoshuang 9af3cd4280 稽查文档管理问题修复
continuous-integration/drone/push Build is passing
2025-04-10 14:32:21 +08:00
wangxiaoshuang ab058d6659 稽查文档排序、新增文件夹更换接口
continuous-integration/drone/push Build is passing
2025-04-10 13:27:10 +08:00
wangxiaoshuang 0d446746e6 稽查文档管理问题修复
continuous-integration/drone/push Build is passing
2025-04-10 10:29:25 +08:00
wangxiaoshuang aaa366934b 影像退回
continuous-integration/drone/push Build is passing
2025-04-10 09:56:02 +08:00
wangxiaoshuang 7614f7a327 1
continuous-integration/drone/push Build is passing
2025-04-09 15:59:36 +08:00
wangxiaoshuang 0035ac1266 邮件管理中子页的操作列中文显示为英文,英文环境下按钮位置需要调整
continuous-integration/drone/push Build is passing
2025-04-09 13:56:49 +08:00
wangxiaoshuang 298d335a03 【忘记密码】点击上一步无法退回到上一个页面
continuous-integration/drone/push Build is passing
2025-04-09 13:50:21 +08:00
wangxiaoshuang 1bddd30709 账户日志的操作类型支持多选
continuous-integration/drone/push Build is passing
2025-04-09 13:43:47 +08:00
wangxiaoshuang 1d6ebe6388 项目切换时,需要根据项目状态过滤项目
continuous-integration/drone/push Build is passing
2025-04-09 13:37:39 +08:00
wangxiaoshuang 236a565250 工作台稽查文档
continuous-integration/drone/push Build is passing
2025-04-09 10:05:30 +08:00
wangxiaoshuang 318954b360 稽查文档管理新增文件夹
continuous-integration/drone/push Build is passing
2025-04-08 17:28:40 +08:00
wangxiaoshuang 91d31b4226 稽查文档预览
continuous-integration/drone/push Build is passing
2025-04-08 16:17:54 +08:00
wangxiaoshuang 1fb76ca72d 稽查文档预览
continuous-integration/drone/push Build is passing
2025-04-08 15:09:04 +08:00
wangxiaoshuang b76d157fca 稽查文档管理
continuous-integration/drone/push Build is passing
2025-04-07 18:00:10 +08:00
wangxiaoshuang fa71af24ad 1
continuous-integration/drone/push Build is passing
2025-04-07 10:12:32 +08:00
wangxiaoshuang a9edab9227 稽查文档管理
continuous-integration/drone/push Build is failing
2025-04-03 17:35:25 +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
wangxiaoshuang 31c9db368c 稽查文档管理
continuous-integration/drone/push Build is failing
2025-04-03 10:28:59 +08:00
wangxiaoshuang 866dc923c7 中心查询条件修改trialSiteId
continuous-integration/drone/push Build is failing
2025-04-03 09:46:21 +08:00
wangxiaoshuang a6b958ce4e dicom文件maxValue tag有问题 2025-04-03 09:45:50 +08:00
wangxiaoshuang 662cc93d85 稽查文档管理
continuous-integration/drone/push Build is failing
2025-04-01 17:59:24 +08:00
caiyiling e3ad114207 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2025-04-01 16:48:50 +08:00
caiyiling 0960b480a6 阅片页面更改 2025-04-01 16:48:04 +08:00
wangxiaoshuang 9a4eb1b210 工作台稽查文档
continuous-integration/drone/push Build is passing
2025-03-28 18:01:45 +08:00
wangxiaoshuang 0567534367 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-03-27 13:31:59 +08:00
wangxiaoshuang 481a436169 项目文档确认收入项问题解决 2025-03-27 13:31:54 +08:00
caiyiling 4188c0110f 非dicom阅片受试者内随机初始化图像逻辑更改
continuous-integration/drone/push Build is passing
2025-03-26 15:23:29 +08:00
wangxiaoshuang 01206716ab 1
continuous-integration/drone/push Build is passing
2025-03-24 16:56:27 +08:00
wangxiaoshuang 7fcbd7f983 生产环境配置修改
continuous-integration/drone/push Build encountered an error
2025-03-24 13:59:21 +08:00
wangxiaoshuang 779badb041 项目文档系统数据中英文转换
continuous-integration/drone/push Build is passing
2025-03-24 09:31:49 +08:00
caiyiling ef5e5527fd 标注更改及检查名称更改
continuous-integration/drone/push Build is passing
2025-03-21 16:29:47 +08:00
caiyiling 52ac12d6b6 阅片页面显示检查名称
continuous-integration/drone/push Build is passing
2025-03-21 15:36:23 +08:00
caiyiling 272c28a988 自定义阅片更改及非dicom阅片添加个性化配置
continuous-integration/drone/push Build is passing
2025-03-21 15:03:21 +08:00
caiyiling 6b10d07e2a 自定义阅片预览paf更改
continuous-integration/drone/push Build is passing
2025-03-21 14:03:53 +08:00
caiyiling 032e710890 阅片工具及报告页查看pdf更改
continuous-integration/drone/push Build is passing
2025-03-21 13:09:36 +08:00
caiyiling 032150904e 非dicom阅片更改
continuous-integration/drone/push Build is passing
2025-03-20 19:23:20 +08:00
wangxiaoshuang d91161a139 网速监控改为三位小数
continuous-integration/drone/push Build is passing
2025-03-19 15:53:41 +08:00
wangxiaoshuang bba2237337 文件大小改为保留3位小数
continuous-integration/drone/push Build is passing
2025-03-19 15:36:53 +08:00
wangxiaoshuang 707ca5602c Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-03-18 17:59:20 +08:00
wangxiaoshuang 3b03d6ba76 项目文档搜索栏样式问题 2025-03-18 17:59:10 +08:00
caiyiling 9277d48c85 1
continuous-integration/drone/push Build is passing
2025-03-18 17:55:22 +08:00
caiyiling d753fbc2ab 非dicom阅片测量工具更改
continuous-integration/drone/push Build is passing
2025-03-18 16:53:03 +08:00
caiyiling 5678e3302b 国际化补充
continuous-integration/drone/push Build is passing
2025-03-18 15:34:12 +08:00
caiyiling c25cb097ab 1
continuous-integration/drone/push Build is passing
2025-03-18 15:28:13 +08:00
caiyiling 070db5158d Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is running
2025-03-18 15:27:16 +08:00
caiyiling 121bf497bb 非dicom阅片更改 2025-03-18 15:26:51 +08:00
wangxiaoshuang e2ffd76504 阅片人筛选机构城市国家中英文显示
continuous-integration/drone/push Build is passing
2025-03-18 15:18:26 +08:00
wangxiaoshuang cfbe3b4839 项目文档培训记录培训人数校验修改
continuous-integration/drone/push Build is running
2025-03-18 15:16:31 +08:00
wangxiaoshuang 81bef8abe0 项目文档培训记录培训人数添加校验
continuous-integration/drone/push Build is passing
2025-03-18 14:15:31 +08:00
wangxiaoshuang 84b1263340 浏览器页签标题修改
continuous-integration/drone/push Build is passing
2025-03-18 13:57:07 +08:00
wangxiaoshuang 4016e71c37 项目文档阅片人问题修复
continuous-integration/drone/push Build is passing
2025-03-18 13:51:25 +08:00
wangxiaoshuang 3b0afd3913 1
continuous-integration/drone/push Build is passing
2025-03-18 13:45:13 +08:00
wangxiaoshuang 3672892451 项目文档阅片人问题修复
continuous-integration/drone/push Build is passing
2025-03-18 09:54:33 +08:00
wangxiaoshuang c46ca18a42 项目文档菜单与列表是否适用字段联动
continuous-integration/drone/push Build is passing
2025-03-17 16:38:14 +08:00
wangxiaoshuang 0e0b5c2c4c 项目文档同意入项记录上传方式修改
continuous-integration/drone/push Build is passing
2025-03-17 14:34:24 +08:00
wangxiaoshuang 5339e2df63 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2025-03-17 13:23:25 +08:00
wangxiaoshuang 66d5591d50 项目文档同意入项记录上传更改为弹框 2025-03-17 13:23:21 +08:00
163 changed files with 25175 additions and 7815 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
@@ -2,6 +2,9 @@
ENV = 'prop'
NODE_ENV = 'prop'
# base public path
VUE_APP_BASE_PATH = '/'
# 是否开启登陆限制 true:是 false:否
VUE_APP_LOGIN_FOR_PERMISSION = 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 = '/'
+197
View File
File diff suppressed because one or more lines are too long
+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')
}
+45 -1
View File
@@ -212,7 +212,20 @@ export function getSystemDocumentList(param) {
data: param
})
}
export function publishSystemDocument(param) {
return request({
url: `/SystemDocument/publishSystemDocument`,
method: 'post',
data: param
})
}
export function outLineSystemDocument(param) {
return request({
url: `/SystemDocument/outLineSystemDocument`,
method: 'post',
data: param
})
}
export function addOrUpdateSystemDocument(param) {
return request({
url: `/SystemDocument/addOrUpdateSystemDocument`,
@@ -220,6 +233,29 @@ export function addOrUpdateSystemDocument(param) {
data: param
})
}
// 新增/修改通用培训附件
export function addOrUpdateSystemDocumentAttachment(param) {
return request({
url: `/SystemDocument/addOrUpdateSystemDocumentAttachment`,
method: 'post',
data: param
})
}
// 通用培训附件列表
export function getSystemDocumentAttachmentList(param) {
return request({
url: `/SystemDocument/getSystemDocumentAttachmentList`,
method: 'post',
data: param
})
}
// 删除通用培训附件
export function deleteSystemDocumentAttachment(systemDocumentAttachmentId) {
return request({
url: `/SystemDocument/deleteSystemDocumentAttachment/${systemDocumentAttachmentId}`,
method: 'delete',
})
}
export function deleteSystemDocument(id) {
return request({
@@ -1265,6 +1301,14 @@ export function getTrialFileTypeFile(data) {
data
})
}
// 项目文档-删除上传同意入项记录
export function deleteTrialTypeFile(params) {
return request({
url: `/TrialFileType/deleteTrialTypeFile`,
method: 'post',
params
})
}
// 项目文档-获取资质材料列表
export function getTrialAttachments(data) {
return request({
+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({
+130 -1
View File
@@ -1035,7 +1035,27 @@ export function getQCVisitList(param) {
data: param
})
}
export function getImageBackList(param) {
return request({
url: `/QCList/getImageBackList`,
method: 'post',
data: param
})
}
export function getImageBackApplyUserList(params) {
return request({
url: `/QCList/getImageBackApplyUserList`,
method: 'get',
params
})
}
export function auditImageBack(params) {
return request({
url: `/QCOperation/auditImageBack`,
method: 'put',
params
})
}
export function getQCChallengeList(param) {
return request({
url: `/QCList/getQCChallengeList`,
@@ -1599,6 +1619,13 @@ export function getDocumentConfirmList(param) {
data: param
})
}
export function getSysDocumentConfirmList(param) {
return request({
url: `/TrialDocument/getSysDocumentConfirmList`,
method: 'post',
data: param
})
}
export function getTrialUserSelect(trialId) {
return request({
url: `/TrialDocument/getTrialUserSelect/${trialId}`,
@@ -1613,6 +1640,13 @@ export function getTrialDocAndSystemDocType(trialId) {
})
}
export function getSysDocSignUserList() {
return request({
url: `/TrialDocument/getSysDocSignUserList`,
method: 'get'
})
}
export function updateSubjectStatus(param) {
return request({
url: `/Subject/updateSubjectStatus`,
@@ -3308,6 +3342,13 @@ export function getReReadingApplyToBeDoneList(param) {
data: param
})
}
export function getPMImageBackToBeDoneList(param) {
return request({
url: `/PersonalWorkstation/getPMImageBackToBeDoneList`,
method: 'post',
data: param
})
}
export function deleteClinicalForm(param) {
return request({
@@ -3995,3 +4036,91 @@ export function deleteTrialFileType(id) {
})
}
// 工作台-获取稽查文档
export function getAuditDocumentData(data) {
return request({
url: `/AuditDocument/getAuditDocumentData`,
method: 'post',
data
})
}
// 工作台-新增稽查文档
export function addAuditDocument(data) {
return request({
url: `/AuditDocument/addAuditDocument`,
method: 'post',
data
})
}
// 工作台-获取当前目录层级
export function getBreadcrumbData(data) {
return request({
url: `/AuditDocument/getBreadcrumbData`,
method: 'post',
data
})
}
// 工作台-修改稽查文档
export function updateAuditDocument(data) {
return request({
url: `/AuditDocument/updateAuditDocument`,
method: 'post',
data
})
}
// 工作台-删除稽查文档
export function deleteAuditDocument(data) {
return request({
url: `/AuditDocument/deleteAuditDocument`,
method: 'post',
data
})
}
// 工作台-稽查文档获取历史版本
export function getHistoricalVersion(data) {
return request({
url: `/AuditDocument/getHistoricalVersion`,
method: 'post',
data
})
}
// 工作台-稽查文档设置当前版本
export function setCurrentVersion(data) {
return request({
url: `/AuditDocument/setCurrentVersion`,
method: 'post',
data
})
}
// 工作台-稽查文档移动
export function movieFileOrFolder(data) {
return request({
url: `/AuditDocument/movieFileOrFolder`,
method: 'post',
data
})
}
// 工作台-稽查文档复制
export function copyFileOrFolder(data) {
return request({
url: `/AuditDocument/copyFileOrFolder`,
method: 'post',
data
})
}
// 工作台-稽查文档授权
export function setIsAuthorization(data) {
return request({
url: `/AuditDocument/setIsAuthorization`,
method: 'post',
data
})
}
// 工作台-稽查文档新增文件夹
export function addFolder(data) {
return request({
url: `/AuditDocument/addFolder`,
method: 'post',
data
})
}
+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'
})
}
+8
View File
@@ -287,4 +287,12 @@ export function forwardSVDicomImage(param) {
data: param
})
}
// crc、iqc申请影像退回
export function requestImageBack(params) {
return request({
url: `/QCOperation/requestImageBack`,
method: 'put',
params
})
}
+8
View File
@@ -209,3 +209,11 @@ export function loginSelectUserRole(params) {
params,
})
}
// 忽略异地登录
export function setIsIgnoreUncommonly(data) {
return request({
url: `/User/setIsIgnoreUncommonly`,
method: 'post',
data,
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

+28 -15
View File
@@ -1,28 +1,28 @@
<template>
<div class="preview-wrapper">
<iframe
v-if="fileType.indexOf('jpg') !== -1 || fileType.indexOf('png') !== -1"
frameborder="0"
:src="filePath"
width="100%"
height="100%"
/>
<iframe v-if="fileType.indexOf('jpg') !== -1 || fileType.indexOf('png') !== -1" frameborder="0" :src="filePath"
width="100%" height="100%" />
<!-- <embed v-else-if="fileType.indexOf('pdf') !== -1" :src="filePath+'#toolbar=0'" style="width: 100%; height: 100%"> -->
<!-- <iframe v-else-if="fileType.indexOf('pdf') !== -1" :src="filePath+'#toolbar=0'" width="100%" height="100%" frameborder="0" /> -->
<iframe v-else-if="fileType.indexOf('pdf') !== -1" :src="`/static/pdfjs/web/viewer.html?file=${OSSclientConfig.basePath}${filePath}?userName=${currentUser}&COMPANY=${COMPANY}`" width="100%" height="100%" frameborder="0" crossorigin="anonymous" />
<iframe v-else-if="fileType.indexOf('pdf') !== -1"
:src="`/static/pdfjs/web/viewer.html?file=${OSSclientConfig.basePath}${filePath}?userName=${currentUser}&COMPANY=${COMPANY}`"
width="100%" height="100%" frameborder="0" crossorigin="anonymous" />
<!-- <pdf-->
<!-- v-else-if="fileType.indexOf('pdf') !== -1"-->
<!-- :src="`/static/pdfjs/web/viewer.html?file=${filePath}`">-->
<!-- </pdf>-->
<div v-else>
<iframe v-else
: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') }}
<el-link type="primary" @click="downLoadFile">{{ $t('common:button:download') }}</el-link>
</div>
</div> -->
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import DOCUMENTTYPE from "@/utils/onlyOffice_type.js"
export default {
name: 'PreviewFile',
computed: {
@@ -36,12 +36,23 @@ export default {
fileType: {
type: String,
required: true
}
},
title: {
type: String,
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: {
documentType() {
console.log(DOCUMENTTYPE[`.${this.fileType}`], 'documentType')
return DOCUMENTTYPE[`.${this.fileType}`]
}
},
mounted() {
@@ -62,14 +73,16 @@ export default {
}
</script>
<style lang="scss">
.preview-wrapper{
.preview-wrapper {
height: 100%;
width:100%;
width: 100%;
padding: 10px;
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
+23
View File
@@ -0,0 +1,23 @@
import Vue from "vue";
import OnlyOffice from "./index.vue";
const PreviewConstructor = Vue.extend(OnlyOffice);
const onlyOffice = options => {
const { path, type, title } = options;
if (!path) throw `path is requred.but ${path}`
const id = `OnlyOffice_${new Date().getTime()}`;
const instance = new PreviewConstructor();
instance.id = id;
instance.vm = instance.$mount();
if (instance.vm.visible) return;
document.body.appendChild(instance.vm.$el);
instance.vm.open(path, type, title);
instance.vm.$on("closed", () => {
instance.vm.docEditor = null
document.body.removeChild(instance.vm.$el);
instance.vm.$destroy();
});
return instance.vm;
}
export default onlyOffice;
@@ -0,0 +1,7 @@
import OnlyOffice from "./index.vue";
import onlyOffice from "./fun";
export default Vue => {
Vue.component(OnlyOffice.name, OnlyOffice);
Vue.prototype.$onlyOffice = onlyOffice;
};
@@ -0,0 +1,46 @@
<template>
<el-dialog v-if="visible" :visible.sync="visible" :title="title" :fullscreen="true" append-to-body
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}?onlyOffice_url=${onlyOffice_url}&type=${type}&title=${title}&documentType=${documentType}&userName=${currentUser}`"
width="100%" height="99%" frameborder="0" crossorigin="anonymous" />
</div>
</el-dialog>
</template>
<script>
import DOCUMENTTYPE from "@/utils/onlyOffice_type.js"
export default {
name: "OnlyOffice",
data() {
return {
visible: false,
path: null,
type: null,
title: null,
documentType: null,
currentUser: zzSessionStorage.getItem('userName'),
onlyOffice_url: process.env.VUE_APP_ONLYOFFICE_URL
};
},
methods: {
open(path, type, title) {
this.path = path;
this.type = type.toLowerCase();
this.documentType = DOCUMENTTYPE[`.${this.type}`]
this.title = title;
this.visible = true;
},
handleClose() {
this.$emit("closed");
},
},
};
</script>
<style lang="scss" scoped>
#placeholder {
width: 100%;
height: 100%;
}
</style>
@@ -316,9 +316,9 @@
scope.row.dicomInfo.fileCount
}}
({{
(scope.row.dicomInfo.uploadFileSize / 1024 / 1024).toFixed(2)
(scope.row.dicomInfo.uploadFileSize / 1024 / 1024).toFixed(3)
}}MB/{{
(scope.row.dicomInfo.fileSize / 1024 / 1024).toFixed(2)
(scope.row.dicomInfo.fileSize / 1024 / 1024).toFixed(3)
}}MB)
</span>
</template>
@@ -8,65 +8,29 @@
</div>
</div>
<!--检查列表-->
<el-table
:data="list"
style="width: 100%"
v-adaptive="{ bottomOffset: 60 }"
:loading="loading"
@sort-change="handleSortByColumn"
:default-sort="{ prop: 'TaskBlindName', order: 'descending' }"
>
<el-table :data="list" style="width: 100%" v-adaptive="{ bottomOffset: 60 }" :loading="loading"
@sort-change="handleSortByColumn" :default-sort="{ prop: 'TaskBlindName', order: 'descending' }">
<!--受试者-->
<el-table-column
prop="SubjectCode"
:label="$t('upload:nonedicom:table:subject')"
/>
<el-table-column prop="SubjectCode" :label="$t('upload:nonedicom:table:subject')" />
<!--任务名称-->
<el-table-column
prop="TaskBlindName"
:label="$t('upload:nonedicom:table:taskName')"
sortable="custom"
/>
<el-table-column prop="TaskBlindName" :label="$t('upload:nonedicom:table:taskName')" sortable="custom" />
<!--检查类型-->
<el-table-column
prop="Modality"
:label="$t('upload:nonedicom:table:molityType')"
sortable="custom"
>
<el-table-column prop="Modality" :label="$t('upload:nonedicom:table:molityType')" sortable="custom">
</el-table-column>
<!--检查部位-->
<el-table-column
prop="BodyPart"
:label="$t('upload:nonedicom:table:bodyPart')"
sortable="custom"
/>
<el-table-column prop="BodyPart" :label="$t('upload:nonedicom:table:bodyPart')" sortable="custom" />
<!--原文件数-->
<el-table-column
prop="FileCount"
:label="$t('upload:nonedicom:table:fileCount')"
>
<el-table-column prop="FileCount" :label="$t('upload:nonedicom:table:fileCount')">
<template slot-scope="scope">
<el-popover
v-if="scope.row.FileCount"
trigger="click"
placement="bottom"
>
<el-popover v-if="scope.row.FileCount" trigger="click" placement="bottom">
<el-table :data="scope.row.FileList" height="300" size="small">
<!-- 文件名称 -->
<el-table-column
prop="FileName"
:label="$t('trials:audit:table:nonDicomsFileName')"
width="200"
/>
<el-table-column
prop="FileSize"
:label="$t('trials:audit:table:nonDicomsFileSize')"
width="100"
>
<el-table-column prop="FileName" :label="$t('trials:audit:table:nonDicomsFileName')" width="200" />
<el-table-column prop="FileSize" :label="$t('trials:audit:table:nonDicomsFileSize')" width="100">
<template slot-scope="scope">
<span>{{
scope.row.FileSize && scope.row.FileSize > 0
? `${(scope.row.FileSize / 1024 / 1024).toFixed(2)}MB`
? `${(scope.row.FileSize / 1024 / 1024).toFixed(3)}MB`
: ''
}}</span>
</template>
@@ -74,27 +38,13 @@
<el-table-column :label="$t('common:action:action')" width="120">
<template slot-scope="files">
<!-- 预览 -->
<viewer
:ref="files.row.Path"
style="margin: 0 10px"
:images="[`${OSSclientConfig.basePath}${files.row.Path}`]"
>
<el-button
circle
icon="el-icon-view"
:title="$t('upload:nonedicom:button:preview')"
:disabled="
files.row.FileType &&
files.row.FileType.indexOf('zip') >= 0
"
@click.native.prevent="previewFile(files.row)"
/>
<img
v-show="false"
crossorigin="anonymous"
:src="`${OSSclientConfig.basePath}${files.row.Path}`"
alt="Image"
/>
<viewer :ref="files.row.Path" style="margin: 0 10px"
:images="[`${OSSclientConfig.basePath}${files.row.Path}`]">
<el-button circle icon="el-icon-view" :title="$t('upload:nonedicom:button:preview')" :disabled="files.row.FileType &&
files.row.FileType.indexOf('zip') >= 0
" @click.native.prevent="previewFile(files.row)" />
<img v-show="false" crossorigin="anonymous" :src="`${OSSclientConfig.basePath}${files.row.Path}`"
alt="Image" />
</viewer>
</template>
</el-table-column>
@@ -109,36 +59,17 @@
</template>
</el-table-column>
<!--后处理文件数-->
<el-table-column
prop="UploadedFileCount"
:label="$t('upload:nonedicom:table:uploadFileCount')"
>
<el-table-column prop="UploadedFileCount" :label="$t('upload:nonedicom:table:uploadFileCount')">
<template slot-scope="scope">
<el-popover
v-if="scope.row.UploadedFileCount"
trigger="click"
placement="bottom"
>
<el-table
:data="scope.row.UploadedFileList"
height="300"
size="small"
>
<el-popover v-if="scope.row.UploadedFileCount" trigger="click" placement="bottom">
<el-table :data="scope.row.UploadedFileList" height="300" size="small">
<!-- 文件名称 -->
<el-table-column
prop="FileName"
:label="$t('trials:audit:table:nonDicomsFileName')"
width="200"
/>
<el-table-column
prop="FileSize"
:label="$t('trials:audit:table:nonDicomsFileSize')"
width="100"
>
<el-table-column prop="FileName" :label="$t('trials:audit:table:nonDicomsFileName')" width="200" />
<el-table-column prop="FileSize" :label="$t('trials:audit:table:nonDicomsFileSize')" width="100">
<template slot-scope="scope">
<span>{{
scope.row.FileSize && scope.row.FileSize > 0
? `${(scope.row.FileSize / 1024 / 1024).toFixed(2)}MB`
? `${(scope.row.FileSize / 1024 / 1024).toFixed(3)}MB`
: ''
}}</span>
</template>
@@ -146,27 +77,13 @@
<el-table-column :label="$t('common:action:action')" width="120">
<template slot-scope="files">
<!-- 预览 -->
<viewer
:ref="files.row.Path"
style="margin: 0 10px"
:images="[`${OSSclientConfig.basePath}${files.row.Path}`]"
>
<el-button
circle
icon="el-icon-view"
:title="$t('upload:nonedicom:button:preview')"
:disabled="
files.row.FileType &&
files.row.FileType.indexOf('zip') >= 0
"
@click.native.prevent="previewFile(files.row)"
/>
<img
v-show="false"
crossorigin="anonymous"
:src="`${OSSclientConfig.basePath}${files.row.Path}`"
alt="Image"
/>
<viewer :ref="files.row.Path" style="margin: 0 10px"
:images="[`${OSSclientConfig.basePath}${files.row.Path}`]">
<el-button circle icon="el-icon-view" :title="$t('upload:nonedicom:button:preview')" :disabled="files.row.FileType &&
files.row.FileType.indexOf('zip') >= 0
" @click.native.prevent="previewFile(files.row)" />
<img v-show="false" crossorigin="anonymous" :src="`${OSSclientConfig.basePath}${files.row.Path}`"
alt="Image" />
</viewer>
</template>
</el-table-column>
@@ -180,75 +97,34 @@
<span v-else>{{ scope.row.UploadedFileCount }}</span>
</template>
</el-table-column>
<el-table-column
:label="$t('common:action:action')"
fixed="right"
width="180"
>
<el-table-column :label="$t('common:action:action')" fixed="right" width="180">
<template slot-scope="scope">
<!--预览--->
<el-button
circle
:disabled="scope.row.UploadedFileCount <= 0"
icon="el-icon-view"
:title="$t('upload:nonedicom:button:preview')"
@click.stop="handlePreviewNoneDicomFiles(scope.row)"
/>
<el-button circle :disabled="scope.row.UploadedFileCount <= 0" icon="el-icon-view"
:title="$t('upload:nonedicom:button:preview')" @click.stop="handlePreviewNoneDicomFiles(scope.row)" />
<!--上传--->
<el-button
circle
icon="el-icon-upload2"
:title="$t('upload:nonedicom:button:upload')"
@click.native.prevent="handleUpload(scope.row)"
/>
<el-button circle icon="el-icon-upload2" :title="$t('upload:nonedicom:button:upload')"
@click.native.prevent="handleUpload(scope.row)" />
<!--删除--->
<el-button
:disabled="
scope.row.UploadedFileCount <= 0 ||
scope.row.ReadingTaskState === 2
"
circle
icon="el-icon-delete"
:title="$t('upload:nonedicom:button:delete')"
@click.stop="remove(scope.row)"
/>
<el-button :disabled="scope.row.UploadedFileCount <= 0 ||
scope.row.ReadingTaskState === 2
" circle icon="el-icon-delete" :title="$t('upload:nonedicom:button:delete')"
@click.stop="remove(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 预览单个图像 -->
<el-dialog
v-if="imgObj.visible"
:visible.sync="imgObj.visible"
:title="$t('upload:nonedicom:dialogTitle:preview')"
append-to-body
width="565px"
>
<div
v-loading="imgObj.loading"
class="base-modal-body"
style="border: 2px solid #ccc; padding: 10px"
>
<el-image
:src="`${OSSclientConfig.basePath}${imgObj.url}`"
crossorigin="anonymous"
fit="fit"
style="height: 500px; width: 500px"
@error="imgObj.loading = false"
@load="imgObj.loading = false"
/>
<el-dialog v-if="imgObj.visible" :visible.sync="imgObj.visible" :title="$t('upload:nonedicom:dialogTitle:preview')"
append-to-body width="565px">
<div v-loading="imgObj.loading" class="base-modal-body" style="border: 2px solid #ccc; padding: 10px">
<el-image :src="`${OSSclientConfig.basePath}${imgObj.url}`" crossorigin="anonymous" fit="fit"
style="height: 500px; width: 500px" @error="imgObj.loading = false" @load="imgObj.loading = false" />
</div>
</el-dialog>
<!-- 上传非dicom文件 -->
<el-dialog
v-if="uploadVisible"
:visible.sync="uploadVisible"
:close-on-click-modal="false"
:title="$t('trials:uploadNonDicoms:dialogTitle:upload')"
width="800px"
append-to-body
custom-class="base-dialog-wrapper"
@close="resetFileDiaolg"
>
<el-dialog v-if="uploadVisible" :visible.sync="uploadVisible" :close-on-click-modal="false"
:title="$t('trials:uploadNonDicoms:dialogTitle:upload')" width="800px" append-to-body
custom-class="base-dialog-wrapper" @close="resetFileDiaolg">
<!-- 多文件上传 -->
<form id="inputForm" ref="uploadForm">
<el-divider content-position="left">{{
@@ -256,33 +132,15 @@
}}</el-divider>
<div class="form-group">
<div class="upload" style="margin-right: 10px">
<input
multiple="multiple"
webkitdirectory=""
directory
accept="*/*"
type="file"
name="uploadFolder"
class="select-file"
title=""
@change="beginScanFiles($event)"
v-if="!btnLoading"
/>
<input multiple="multiple" webkitdirectory="" directory accept="*/*" type="file" name="uploadFolder"
class="select-file" title="" @change="beginScanFiles($event)" v-if="!btnLoading" />
<div class="btn-select">
{{ $t('trials:uploadNonDicoms:button:selectFolder') }}
</div>
</div>
<div class="upload">
<input
class="select-file"
multiple=""
:accept="faccept.join(',')"
type="file"
name="uploadFile"
title=""
@change="beginScanFiles($event)"
v-if="!btnLoading"
/>
<input class="select-file" multiple="" :accept="faccept.join(',')" type="file" name="uploadFile" title=""
@change="beginScanFiles($event)" v-if="!btnLoading" />
<div class="btn-select">
{{ $t('trials:uploadNonDicoms:button:select') }}
</div>
@@ -293,82 +151,46 @@
</div>
</form>
<!-- 文件列表 -->
<el-table
ref="filesTable"
:data="fileList"
class="dicomFiles-table"
height="300"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
:selectable="(row, index) => row.status !== 2 && !btnLoading"
/>
<el-table ref="filesTable" :data="fileList" class="dicomFiles-table" height="300"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" :selectable="(row, index) => row.status !== 2 && !btnLoading" />
<el-table-column type="index" width="50" />
<!-- 文件名称 -->
<el-table-column
prop="name"
:label="$t('trials:uploadNonDicoms:table:fileName')"
min-width="100"
/>
<el-table-column prop="name" :label="$t('trials:uploadNonDicoms:table:fileName')" min-width="100" />
<!-- 文件大小 -->
<el-table-column
prop="size"
:label="$t('trials:uploadNonDicoms:table:fileSize')"
>
<el-table-column prop="size" :label="$t('trials:uploadNonDicoms:table:fileSize')">
<template slot-scope="scope">
<span>{{
scope.row.size && scope.row.size > 0
? `${(scope.row.size / 1024 / 1024).toFixed(2)}MB`
: ''
? `${(scope.row.size / 1024 / 1024).toFixed(3)}MB`
: '0MB'
}}</span>
</template>
</el-table-column>
<!-- 文件类型 -->
<el-table-column
prop="type"
:label="$t('trials:uploadNonDicoms:table:fileType')"
/>
<el-table-column prop="type" :label="$t('trials:uploadNonDicoms:table:fileType')" />
<!-- 上传状态 -->
<el-table-column
prop="status"
:label="$t('trials:uploadNonDicoms:table:uploadStatus')"
min-width="100"
>
<el-table-column prop="status" :label="$t('trials:uploadNonDicoms:table:uploadStatus')" min-width="100">
<template slot-scope="scope">
<el-tag
:type="['warning', 'info', 'success', 'danger'][scope.row.status]"
v-if="scope.row.status || scope.row.status === 0"
>{{ $fd('NoneDicomUploadStatus', scope.row.status) }}
<el-tag :type="['warning', 'info', 'success', 'danger'][scope.row.status]"
v-if="scope.row.status || scope.row.status === 0">{{ $fd('NoneDicomUploadStatus', scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
:label="$t('trials:uploadNonDicoms:table:failedFileCount')"
min-width="150"
show-overflow-tooltip
>
<el-table-column :label="$t('trials:uploadNonDicoms:table:failedFileCount')" min-width="150"
show-overflow-tooltip>
<template slot-scope="scope">
<el-progress
color="#409eff"
:percentage="
((scope.row.uploadFileSize * 100) / scope.row.size).toFixed(2) *
1
"
/>
<el-progress color="#409eff" :percentage="scope.row.size && scope.row.size > 0 ? ((scope.row.uploadFileSize * 100) / scope.row.size).toFixed(2) *
1
: ((scope.row.uploadFileSize * 100) / 1).toFixed(2) *
1
" />
</template>
</el-table-column>
<el-table-column :label="$t('common:action:action')">
<template slot-scope="scope">
<el-button
size="mini"
icon="el-icon-delete"
circle
:disabled="btnLoading"
:title="$t('trials:crcUpload:action:delete')"
@click="handleRemoveFile(scope.row)"
/>
<el-button size="mini" icon="el-icon-delete" circle :disabled="btnLoading"
:title="$t('trials:crcUpload:action:delete')" @click="handleRemoveFile(scope.row)" />
</template>
</el-table-column>
</el-table>
@@ -376,13 +198,8 @@
<span style="margin-right: 10px">{{
$store.state.trials.uploadTip
}}</span>
<el-button
size="small"
type="primary"
:disabled="selectArr.length == 0"
:loading="btnLoading"
@click="beginUpload"
>
<el-button size="small" type="primary" :disabled="selectArr.length == 0" :loading="btnLoading"
@click="beginUpload">
{{ $t('trials:uploadNonDicoms:action:upload') }}
</el-button>
</div>
@@ -588,7 +405,7 @@ export default {
)
}
})
.catch(() => {})
.catch(() => { })
},
resetUploadForm() {
this.$nextTick(() => {
@@ -714,11 +531,10 @@ export default {
if (!this.uploadVisible) return
let file = this.fileList.filter((item) => item.id === arr[index].id)[0]
file.status = 1
let path = `/${this.$route.query.trialId}/TaskImage/${
this.currentRow.SubjectId
}/${this.currentRow.VisitTaskId}/${this.$guid()}${file.name
.substring(file.name.lastIndexOf('.'))
.toLocaleLowerCase()}`
let path = `/${this.$route.query.trialId}/TaskImage/${this.currentRow.SubjectId
}/${this.currentRow.VisitTaskId}/${this.$guid()}${file.name
.substring(file.name.lastIndexOf('.'))
.toLocaleLowerCase()}`
file.curPath = path
const fileData = await this.fileToBlob(file.file)
let res = await this.fileToOss(path, fileData, file)
@@ -772,7 +588,7 @@ export default {
(percentage, checkpoint, lastPer) => {
item.uploadFileSize += checkpoint.size * (percentage - lastPer)
if (item.uploadFileSize > file.fileSize) {
item.uploadFileSize = file.fileSize
item.uploadFileSize = file.fileSize > 0 ? file.fileSize : 1
}
}
)
@@ -869,6 +685,7 @@ export default {
.top {
margin: 10px 0;
}
.upload {
display: inline-block;
height: 30px;
@@ -883,6 +700,7 @@ export default {
background: #428bca;
border-color: #428bca;
color: #fff;
.select-file {
height: 30px;
width: 90px;
@@ -893,6 +711,7 @@ export default {
opacity: 0;
font-size: 0;
}
.btn-select {
//给显示在页面上的按钮写样式
width: 90px;
@@ -908,10 +727,12 @@ export default {
pointer-events: none; //pointer-events:none用来控制该标签的点击穿透事件
}
}
.tip {
display: flex;
align-items: flex-start;
margin-top: 5px;
i {
margin: 3px 5px 0 0;
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

+7
View File
@@ -54,6 +54,8 @@ import upload from '@/components/element-ui/upload'
Vue.use(upload)
import Preview from '@/components/Preview/index'
Vue.use(Preview)
import Onlyoffice from '@/components/Preview_onlyoffice/index'
Vue.use(Onlyoffice)
import MFA from '@/components/MFA/index'
Vue.use(MFA)
import FB from '@/components/feedBack/index'
@@ -308,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;
}
+1 -1
View File
@@ -1,7 +1,7 @@
// eslint-disable-next-line no-undef
module.exports = {
title: 'IRCIS',
title: 'IRC Imaging System',
/**
* @type {boolean} true | false
+1
View File
@@ -862,6 +862,7 @@ const actions = {
const data = {}
data.StudyId = study.StudyId
data.StudyCode = study.StudyCode
data.StudyName = study.StudyName
data.Modalities = study.Modalities
data.SeriesCount = study.SeriesCount
data.InstanceCount = study.InstanceCount
+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);
}
+1 -1
View File
@@ -1,6 +1,6 @@
import defaultSettings from '@/settings'
const title = defaultSettings.title || 'IRCIS'
const title = defaultSettings.title || 'IRC Imaging System'
export default function getPageTitle(pageTitle) {
if (pageTitle) {
+149
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);
@@ -99,5 +201,52 @@ function metaDataProvider(type, imageId) {
columnPixelSpacing,
};
}
if (type === 'imagePixelModule') {
const imagePixelModule = {
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.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
@@ -445,7 +445,7 @@ function setTimer() {
totalBytes = totalBytes / 1024;
unit = "MB/s";
}
store.state.trials.uploadTip = totalBytes.toFixed(2) + unit;
store.state.trials.uploadTip = totalBytes.toFixed(3) + unit;
}
if (timeList.length >= 5) {
delete bytesReceivedPerSecond[timeList[0]]
+1 -1
View File
@@ -150,7 +150,7 @@ function setTimer() {
totalBytes = totalBytes / 1024;
unit = "MB/s";
}
store.state.trials.uploadTip = totalBytes.toFixed(2) + unit;
store.state.trials.uploadTip = totalBytes.toFixed(3) + unit;
}
if (timeList.length >= 5) {
delete bytesReceivedPerSecond[timeList[0]]
+66
View File
@@ -0,0 +1,66 @@
let TYPES = ['word', 'cell', 'slide', 'pdf']
let DOCUMENTTYPE = {
'.doc': TYPES[0],
'.docm': TYPES[0],
'.docx': TYPES[0],
'.dot': TYPES[0],
'.dotm': TYPES[0],
'.dotx': TYPES[0],
'.epub': TYPES[0],
'.fb2': TYPES[0],
'.fodt': TYPES[0],
'.htm': TYPES[0],
'.html': TYPES[0],
'.mht': TYPES[0],
'.mhtml': TYPES[0],
'.odt': TYPES[0],
'.ott': TYPES[0],
'.pages': TYPES[0],
'.rtf': TYPES[0],
'.stw': TYPES[0],
'.sxw': TYPES[0],
'.txt': TYPES[0],
'.wps': TYPES[0],
'.wpt': TYPES[0],
'.xml': TYPES[0],
'.csv': TYPES[1],
'.et': TYPES[1],
'.ett': TYPES[1],
'.fods': TYPES[1],
'.numbers': TYPES[1],
'.ods': TYPES[1],
'.ots': TYPES[1],
'.sxc': TYPES[1],
'.xls': TYPES[1],
'.xlsb': TYPES[1],
'.xlsm': TYPES[1],
'.xlsx': TYPES[1],
'.xlt': TYPES[1],
'.xltm': TYPES[1],
'.xltx': TYPES[1],
'.xml': TYPES[1],
'.dps': TYPES[2],
'.dpt': TYPES[2],
'.fodp': TYPES[2],
'.key': TYPES[2],
'.odp': TYPES[2],
'.otp': TYPES[2],
'.pot': TYPES[2],
'.potm': TYPES[2],
'.potx': TYPES[2],
'.pps': TYPES[2],
'.ppsm': TYPES[2],
'.ppsx': TYPES[2],
'.ppt': TYPES[2],
'.pptm': TYPES[2],
'.pptx': TYPES[2],
'.sxi': TYPES[2],
'.djvu': TYPES[3],
'.docxf': TYPES[3],
'.oform': TYPES[3],
'.oxps': TYPES[3],
'.pdf': TYPES[3],
'.xps': TYPES[3]
}
export default DOCUMENTTYPE
+3
View File
@@ -9,6 +9,7 @@ const ROUTER = require('@/router');
axios.defaults.withCredentials = false
const service = axios.create({
baseURL: '/api',
// baseURL: process.env.NODE_ENV === 'prod' ? "https://api.irc.extimaging.com" : '/api',
timeout: 2 * 360000, // request timeout
withCredentials: false
})
@@ -20,6 +21,8 @@ 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.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'
config.headers['TimeZoneId'] = moment.tz.guess()
@@ -1,100 +1,45 @@
<template>
<el-form
ref="sysAttachmentFrom"
v-loading="loading"
:model="form"
label-width="190px"
size="small"
:rules="rules"
class="upload-temporary-file"
>
<el-form ref="sysAttachmentFrom" v-loading="loading" :model="form" label-width="190px" size="small" :rules="rules"
class="upload-temporary-file">
<div class="base-dialog-body">
<el-form-item
:label="$t('dictionary:signature:form:FileTypeId')"
prop="FileTypeId"
>
<el-select
v-model="form.FileTypeId"
style="width: 100%"
size="small"
filterable
>
<el-option
v-for="item of dictionaryList.Sys_Document"
:key="item.Id"
:label="item.Value"
:value="item.Id"
/>
<el-form-item :label="$t('dictionary:signature:form:FileTypeId')" prop="FileTypeId">
<el-select v-model="form.FileTypeId" style="width: 100%" size="small" filterable>
<el-option v-for="item of dictionaryList.Sys_Document" :key="item.Id" :label="item.Value" :value="item.Id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:form:File')">
<div class="upload-container">
<el-upload
class="upload-demo"
action
accept=".pdf"
:before-upload="beforeUpload"
:http-request="handleUploadFile"
:on-preview="handlePreview"
:on-remove="handleRemoveFile"
:show-file-list="true"
:file-list="fileList"
:limit="1"
:on-exceed="handleExceed"
:disabled="form.Type === ''"
>
<el-button
size="small"
type="primary"
:disabled="form.FileTypeId === ''"
:loading="btnLoading"
>{{ $t('common:button:check') }}</el-button
>
<el-upload class="upload-demo" action accept=".pdf" :before-upload="beforeUpload"
:http-request="handleUploadFile" :on-preview="handlePreview" :on-remove="handleRemoveFile"
:show-file-list="true" :file-list="fileList" :limit="1" :on-exceed="handleExceed"
:disabled="form.Type === ''">
<el-button size="small" type="primary" :disabled="form.FileTypeId === ''" :loading="btnLoading">{{
$t('common:button:check') }}</el-button>
<span slot="tip" style="margin-left: 10px" class="el-upload__tip">
({{ $t('trials:signature:label:mustBepdf') }})
</span>
</el-upload>
</div>
</el-form-item>
<el-form-item
:label="$t('dictionary:signature:form:NeedConfirmedUserTypeIdList')"
prop="NeedConfirmedUserTypeIdList"
>
<el-select
v-model="form.NeedConfirmedUserTypeIdList"
style="width: 100%"
multiple
>
<el-option
v-for="item of userTypeOptions"
:key="item.Id"
:label="item.UserTypeShortName"
:value="item.Id"
/>
<el-form-item :label="$t('dictionary:signature:form:NeedConfirmedUserTypeIdList')"
prop="NeedConfirmedUserTypeIdList">
<el-select v-model="form.NeedConfirmedUserTypeIdList" style="width: 100%" multiple>
<el-option v-for="item of userTypeOptions" :key="item.Id" :label="item.UserTypeShortName" :value="item.Id" />
</el-select>
</el-form-item>
<el-form-item
:label="$t('dictionary:signature:form:DocUserSignType')"
prop="DocUserSignType"
>
<el-switch
v-model="form.DocUserSignType"
:active-value="1"
:inactive-value="0"
>
<el-form-item :label="$t('dictionary:signature:form:DocUserSignType')" prop="DocUserSignType">
<el-switch v-model="form.DocUserSignType" :active-value="1" :inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item
:label="$t('dictionary:signature:form:SignViewMinimumMinutes')"
prop="SignViewMinimumMinutes"
>
<el-input-number
v-model="form.SignViewMinimumMinutes"
controls-position="right"
:min="1"
:max="50"
/>
<el-form-item :label="$t('dictionary:signature:form:SignViewMinimumMinutes')" prop="SignViewMinimumMinutes">
<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="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="0" :max="1000" />
</el-form-item>
<!-- <el-form-item v-if="form.Id !== ''" label="是否废除: ">
<el-radio-group v-model="form.IsDeleted">
@@ -105,14 +50,8 @@
</div>
<div class="base-dialog-footer" style="text-align: right; margin-top: 10px">
<el-form-item style="text-align: right">
<el-button
size="small"
type="primary"
:disabled="form.FileTypeId === '' || form.Name === ''"
:loading="saveBtnLoading"
@click="handleSave"
>{{ $t('common:button:save') }}</el-button
>
<el-button size="small" type="primary" :disabled="form.FileTypeId === '' || form.Name === ''"
:loading="saveBtnLoading" @click="handleSave">{{ $t('common:button:save') }}</el-button>
</el-form-item>
</div>
</el-form>
@@ -138,9 +77,12 @@ export default {
FileTypeId: '',
Name: '',
Path: '',
IsDeleted: false,
IsPublish: false,
IsDeleted: true,
SignViewMinimumMinutes: null,
DocUserSignType: 0,
CurrentStaffTrainDays: 1,
NewStaffTrainDays: 14,
},
rules: {
FileTypeId: [
@@ -196,8 +138,11 @@ 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
this.form.NewStaffTrainDays = this.data.NewStaffTrainDays
}
this.loading = false
},
@@ -207,7 +152,7 @@ export default {
.then((res) => {
this.dictionaryList = { ...res.Result }
})
.catch(() => {})
.catch(() => { })
},
// 获取用户类型下拉数据
getUserType() {
@@ -305,13 +250,16 @@ export default {
width: 80px;
height: 40px;
}
.upload-container .el-input--small {
margin-bottom: 5px;
}
.upload-container .el-icon-circle-check {
color: #428bca;
font-size: 13px;
}
.account_item_clear {
.el-tag__close {
display: none !important;
@@ -0,0 +1,223 @@
<template>
<el-form ref="attachmentFrom" v-loading="loading" :model="form" label-width="190px" size="small" :rules="rules"
class="upload-temporary-file">
<div class="base-dialog-body">
<el-form-item :label="$t('dictionary:signature:form:FileName')" prop="Name">
<el-input v-model="form.Name" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:form:OffLine')" prop="OffLine">
<el-switch v-model="form.OffLine" :active-value="true" :inactive-value="false"
:active-text="$fd('YesOrNo', true)" :inactive-text="$fd('YesOrNo', false)">
</el-switch>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:form:File')">
<div class="upload-container">
<el-upload class="upload-demo" action :accept="this.accept.join(',')" :before-upload="beforeUpload"
:http-request="handleUploadFile" :on-preview="handlePreview" :on-remove="handleRemoveFile"
:show-file-list="true" :file-list="fileList" :limit="1" :on-exceed="handleExceed">
<el-button size="small" type="primary" :loading="btnLoading">{{ $t('common:button:check')
}}</el-button>
<span slot="tip" style="margin-left: 10px" class="el-upload__tip">
({{ $t('trials:signature:label:type').replace("xxx", this.accept.join(", ")) }})
</span>
</el-upload>
</div>
</el-form-item>
</div>
<div class="base-dialog-footer" style="text-align: right; margin-top: 10px">
<el-form-item style="text-align: right">
<el-button size="small" type="primary" :disabled="form.FileTypeId === '' || form.Name === ''"
:loading="saveBtnLoading" @click="handleSave">{{ $t('common:button:save') }}</el-button>
</el-form-item>
</div>
</el-form>
</template>
<script>
import { addOrUpdateSystemDocumentAttachment } from '@/api/dictionary'
export default {
props: {
data: {
type: Object,
default() {
return {}
},
},
SystemDocumentId: {
type: String,
default: ''
}
},
data() {
return {
form: {
Id: '',
Name: null,
FileFormat: null,
FileName: null,
FilePath: null,
FileSize: null,
OffLine: false,
SystemDocumentId: null,
},
rules: {
Name: [
{
required: true,
message: this.$t('common:ruleMessage:specify'),
trigger: ['change'],
},
],
},
fileList: [],
btnLoading: false,
saveBtnLoading: false,
loading: false,
accept: ['.jpg',
'.jpeg',
'.png',
'.pdf',
'.ppt',
'.pptx',
'.doc',
'.docx',
'.xls',
'.xlsx']
}
},
mounted() {
this.initForm()
},
methods: {
async initForm() {
this.loading = true
if (this.data && this.data.Id) {
Object.keys(this.form).forEach(key => {
this.form[key] = this.data[key]
})
this.fileList = [
{
name: this.data.FileName,
url: this.data.FilePath,
path: this.data.FilePath,
},
]
}
this.loading = false
},
beforeUpload(file) {
// 检测文件类型是否符合要求
if (this.checkFileSuffix(file.name)) {
this.fileList = []
return true
} else {
this.$alert(this.$t('trials:signature:label:type').replace("xxx", this.accept.join(", ")))
return false
}
},
async handleUploadFile(param) {
this.loading = true
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(
`/System/DocumentToSign/${param.file.name}${new Date().getTime()}`,
file
)
this.fileList.push({
name: param.file.name,
path: this.$getObjectName(res.url),
url: this.$getObjectName(res.url),
})
this.form.Name = param.file.name
this.form.FileName = param.file.name
this.form.FilePath = this.$getObjectName(res.url)
this.form.FileSize = param.file.size
this.form.FileFormat = param.file.name
.substring(param.file.name.lastIndexOf('.'))
.toLocaleLowerCase().split('.')[1]
this.loading = false
},
handleSave() {
this.$refs.attachmentFrom.validate((valid) => {
if (!valid) return false
if (!this.form.FilePath)
return this.$alert(this.$t('trials:signature:message:selectFile'))
this.saveBtnLoading = true
if (this.SystemDocumentId) this.form.SystemDocumentId = this.SystemDocumentId
addOrUpdateSystemDocumentAttachment(this.form)
.then((res) => {
this.saveBtnLoading = false
this.$emit('closeDialog')
this.$emit('getList')
this.$message.success(this.$t('common:message:updatedSuccessfully'))
})
.catch(() => {
this.saveBtnLoading = false
})
})
},
handleRemoveFile() {
this.fileList = []
this.form.FilePath = ''
this.form.FileSize = ''
this.form.FileFormat = ''
this.form.FileName = ''
},
handlePreview(file) {
if (file.fullPath) {
window.open(file.fullPath, '_blank')
}
},
handleExceed(files, fileList) {
this.$message.warning(this.$t('upload:rule:maxFile1'))
},
checkFileSuffix(fileName) {
var typeArr = [
'jpg',
'jpeg',
'png',
'pdf',
'ppt',
'pptx',
'doc',
'docx',
'xls',
'xlsx',
]
var extendName = fileName
.substring(fileName.lastIndexOf('.') + 1)
.toLocaleLowerCase()
if (typeArr.indexOf(extendName) !== -1) {
return true
} else {
return false
}
},
},
}
</script>
<style lang="scss">
.upload-temporary-file {
.upload-container .el-upload--text {
border: none;
width: 80px;
height: 40px;
}
.upload-container .el-input--small {
margin-bottom: 5px;
}
.upload-container .el-icon-circle-check {
color: #428bca;
font-size: 13px;
}
.account_item_clear {
.el-tag__close {
display: none !important;
}
}
}
</style>
@@ -0,0 +1,258 @@
<template>
<div v-if="config.visible">
<base-model :config="config">
<template slot="dialog-body">
<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">
<el-table-column type="index" width="60" />
<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" 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)"
:inactive-text="$fd('YesOrNo', false)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="CreateTime" :label="$t('dictionary:signature:attachmentList:CreateTime')"
sortable="custom" />
<el-table-column :label="$t('common:action:action')" min-width="120px">
<template slot-scope="scope">
<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)" 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)" v-if="!isView">
{{ $t('common:button:delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增/编辑附件 -->
<el-dialog :visible.sync="visible" :close-on-click-modal="false" :append-to-body="true" :title="title"
width="800px" custom-class="base-dialog-wrapper">
<attachmentForm v-if="visible" :data="rowData" :SystemDocumentId="SystemDocumentId"
@closeDialog="closeDialog" @getList="getAllList" />
</el-dialog>
</template>
</base-model>
<viewer ref="picture_perview2" style="margin: 0 10px"
v-if="rowData.FileFormat && ['png', 'jpg', 'jpeg'].includes(rowData.FileFormat.toLowerCase())"
:images="[`${OSSclientConfig.basePath}${rowData.FilePath}`]" :options="viewerOptions">
<img v-show="false" :src="`${OSSclientConfig.basePath}${rowData.FilePath}`" alt="Image" />
</viewer>
<!-- <attachmentPreview :SystemDocumentId="SystemDocumentId" :visible.sync="perview_visible"
v-if="perview_visible" /> -->
</div>
</template>
<script>
import BaseModel from '@/components/BaseModel'
import attachmentForm from './attachmentForm'
// import attachmentPreview from './attachmentPreview'
import { addOrUpdateSystemDocumentAttachment, getSystemDocumentAttachmentList, deleteSystemDocumentAttachment } from '@/api/dictionary'
const defaultSearchData = () => {
return {
PageIndex: 1,
PageSize: 1000,
Asc: false,
OffLine: null,
SortField: null
}
}
export default {
components: {
BaseModel,
attachmentForm,
// attachmentPreview
},
props: {
config: {
type: Object,
default: () => {
return {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
}
}
},
isView: {
type: Boolean,
default: false
},
rowData: {
type: Object,
default: () => {
return {}
}
},
SystemDocumentId: {
type: String,
default: ''
}
},
data() {
return {
searchData: defaultSearchData(),
visible: false,
title: null,
list: [],
rowData: {},
loading: false,
viewerOptions: {
toolbar: {
zoomIn: true,
zoomOut: true,
reset: true,
prev: false,
next: false,
rotateLeft: true,
rotateRight: true,
flipHorizontal: true,
flipVertical: true,
}
},
// perview_visible: false
}
},
watch: {
SystemDocumentId: {
handler() {
this.getList()
},
immediate: true,
}
},
methods: {
closeDialog() {
this.visible = false
},
getAllList() {
this.getList()
this.$emit("getList")
},
async del(row) {
try {
let confirm = await this.$confirm(this.$t("dictionary:signature:attachmentList:message:del"))
if (!confirm) return false
this.loading = true
let res = await deleteSystemDocumentAttachment(row.Id)
this.loading = false
if (res.IsSuccess) {
this.getAllList()
}
} catch (err) {
console.log(err)
this.loading = false
}
},
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
},
async getList() {
try {
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) {
this.list = res.Result.CurrentPageData
}
} catch (err) {
this.loading = false
console.log(err)
}
},
async OffLine(row, val) {
try {
let data = Object.assign({}, row)
data.OffLine = val
this.loading = true
let res = await addOrUpdateSystemDocumentAttachment(data)
this.loading = false
if (res.IsSuccess) {
this.$t('dictionary:signature:attachmentList:updateSuccessfully')
this.getList()
}
} catch (err) {
this.loading = false
row.OffLine = !row.OffLine
console.log(err)
}
},
preview(data) {
// return this.perview_visible = true
this.rowData = Object.assign({}, data)
if (['.ppt',
'.pptx',
'.doc',
'.docx',
'.xls',
'.xlsx'].includes(`.${data.FileFormat.toLowerCase()}`)) {
this.$onlyOffice({
path: data.FilePath,
type: data.FileFormat,
title: data.Name
})
}
if (['.jpg',
'.jpeg',
'.png'].includes(`.${data.FileFormat.toLowerCase()}`)) {
this.$nextTick(() => {
this.$refs['picture_perview2'].$viewer.show()
})
}
if (['.pdf'].includes(`.${data.FileFormat.toLowerCase()}`)) {
this.$preview({
path: data.Path || data.FilePath,
type: 'pdf',
title: data.Name,
})
}
},
// 排序
handleSortByColumn(column) {
if (column.order === 'ascending') {
this.searchData.Asc = true
} else {
this.searchData.Asc = false
}
this.searchData.SortField = column.prop
this.searchData.PageIndex = 1
this.getList()
},
}
}
</script>
<style lang="scss" scoped>
.name {
display: block;
width: 90%;
}
</style>
@@ -0,0 +1,312 @@
<template>
<el-dialog :visible.sync="visible" :title="title" :fullscreen="true" append-to-body
custom-class="base-dialog-wrapper" :before-close="handleClose">
<div v-loading="loading" class="img-container" v-if="visible">
<el-card class="box-card left">
<div class="title">
{{ $t('dictionary:signature:fileList') }}
</div>
<div class="left-content">
<!-- 检查层级 -->
<div id="imgList" style="height: 100%; overflow: hidden">
<template v-for="(item, index) in list">
<div :id="`img${item.Id}`" :key="item.Id" :class="{
'is-boxActive': item.Id === rowData.Id,
}" class="img-box" @click="selected(item)">
<div class="file-image">
<el-image v-if="
[
'jpeg',
'jpg',
'png',
].includes(item.FileFormat)
" style="width: 100%; height: 100%"
:src="`${OSSclientConfig.basePath}${item.FilePath}?x-oss-process=image/resize,w_50,h_50/format,png`"
fit="contain" crossorigin="anonymous" />
<el-image v-else style="width: 100%; height: 100%" :src="getsrc(item.FileFormat)"
fit="contain" crossorigin="anonymous" />
</div>
<div v-if="item.Name.length < 15" class="img-text">
{{ `${index + 1}. ${item.Name}` }}
</div>
<el-tooltip v-else :content="item.Name" placement="bottom">
<div class="img-text">
{{ `${index + 1}. ${item.Name}` }}
</div>
</el-tooltip>
</div>
</template>
</div>
</div>
</el-card>
<!-- 预览图像 -->
<el-card class="box-card right">
<div style="width: 100%; height: 100%" v-if="[
'jpeg',
'jpg',
'png',
].includes(rowData.FileFormat)">
<imageViewer :rowData="rowData" />
</div>
<div style="width: 100%; height: 100%" v-else>
<PreviewFile v-if='rowData.FilePath' :file-path="rowData.FilePath" :file-type="rowData.FileFormat"
:title="rowData.Name" />
</div>
</el-card>
<!-- <el-card class="box-card" style="width:300px;height:100%;padding: 10px;margin-left:10px;">
<CheckForm />
</el-card> -->
</div>
</el-dialog>
</template>
<script>
import pdf from '@/assets/file_icon/pdf.png'
import zip from '@/assets/file_icon/zip.png'
import doc from '@/assets/file_icon/doc.png'
import docx from '@/assets/file_icon/docx.png'
import ppt from '@/assets/file_icon/ppt.png'
import pptx from '@/assets/file_icon/pptx.png'
import xls from '@/assets/file_icon/xls.png'
import xlsx from '@/assets/file_icon/xlsx.png'
import PreviewFile from '@/components/PreviewFile'
import imageViewer from './image-viewer'
import { getSystemDocumentAttachmentList } from '@/api/dictionary'
const defaultSearchData = () => {
return {
PageIndex: 1,
PageSize: 1000,
Asc: false,
OffLine: null,
SortField: null
}
}
export default {
name: 'Notice',
components: {
PreviewFile,
imageViewer
},
props: {
SystemDocumentId: {
type: String,
default: ''
},
visible: {
type: Boolean,
default: false
},
isView: {
type: Boolean,
default: false
}
},
data() {
return {
pdf,
zip,
doc,
docx,
ppt,
pptx,
xls,
xlsx,
rowData: {},
list: [],
searchData: defaultSearchData(),
title: this.$t('dictionary:signature:fileList')
}
},
watch: {
SystemDocumentId: {
handler() {
this.getList()
},
immediate: true,
}
},
methods: {
getsrc(type) {
return this[type.toLowerCase()]
},
selected(row) {
this.rowData = row
},
handleClose() {
this.$emit("update:visible", false)
},
async getList() {
try {
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) {
this.list = res.Result.CurrentPageData
this.rowData = this.list[0] || {}
}
} catch (err) {
this.loading = false
console.log(err)
}
},
},
}
</script>
<style lang="scss" scoped>
.img-container {
position: relative;
width: 100%;
height: 100%;
padding: 10px;
display: flex;
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
::v-deep .el-card__body {
padding: 0px;
}
}
.study-desc {
padding: 10px 5px;
line-height: 20px;
background-color: #d5d5d5;
font-weight: 500;
}
.left {
width: 220px;
height: 100%;
::v-deep .el-card__body {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.title {
height: 40px;
line-height: 40px;
border: 1ppx solid;
border: 1px solid #ebe7e7;
padding-left: 10px;
background-color: #4e4e4e;
color: #ffffff;
}
.left-content {
flex: 1;
overflow-y: auto;
}
// ::v-deep .el-tabs{
// height: 100%;
// }
// ::v-deep .el-tabs__header{
// height: 40px;
// }
// ::v-deep .el-tabs__content{
// flex: 1;
// overflow-y: auto;
// padding: 0;
// }
.img-box {
// position: relative;
display: inline-block;
box-sizing: border-box;
border-bottom: 2px solid #f3f3f3;
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-start;
padding: 5px 10px;
cursor: pointer;
// margin-bottom: 5px;
padding-left: 5px;
}
.img-text {
display: inline-block;
width: calc(100% - 60px);
margin-left: 5px;
height: 50px;
line-height: 50px;
overflow: hidden;
text-overflow: ellipsis;
/* 用省略号表示溢出的文本 */
white-space: nowrap;
}
.img-box:nth-last-child(1) {
margin-bottom: 0px;
}
.is-boxActive {
// border-color: #409eff;
color: #409eff;
}
.is-boxActiv:after {
opacity: 0;
}
}
.right {
flex: 1;
height: 100%;
margin-left: 10px;
::v-deep .el-card__body {
height: 100%;
width: 100%;
}
}
.switchBox {
width: 100%;
margin: 5px 0;
color: #4e4e4e;
.item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 5px;
}
}
.file-image {
width: 50px;
height: 50px;
}
::v-deep .el-collapse-item__header {
background-color: #d5d5d5;
}
::v-deep .el-collapse-item__header {
min-height: 48px;
height: auto;
}
::v-deep .el-collapse-item__content {
padding-bottom: 0;
}
</style>
@@ -0,0 +1,381 @@
/* eslint-disable no-unused-vars */
<template>
<div style="width: 100%; height: 100%">
<transition name="viewer-fade">
<div ref="image-viewer__wrapper" tabindex="-1" class="image-viewer__wrapper" :style="{ 'z-index': 5 }">
<!-- Arrow -->
<!-- 工具栏 -->
<div class="image-viewer__btn image-viewer__actions">
<div class="image-viewer__actions__inner">
<i class="el-icon-zoom-out" @click="handleActions('zoomOut')" />
<i class="el-icon-zoom-in" @click="handleActions('zoomIn')" />
<i class="el-image-viewer__actions__divider" />
<i class="el-icon-c-scale-to-original" @click="toggleMode" />
<i class="el-image-viewer__actions__divider" />
<i class="el-icon-refresh-left" @click="handleActions('anticlocelise')" />
<i class="el-icon-refresh-right" @click="handleActions('clocelise')" />
</div>
</div>
<!-- 图片 -->
<div id="image-viewer__canvas" class="image-viewer__canvas">
<img crossorigin="anonymous" :src="`${OSSclientConfig.basePath}${rowData.FilePath}`
" :style="imgStyle" style="max-width: 100%; max-height: 100%" @load="handleImgLoad" @error="handleImgError"
@mousedown="handleMouseDown" />
</div>
</div>
</transition>
</div>
</template>
<script>
import { on, off } from 'element-ui/src/utils/dom'
import { rafThrottle, isFirefox } from 'element-ui/src/utils/util'
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
export default {
name: 'ImageViewer',
props: {
onSwitch: {
type: Function,
default: () => { },
},
onClose: {
type: Function,
default: () => { },
},
rowData:{
type: Object,
default: () => {
return {}
},
}
},
data() {
return {
loading: false,
transform: {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false,
},
}
},
computed: {
imgStyle() {
const { scale, deg, offsetX, offsetY, enableTransition } = this.transform
const style = {
transform: `scale(${scale}) rotate(${deg}deg)`,
transition: enableTransition ? 'transform .3s' : '',
'margin-left': `${offsetX}px`,
'margin-top': `${offsetY}px`,
}
return style
},
},
mounted() {
document.getElementById('image-viewer__canvas').onmousewheel = (event) => {
if (event.deltaY > 0) {
// 缩小
this.handleActions('zoomOut', {
zoomRate: 0.015,
enableTransition: false,
})
} else {
// 放大
this.handleActions('zoomIn', {
zoomRate: 0.015,
enableTransition: false,
})
}
return false
}
this.deviceSupportInstall()
},
methods: {
hide() {
this.deviceSupportUninstall()
this.onClose()
},
deviceSupportInstall() {
this._keyDownHandler = (e) => {
e.stopPropagation()
const keyCode = e.keyCode
switch (keyCode) {
// ESC
case 27:
this.hide()
break
// SPACE
case 32:
this.toggleMode()
break
// LEFT_ARROW
case 37:
this.prev()
break
// UP_ARROW
case 38:
this.handleActions('zoomIn')
break
// RIGHT_ARROW
case 39:
this.next()
break
// DOWN_ARROW
case 40:
this.handleActions('zoomOut')
break
}
}
this._mouseWheelHandler = rafThrottle((e) => {
const delta = e.wheelDelta ? e.wheelDelta : -e.detail
if (delta > 0) {
this.handleActions('zoomIn', {
zoomRate: 0.015,
enableTransition: false,
})
} else {
this.handleActions('zoomOut', {
zoomRate: 0.015,
enableTransition: false,
})
}
})
on(document, 'keydown', this._keyDownHandler)
on(document, mousewheelEventName, null)
// on(document, mousewheelEventName, this._mouseWheelHandler)
},
deviceSupportUninstall() {
off(document, 'keydown', this._keyDownHandler)
off(document, mousewheelEventName, this._mouseWheelHandler)
this._keyDownHandler = null
this._mouseWheelHandler = null
},
handleImgLoad(e) {
this.loading = false
},
handleImgError(e) {
this.loading = false
e.target.alt = '加载失败'
},
handleMouseDown(e) {
if (this.loading || e.button !== 0) return
const { offsetX, offsetY } = this.transform
const startX = e.pageX
const startY = e.pageY
this._dragHandler = rafThrottle((ev) => {
this.transform.offsetX = offsetX + ev.pageX - startX
this.transform.offsetY = offsetY + ev.pageY - startY
})
on(document, 'mousemove', this._dragHandler)
on(document, 'mouseup', (ev) => {
off(document, 'mousemove', this._dragHandler)
})
e.preventDefault()
},
reset() {
this.transform = {
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false,
}
},
toggleMode() {
if (this.loading) return
this.reset()
},
handleActions(action, options = {}) {
if (this.loading) return
const { zoomRate, rotateDeg, enableTransition } = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options,
}
const { transform } = this
switch (action) {
case 'zoomOut':
if (transform.scale > 0.2) {
transform.scale = parseFloat(
(transform.scale - zoomRate).toFixed(3)
)
}
break
case 'zoomIn':
if (transform.scale < 5) {
transform.scale = parseFloat(
(transform.scale + zoomRate).toFixed(3)
)
}
break
case 'clocelise':
transform.deg += rotateDeg
break
case 'anticlocelise':
transform.deg -= rotateDeg
break
}
transform.enableTransition = enableTransition
},
},
}
</script>
<style lang="scss" scoped>
.image-viewer__wrapper {
position: relative;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
.image-viewer__btn {
position: absolute;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
opacity: 0.8;
cursor: pointer;
box-sizing: border-box;
user-select: none;
}
.image-viewer__close {
display: none;
top: 40px;
right: 40px;
width: 40px;
height: 40px;
font-size: 40px;
}
.image-viewer_desc {
position: absolute;
top: 40px;
left: 40px;
font-size: 15px;
padding: 5px;
height: 30px;
width: 70px;
line-height: 20px;
text-align: center;
color: #fff;
background-color: #606266;
border-color: #fff;
z-index: 1;
border-radius: 17px;
// border-radius: 2%;
}
.image-viewer__canvas {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.image-viewer__actions {
left: 50%;
bottom: 30px;
transform: translateX(-50%);
width: 282px;
height: 44px;
padding: 0 23px;
background-color: #606266;
border-color: #fff;
border-radius: 22px;
}
.image-viewer__actions__inner {
width: 100%;
height: 100%;
text-align: justify;
cursor: default;
font-size: 23px;
color: #fff;
display: flex;
align-items: center;
justify-content: space-around;
}
.image-viewer__next,
.image-viewer__prev {
top: 50%;
width: 44px;
height: 44px;
font-size: 24px;
color: #fff;
background-color: #606266;
border-color: #fff;
}
.image-viewer__prev {
transform: translateY(-50%);
left: 40px;
}
.image-viewer__next {
transform: translateY(-50%);
right: 40px;
text-indent: 2px;
}
.image-viewer__mask {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0.5;
// background:#000
}
.viewer-fade-enter-active {
animation: viewer-fade-in 0.3s;
}
.viewer-fade-leave-active {
animation: viewer-fade-out 0.3s;
}
@keyframes viewer-fade-in {
0% {
transform: translate3d(0, -20px, 0);
opacity: 0;
}
100% {
transform: translate3d(0, 0, 0);
opacity: 1;
}
}
@keyframes viewer-fade-out {
0% {
transform: translate3d(0, 0, 0);
opacity: 1;
}
100% {
transform: translate3d(0, -20px, 0);
opacity: 0;
}
}
</style>
@@ -1,80 +1,103 @@
<template>
<BaseContainer>
<template slot="title-container">
<el-row v-if="isSystem" style="width: 100%;">
<el-col :span="12">
<h3>{{ TITLE }}</h3>
</el-col>
<el-col :span="12" style="text-align:right;">
<h3>
<Pagination class="page" :total="total" :page.sync="searchData.pageIndex" :limit.sync="searchData.pageSize"
layout="total, sizes, prev, pager, next" :background="false" style="display: inline-block;"
@pagination="getList" />
<el-button icon="el-icon-refresh-left" size="small" circle :title="$t('common:button:reset')"
@click="handleReset" />
</h3>
</el-col>
</el-row>
</template>
<template slot="search-container">
<el-form :inline="true" size="small">
<el-form-item :label="$t('dictionary:signature:search:FileTypeId')">
<el-select v-model="searchData.FileTypeId" style="width: 150px">
<el-option
v-for="item of dict.type.Sys_Document"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="searchData.FileTypeId" style="width: 150px" clearable>
<el-option v-for="item of $d.Sys_Document" :key="item.id" :label="item.label" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:search:Name')">
<el-input v-model="searchData.Name" style="width: 130px" clearable />
</el-form-item>
<el-form-item :label="$t('dictionary:signature:table:NeedConfirmedUserTypes')">
<el-select v-model="searchData.UserTypeId" style="width: 150px" clearable>
<el-option v-for="item of userTypeOptions" :key="item.Id" :label="item.UserTypeShortName"
:value="item.Id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:table:IsDeleted')">
<el-select v-model="searchData.IsDeleted" style="width: 150px" clearable>
<el-option v-for="item of $d.TrainingStatus" :key="item.id" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:table:DocUserSignType')">
<el-select v-model="searchData.DocUserSignType" style="width: 150px" clearable>
<el-option v-for="item of $d.ReadingYesOrNo" :key="item.id" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:signature:table:createTime')">
<el-date-picker v-model="timeList" @change="changeTimeList" value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']" type="datetimerange">
</el-date-picker>
</el-form-item>
<el-form-item>
<!-- 查询 -->
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<!-- 重置 -->
<el-button
type="primary"
icon="el-icon-refresh-left"
@click="handleReset"
>
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
<el-button type="primary" size="small" @click="handleAdd">
{{ $t('common:button:new') }}
</el-button>
<el-button type="primary" size="small" :disabled="multipleSelection.length <= 0"
@click="publishSystemDocument(multipleSelection)">
{{ $t('dictionary:signature:button:publish') }}
</el-button>
<el-button type="primary" size="small" :disabled="multipleSelection.length <= 0"
@click="handleRepeal(multipleSelection)">
{{ $t('common:button:revoke') }}
</el-button>
</el-form-item>
</el-form>
<el-button
type="primary"
style="margin-left: auto; margin-bottom: 10px"
size="small"
@click="handleAdd"
>
{{ $t('common:button:new') }}
</el-button>
</template>
<template slot="main-container">
<el-table
v-adaptive="{ bottomOffset: 60 }"
v-loading="loading"
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<el-table-column type="index" width="40" />
<el-table-column
prop="FileType"
:label="$t('dictionary:signature:table:FileType')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
prop="Name"
:label="$t('dictionary:signature:table:Name')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
prop="SignViewMinimumMinutes"
:label="$t('dictionary:signature:table:SignViewMinimumMinutes')"
show-overflow-tooltip
sortable="custom"
min-width="110"
/>
<el-table-column
prop="NeedConfirmedUserTypes"
:label="$t('dictionary:signature:table:NeedConfirmedUserTypes')"
show-overflow-tooltip
>
<el-table v-adaptive="{ bottomOffset: 60 }" v-loading="loading" :data="list" stripe height="100"
@sort-change="handleSortByColumn" @selection-change="handleSelectionChange">
<!-- <el-table-column type="index" width="40" /> -->
<el-table-column type="selection" width="55" :selectable="handleSelectable">
</el-table-column>
<el-table-column prop="FileType" :label="$t('dictionary:signature:table:FileType')" show-overflow-tooltip
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')"
show-overflow-tooltip sortable="custom" min-width="200px" />
<el-table-column prop="NewStaffTrainDays" :label="$t('dictionary:signature:table:NewStaffTrainDays')"
show-overflow-tooltip sortable="custom" min-width="200px" />
<el-table-column prop="NeedConfirmedUserTypes" :label="$t('dictionary:signature:table:NeedConfirmedUserTypes')"
show-overflow-tooltip>
<template slot-scope="scope">
{{
scope.row.NeedConfirmedUserTypes
@@ -83,12 +106,8 @@
}}
</template>
</el-table-column>
<el-table-column
prop="IsDeleted"
:label="$t('dictionary:signature:table:IsDeleted')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="IsDeleted" :label="$t('dictionary:signature:table:IsDeleted')" show-overflow-tooltip
sortable="custom" min-width="120px">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsDeleted" type="danger">{{
$fd('TrainingStatus', scope.row.IsDeleted)
@@ -98,103 +117,89 @@
}}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="DocUserSignType"
:label="$t('dictionary:signature:table:DocUserSignType')"
show-overflow-tooltip
sortable="custom"
min-width="90"
>
<el-table-column prop="IsPublish" :label="$t('dictionary:signature:table:IsPublish')" show-overflow-tooltip
sortable="custom" min-width="120px">
<template slot-scope="scope">
<el-tag :type="scope.row.IsPublish ? 'primary' : 'danger'">{{
$fd('AttachmentPublishStatus', scope.row.IsPublish)
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="DocUserSignType" :label="$t('dictionary:signature:table:DocUserSignType')"
show-overflow-tooltip sortable="custom" min-width="150px">
<template slot-scope="scope">
{{ $fd('ReadingYesOrNo', Number(scope.row.DocUserSignType)) }}
</template>
</el-table-column>
<el-table-column
prop="CreateTime"
:label="$t('dictionary:signature:table:CreateTime')"
show-overflow-tooltip
sortable="custom"
/>
<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="120">
<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.IsDeleted"
type="text"
@click="handleRepeal(scope.row)"
>
{{ $t('common:button:revoke') }}
<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 type="text" @click="handleDelete(scope.row)">
{{ $t('common:button:delete') }}
<el-button :disabled="scope.row.IsDeleted" circle icon="el-icon-close" :title="$t('common:button:revoke')"
@click="handleRepeal([scope.row])">
</el-button>
<el-button circle icon="el-icon-delete" :title="$t('common:button:delete')"
@click="handleDelete(scope.row)">
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
class="page"
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" v-if="!isSystem" />
<!-- 新增/编辑 -->
<el-dialog
v-if="editVisible"
:visible.sync="editVisible"
:close-on-click-modal="false"
:title="title"
width="600px"
custom-class="base-dialog-wrapper"
>
<TemplateForm
:data="currentRow"
@closeDialog="closeDialog"
@getList="getList"
/>
<el-dialog v-if="editVisible" :visible.sync="editVisible" :close-on-click-modal="false" :title="title"
width="600px" custom-class="base-dialog-wrapper">
<TemplateForm :data="currentRow" @closeDialog="closeDialog" @getList="getList" />
</el-dialog>
<!--附件列表-->
<attachmentList v-if="config.visible" :config="config" :rowData="currentRow" :SystemDocumentId="SystemDocumentId"
@getList="getList" />
<!-- 预览文件 -->
<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"
/>
<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>
</el-dialog>
</template>
</BaseContainer>
</template>
<script>
import { getSystemDocumentList, deleteSystemDocument } from '@/api/dictionary'
import { userAbandonDoc } from '@/api/trials'
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'
import TemplateForm from './TemplateForm'
import attachmentList from './attachmentList'
import PreviewFile from '@/components/PreviewFile/index'
import attachmentPreview from './attachmentPreview'
const searchDataDefault = () => {
return {
FileTypeId: '',
BeginCreateTime: null,
EndCreateTime: null,
IsDeleted: null,
DocUserSignType: null,
UserTypeId: null,
Name: '',
PageIndex: 1,
PageSize: 20,
@@ -202,8 +207,18 @@ const searchDataDefault = () => {
}
export default {
name: 'SignatureTemplate',
components: { BaseContainer, Pagination, TemplateForm, PreviewFile },
components: { BaseContainer, Pagination, TemplateForm, PreviewFile, attachmentList, attachmentPreview },
dicts: ['Sys_Document'],
props: {
isSystem: {
type: Boolean,
default: false
},
TITLE: {
type: String,
default: ''
}
},
data() {
return {
searchData: searchDataDefault(),
@@ -216,12 +231,82 @@ export default {
previewVisible: false,
title: '',
loading: false,
userTypeOptions: [],
timeList: [],
attachment_Visible: false,
SystemDocumentId: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
multipleSelection: [],
perview_visible: false
}
},
mounted() {
this.getList()
this.getUserType()
},
methods: {
handleSelectable(row) {
return !row.IsDeleted || !row.IsPublish
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
// 发布
async publishSystemDocument(list) {
try {
let confirm = await this.$confirm(this.$t('dictionary:signature:confirm:publishFile'), {
type: 'warning',
distinguishCancelAndClose: true,
})
if (!confirm) return false
let arr = list.filter(item => !item.IsPublish)
if (arr.length <= 0) return this.getList()
let data = {
ids: arr.map(item => item.Id)
}
this.loading = true
let res = await publishSystemDocument(data)
this.loading = false
if (res.IsSuccess) {
this.getList()
}
} catch (err) {
this.loading = false
console.log(err)
}
},
openAttachment(row, isList = false) {
this.SystemDocumentId = row.Id
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
changeTimeList() {
if (this.timeList) {
this.searchData.BeginCreateTime = this.timeList[0]
this.searchData.EndCreateTime = this.timeList[1]
} else {
this.searchData.BeginCreateTime = null
this.searchData.EndCreateTime = null
}
},
// 获取用户类型下拉数据
getUserType() {
getTrialUserTypeList()
.then((res) => {
this.userTypeOptions = res.Result
})
.catch(() => {
this.loading = false
})
},
// 获取系统文件数据
getList() {
this.loading = true
@@ -251,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
},
// 编辑
@@ -272,8 +368,13 @@ export default {
distinguishCancelAndClose: true,
})
.then(() => {
let arr = row.filter(item => !item.IsDeleted)
if (arr.length <= 0) return this.getList()
let data = {
Ids: arr.map(item => item.Id)
}
this.loading = true
userAbandonDoc(row.Id, true)
outLineSystemDocument(data)
.then((res) => {
this.loading = false
if (res.IsSuccess) {
@@ -287,7 +388,7 @@ export default {
this.loading = false
})
})
.catch(() => {})
.catch(() => { })
},
// 删除
handleDelete(row) {
@@ -317,6 +418,7 @@ export default {
// 关闭弹窗
closeDialog() {
this.editVisible = false
this.attachment_Visible = false
},
// 重置
handleReset() {
@@ -4,319 +4,160 @@
<el-form :inline="true" class="base-search-form">
<!-- 是否需要回执 -->
<el-form-item :label="$t('trials:emailManageCfg:title:isReturnRequired')" v-if="!systemLevel">
<el-select
v-model="searchData.IsReturnRequired"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.YesOrNo"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-select v-model="searchData.IsReturnRequired" clearable style="width: 100px">
<el-option v-for="item of $d.YesOrNo" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<!-- 是否加急 -->
<el-form-item :label="$t('dictionary:email:label:IsUrgent')" v-if="!systemLevel">
<el-select
v-model="searchData.IsUrgent"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.YesOrNo"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-select v-model="searchData.IsUrgent" clearable style="width: 100px">
<el-option v-for="item of $d.YesOrNo" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<!-- 是否启用 -->
<el-form-item :label="$t('trials:emailManageCfg:table:IsEnable')" v-if="!systemLevel">
<el-select
v-model="searchData.IsEnable"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.YesOrNo"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-select v-model="searchData.IsEnable" clearable style="width: 100px">
<el-option v-for="item of $d.YesOrNo" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<el-form-item
:label="$t('trials:auditRecord:table:criterion')"
v-if="isDistinguishCriteria && !systemLevel"
>
<el-select
v-model="searchData.CriterionTypeEnum"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.CriterionType"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-form-item :label="$t('trials:auditRecord:table:criterion')" v-if="isDistinguishCriteria && !systemLevel">
<el-select v-model="searchData.CriterionTypeEnum" clearable style="width: 100px">
<el-option v-for="item of $d.CriterionType" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('trials:emailManageCfg:form:BusinessModule')">
<el-select
v-model="searchData.BusinessModuleEnum"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.BusinessModule"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-select v-model="searchData.BusinessModuleEnum" clearable style="width: 100px">
<el-option v-for="item of $d.BusinessModule" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<el-form-item
:label="$t('trials:emailManageCfg:form:BusinessScenario')"
>
<el-select
v-model="searchData.BusinessScenarioEnum"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.Email_BusinessScenario"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-form-item :label="$t('trials:emailManageCfg:form:BusinessScenario')">
<el-select v-model="searchData.BusinessScenarioEnum" clearable style="width: 100px">
<el-option v-for="item of $d.Email_BusinessScenario" :value="item.value" :label="item.label"
:key="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t('trials:emailManageCfg:form:ToUserType')">
<el-select
v-model="searchData.ToUserType"
clearable
style="width: 100px"
>
<el-option
v-for="item of UserTypeList"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-select v-model="searchData.ToUserType" clearable style="width: 100px">
<el-option v-for="item of UserTypeList" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<el-form-item
:label="$t('trials:emailManageCfg:form:EmailUrgent')"
v-if="systemLevel === 1"
>
<el-select
v-model="searchData.EmailUrgentEnum"
clearable
style="width: 100px"
>
<el-option
v-for="item of $d.EmailUrgent"
:value="item.value"
:label="item.label"
:key="item.id"
/>
<el-form-item :label="$t('trials:emailManageCfg:form:EmailUrgent')" v-if="systemLevel === 1">
<el-select v-model="searchData.EmailUrgentEnum" clearable style="width: 100px">
<el-option v-for="item of $d.EmailUrgent" :value="item.value" :label="item.label" :key="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<el-button
type="primary"
icon="el-icon-refresh-left"
@click="handleReset"
>
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
</el-form>
<span style="margin-left: auto">
<el-button type="primary" @click="handleExport">
{{ $t('common:button:export') }}
</el-button>
<!-- <el-button
<el-form-item>
<el-button type="primary" @click="handleExport">
{{ $t('common:button:export') }}
</el-button>
<!-- <el-button
type="primary"
size="mini"
>
场景配置
</el-button> -->
<el-button type="primary" @click="handleAdd">
{{ $t('common:button:new') }}
</el-button>
</span>
<el-button type="primary" @click="handleAdd">
{{ $t('common:button:new') }}
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 受试者列表 -->
<el-table
v-loading="loading"
v-adaptive="{ bottomOffset: 45 }"
:data="list"
stripe
height="100"
style="width: 100%"
@sort-change="handleSortByColumn"
>
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 45 }" :data="list" stripe height="100" style="width: 100%"
@sort-change="handleSortByColumn">
<el-table-column type="index" width="40" />
<!-- Code -->
<el-table-column
prop="Code"
:label="$t('trials:emailManageCfg:title:code')"
sortable="custom"
show-overflow-tooltip
min-width="100"
/>
<el-table-column
v-if="isDistinguishCriteria"
prop="TrialReadingCriterionId"
:label="$t('trials:reviewTrack:table:criterionName')"
show-overflow-tooltip
sortable="custom"
min-width="120"
>
<el-table-column prop="Code" :label="$t('trials:emailManageCfg:title:code')" sortable="custom"
show-overflow-tooltip min-width="100" />
<el-table-column v-if="isDistinguishCriteria" prop="TrialReadingCriterionId"
:label="$t('trials:reviewTrack:table:criterionName')" show-overflow-tooltip sortable="custom" min-width="120">
<template slot-scope="scope">
{{ $fd('CriterionType', scope.row.CriterionTypeEnum) }}
</template>
</el-table-column>
<!-- 邮件主题中文 -->
<el-table-column
prop="EmailTopicCN"
:label="$t('trials:emailManageCfg:table:EmailTopicCN')"
sortable="custom"
show-overflow-tooltip
min-width="160"
/>
<el-table-column prop="EmailTopicCN" :label="$t('trials:emailManageCfg:table:EmailTopicCN')" sortable="custom"
show-overflow-tooltip min-width="160" />
<!-- 邮件主题英文 -->
<el-table-column
prop="EmailTopic"
:label="$t('trials:emailManageCfg:table:EmailTopic')"
sortable="custom"
show-overflow-tooltip
min-width="160"
/>
<el-table-column prop="EmailTopic" :label="$t('trials:emailManageCfg:table:EmailTopic')" sortable="custom"
show-overflow-tooltip min-width="160" />
<!-- 业务层级 -->
<el-table-column
prop="BusinessLevelEnum"
:label="$t('dictionary:email:label:businessLevel')"
sortable="custom"
show-overflow-tooltip
min-width="150"
>
<el-table-column prop="BusinessLevelEnum" :label="$t('dictionary:email:label:businessLevel')" sortable="custom"
show-overflow-tooltip min-width="150">
<template slot-scope="scope">
{{ $fd('BusinessLevel', scope.row.BusinessLevelEnum) }}
</template>
</el-table-column>
<!-- 业务模块 -->
<el-table-column
prop="BusinessModuleEnum"
:label="$t('dictionary:email:label:businessModule')"
sortable="custom"
show-overflow-tooltip
min-width="160"
>
<el-table-column prop="BusinessModuleEnum" :label="$t('dictionary:email:label:businessModule')" sortable="custom"
show-overflow-tooltip min-width="160">
<template slot-scope="scope">
{{ $fd('BusinessModule', scope.row.BusinessModuleEnum) }}
</template>
</el-table-column>
<!-- 业务场景 -->
<el-table-column
prop="BusinessScenarioEnum"
:label="$t('trials:emailManageCfg:table:BusinessScenarioEnum')"
sortable="custom"
show-overflow-tooltip
min-width="130"
>
<el-table-column prop="BusinessScenarioEnum" :label="$t('trials:emailManageCfg:table:BusinessScenarioEnum')"
sortable="custom" show-overflow-tooltip min-width="130">
<template slot-scope="scope">
{{ $fd('Email_BusinessScenario', scope.row.BusinessScenarioEnum) }}
</template>
</el-table-column>
<!-- 收件人v-if="systemLevel !== 1" -->
<el-table-column
prop="ToUserTypeList"
:label="$t('trials:emailManageCfg:title:toUserTypeList')"
show-overflow-tooltip
min-width="100"
>
<el-table-column prop="ToUserTypeList" :label="$t('trials:emailManageCfg:title:toUserTypeList')"
show-overflow-tooltip min-width="100">
<template slot-scope="scope">
{{
scope.row.ToUserTypeList.length > 0
? scope.row.ToUserTypeList.map((v) => $fd('UserType', v)).join(
'、'
)
'、'
)
: ''
}}
</template>
</el-table-column>
<!-- 抄送人v-if="!systemLevel" -->
<el-table-column
prop="CopyUserTypeList"
:label="$t('trials:emailManageCfg:title:copyUserTypeList')"
show-overflow-tooltip
min-width="100"
>
<el-table-column prop="CopyUserTypeList" :label="$t('trials:emailManageCfg:title:copyUserTypeList')"
show-overflow-tooltip min-width="100">
<template slot-scope="scope">
{{
scope.row.CopyUserTypeList.length > 0
? scope.row.CopyUserTypeList.map((v) => $fd('UserType', v)).join(
'、'
)
'、'
)
: ''
}}
</template>
</el-table-column>
<!-- 加急状态 -->
<el-table-column
prop="EmailUrgentEnum"
:label="$t('dictionary:email:label:IsUrgent')"
sortable="custom"
show-overflow-tooltip
min-width="110"
>
<el-table-column prop="EmailUrgentEnum" :label="$t('dictionary:email:label:IsUrgent')" sortable="custom"
show-overflow-tooltip min-width="110">
<template slot-scope="scope">
{{ $fd('EmailUrgent', scope.row.EmailUrgentEnum) }}
</template>
</el-table-column>
<!-- 发送周期和时间 -->
<el-table-column
prop="EmailCron"
:label="$t('trials:emailManageCfg:table:EmailCron')"
sortable="custom"
show-overflow-tooltip
min-width="180"
/>
<el-table-column prop="EmailCron" :label="$t('trials:emailManageCfg:table:EmailCron')" sortable="custom"
show-overflow-tooltip min-width="180" />
<!-- 附件 -->
<el-table-column
prop="FileName"
:label="$t('trials:emailManageCfg:title:fileName')"
show-overflow-tooltip
min-width="100"
v-if="!systemLevel"
>
<el-table-column prop="FileName" :label="$t('trials:emailManageCfg:title:fileName')" show-overflow-tooltip
min-width="100" v-if="!systemLevel">
<template slot-scope="scope">
<el-button
v-if="scope.row.FilePath"
type="text"
@click="handlePreview(scope.row.FilePath)"
>
<el-button v-if="scope.row.FilePath" type="text" @click="handlePreview(scope.row.FilePath)">
{{ scope.row.FileName }}
</el-button>
</template>
</el-table-column>
<!-- 是否需要回执 -->
<el-table-column
prop="IsReturnRequired"
:label="$t('trials:emailManageCfg:title:isReturnRequired')"
show-overflow-tooltip
sortable="custom"
min-width="160"
v-if="!systemLevel"
>
<el-table-column prop="IsReturnRequired" :label="$t('trials:emailManageCfg:title:isReturnRequired')"
show-overflow-tooltip sortable="custom" min-width="160" v-if="!systemLevel">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsReturnRequired" type="danger">
{{ $fd('YesOrNo', scope.row.IsReturnRequired) }}
@@ -327,14 +168,8 @@
</template>
</el-table-column>
<!-- 是否自动发送 -->
<el-table-column
prop="IsAutoSend"
:label="$t('trials:emailManageCfg:title:isAutoSend')"
show-overflow-tooltip
sortable="custom"
min-width="160"
v-if="!systemLevel"
>
<el-table-column prop="IsAutoSend" :label="$t('trials:emailManageCfg:title:isAutoSend')" show-overflow-tooltip
sortable="custom" min-width="160" v-if="!systemLevel">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsAutoSend" type="danger">
{{ $fd('YesOrNo', scope.row.IsAutoSend) }}
@@ -344,14 +179,8 @@
}}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="IsEnable"
:label="$t('trials:emailManageCfg:table:IsEnable')"
show-overflow-tooltip
sortable="custom"
min-width="160"
v-if="!systemLevel"
>
<el-table-column prop="IsEnable" :label="$t('trials:emailManageCfg:table:IsEnable')" show-overflow-tooltip
sortable="custom" min-width="160" v-if="!systemLevel">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsEnable" type="danger">
{{ $fd('YesOrNo', scope.row.IsEnable) }}
@@ -362,22 +191,12 @@
</template>
</el-table-column>
<!-- 更新时间 -->
<el-table-column
prop="UpdateTime"
:label="$t('trials:emailManageCfg:title:updateTime')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column prop="UpdateTime" :label="$t('trials:emailManageCfg:title:updateTime')" show-overflow-tooltip
sortable="custom" min-width="160" />
<!-- 创建时间 -->
<el-table-column
prop="CreateTime"
:label="$t('trials:emailManageCfg:title:createTime')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column label="Action" min-width="180" fixed="right">
<el-table-column prop="CreateTime" :label="$t('trials:emailManageCfg:title:createTime')" show-overflow-tooltip
sortable="custom" min-width="160" />
<el-table-column :label="$t('common:action:action')" min-width="180" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">
{{ $t('common:button:edit') }}
@@ -386,77 +205,38 @@
<el-button type="text" @click="handleDelete(scope.row)">
{{ $t('common:button:delete') }}
</el-button>
<el-button
type="text"
@click="
preview(scope.row.EmailHtmlContentCN, scope.row.EmailHtmlContent)
"
>{{ $t('common:button:preview') }}</el-button>
<el-button type="text" @click="
preview(scope.row.EmailHtmlContentCN, scope.row.EmailHtmlContent)
">{{ $t('common:button:preview') }}</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
class="page"
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
<!-- 搜索框 -->
<!-- 修改受试者状态 -->
<el-dialog
v-if="editVisible"
:visible.sync="editVisible"
:close-on-click-modal="false"
:title="title"
width="700px"
fullscreen
custom-class="base-dialog-wrapper"
>
<EmailForm
:data="rowData"
:system-level="systemLevel"
:isDistinguishCriteria="isDistinguishCriteria"
@closeDialog="closeDialog"
@getList="getList"
@PreviewHTML="preview"
/>
<el-dialog v-if="editVisible" :visible.sync="editVisible" :close-on-click-modal="false" :title="title" width="700px"
fullscreen custom-class="base-dialog-wrapper">
<EmailForm :data="rowData" :system-level="systemLevel" :isDistinguishCriteria="isDistinguishCriteria"
@closeDialog="closeDialog" @getList="getList" @PreviewHTML="preview" />
</el-dialog>
<!-- 修改受试者状态 -->
<el-dialog
v-if="attachmentVisible"
:visible.sync="attachmentVisible"
:close-on-click-modal="false"
:title="$t('trials:emailManageCfg:title:fileName')"
width="80%"
custom-class="base-dialog-wrapper"
append-to-body
>
<AttachmentList
:business-scenario-enum="rowData.BusinessScenarioEnum"
:is-distinguish-criteria="rowData.IsDistinguishCriteria"
@getList="getList"
/>
<el-dialog v-if="attachmentVisible" :visible.sync="attachmentVisible" :close-on-click-modal="false"
:title="$t('trials:emailManageCfg:title:fileName')" width="80%" custom-class="base-dialog-wrapper" append-to-body>
<AttachmentList :business-scenario-enum="rowData.BusinessScenarioEnum"
:is-distinguish-criteria="rowData.IsDistinguishCriteria" @getList="getList" />
</el-dialog>
<!-- 预览模板 -->
<el-dialog
v-if="previewVisible"
:visible.sync="previewVisible"
:close-on-click-modal="false"
:title="$t('common:button:preview')"
fullscreen
custom-class="base-dialog-wrapper"
>
<div
style="
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :close-on-click-modal="false"
:title="$t('common:button:preview')" fullscreen custom-class="base-dialog-wrapper">
<div style="
display: flex;
align-items: flex-start;
justify-content: space-around;
flex-wrap: wrap;
"
>
">
<div style="min-width: 50%">
<!-- 邮件内容模版CN -->
<h3>{{ $t('dictionary:email:title:title1') }}</h3>
@@ -630,3 +410,8 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.box-body .search .base-search-form .el-form-item {
margin-bottom: 10px;
}
</style>
+27 -63
View File
@@ -1,85 +1,41 @@
<template>
<base-model :config="config">
<div slot="dialog-body">
<el-form
ref="fileForm"
:model="form"
label-width="100px"
size="small"
:rules="rules"
>
<el-form ref="fileForm" :model="form" label-width="100px" size="small" :rules="rules">
<div class="base-dialog-body">
<el-form-item :label="$t('dictionary:file:form:name')" prop="Name">
<el-input v-model="form.Name" />
</el-form-item>
<el-form-item
:label="$t('dictionary:file:form:nameCN')"
prop="NameCN"
>
<el-form-item :label="$t('dictionary:file:form:nameCN')" prop="NameCN">
<el-input v-model="form.NameCN" />
</el-form-item>
<el-form-item
:label="$t('dictionary:browser:form:ArchiveTypeEnum')"
prop="ArchiveTypeEnum"
>
<el-select
v-model="form.ArchiveTypeEnum"
clearable
placeholder=""
style="width: 100%"
>
<el-option
v-for="item in $d.ArchiveType"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-form-item :label="$t('dictionary:browser:form:ArchiveTypeEnum')" prop="ArchiveTypeEnum">
<el-select v-model="form.ArchiveTypeEnum" clearable placeholder="" style="width: 100%">
<el-option v-for="item in $d.ArchiveType" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item
:label="$t('dictionary:browser:form:SubIdentificationEnum')"
prop="SubIdentificationEnum"
>
<el-select
style="width: 100%"
v-model="form.SubIdentificationEnum"
clearable
placeholder=""
>
<el-option
v-for="item in $d.SubIdentification"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-form-item :label="$t('dictionary:browser:form:SubIdentificationEnum')" prop="SubIdentificationEnum">
<el-select style="width: 100%" v-model="form.SubIdentificationEnum" clearable placeholder="">
<el-option v-for="item in $d.SubIdentification" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item
:label="$t('dictionary:file:form:ShowOrder')"
prop="ShowOrder"
>
<el-input-number
v-model="form.ShowOrder"
:step="1"
step-strictly
></el-input-number>
<el-form-item :label="$t('dictionary:browser:form:SysTemplateTypeEnum')" prop="SysTemplateTypeEnum">
<el-select style="width: 100%" v-model="form.SysTemplateTypeEnum" clearable placeholder="">
<el-option v-for="item in $d.SysTemplateType" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:file:form:ShowOrder')" prop="ShowOrder">
<el-input-number v-model="form.ShowOrder" :step="1" step-strictly></el-input-number>
</el-form-item>
<el-form-item :label="$t('dictionary:file:form:IsEnable')">
<el-switch
v-model="form.IsEnable"
:active-value="true"
:inactive-value="false"
>
<el-switch v-model="form.IsEnable" :active-value="true" :inactive-value="false">
</el-switch>
</el-form-item>
<el-form-item :label="$t('dictionary:file:form:IsConfirmRecord')">
<el-switch
v-model="form.IsConfirmRecord"
:active-value="true"
:inactive-value="false"
>
<el-switch v-model="form.IsConfirmRecord" :active-value="true" :inactive-value="false">
</el-switch>
</el-form-item>
</div>
@@ -95,7 +51,7 @@
</div>
</base-model>
</template>
<script>
<script>
import baseModel from '@/components/BaseModel'
import { addOrUpdateSysFileType } from '@/api/dictionary'
export default {
@@ -126,6 +82,7 @@ export default {
NameCN: null,
SubIdentificationEnum: null,
ShowOrder: 0,
SysTemplateTypeEnum: null
},
rules: {
Name: [
@@ -149,6 +106,13 @@ export default {
trigger: ['blur', 'change'],
},
],
SysTemplateTypeEnum: [
{
required: true,
message: this.$t('common:ruleMessage:select'),
trigger: ['blur', 'change'],
},
],
ArchiveTypeEnum: [
{
required: true,
+53 -150
View File
@@ -11,85 +11,41 @@
<el-input v-model="searchData.NameCN" style="width: 100px" />
</el-form-item>
<el-form-item :label="$t('dictionary:file:search:archiveTypeEnum')">
<el-select
v-model="searchData.ArchiveTypeEnum"
clearable
placeholder=""
style="width: 100px"
>
<el-option
v-for="item in $d.ArchiveType"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-select v-model="searchData.ArchiveTypeEnum" clearable placeholder="" style="width: 100px">
<el-option v-for="item in $d.ArchiveType" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item
:label="$t('dictionary:file:search:subIdentificationEnum')"
>
<el-select
v-model="searchData.SubIdentificationEnum"
clearable
placeholder=""
style="width: 100px"
>
<el-option
v-for="item in $d.SubIdentification"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-form-item :label="$t('dictionary:file:search:subIdentificationEnum')">
<el-select v-model="searchData.SubIdentificationEnum" clearable placeholder="" style="width: 100px">
<el-option v-for="item in $d.SubIdentification" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:file:search:SysTemplateTypeEnum')">
<el-select v-model="searchData.SysTemplateTypeEnum" clearable placeholder="" style="width: 100px">
<el-option v-for="item in $d.SysTemplateType" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:file:search:isEnable')">
<el-select
v-model="searchData.IsEnable"
clearable
placeholder=""
style="width: 100px"
>
<el-option
v-for="item in $d.YesOrNo"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-select v-model="searchData.IsEnable" clearable placeholder="" style="width: 100px">
<el-option v-for="item in $d.YesOrNo" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:file:search:isConfirmRecord')">
<el-select
v-model="searchData.IsConfirmRecord"
clearable
placeholder=""
style="width: 100px"
>
<el-option
v-for="item in $d.YesOrNo"
:key="item.id"
:label="item.label"
:value="item.value"
>
<el-select v-model="searchData.IsConfirmRecord" clearable placeholder="" style="width: 100px">
<el-option v-for="item in $d.YesOrNo" :key="item.id" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
@click="handleSearch"
>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<el-button
type="primary"
icon="el-icon-refresh-left"
@click="handleReset"
>
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
@@ -101,106 +57,60 @@
</span>
</div>
<el-table
v-loading="loading"
v-adaptive="{ bottomOffset: 45 }"
:data="list"
stripe
height="100"
style="width: 100%"
@sort-change="handleSortByColumn"
>
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 45 }" :data="list" stripe height="100"
style="width: 100%" @sort-change="handleSortByColumn">
<el-table-column type="index" width="40" />
<el-table-column
prop="Name"
:label="$t('dictionary:file:table:Name')"
sortable="custom"
show-overflow-tooltip
/>
<el-table-column
prop="NameCN"
:label="$t('dictionary:file:table:NameCN')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
prop="ArchiveTypeEnum"
:label="$t('dictionary:file:table:archiveTypeEnum')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="Name" :label="$t('dictionary:file:table:Name')" sortable="custom" show-overflow-tooltip
min-width="150px" />
<el-table-column prop="NameCN" :label="$t('dictionary:file:table:NameCN')" show-overflow-tooltip
sortable="custom" min-width="150px" />
<el-table-column prop="ArchiveTypeEnum" :label="$t('dictionary:file:table:archiveTypeEnum')"
show-overflow-tooltip sortable="custom" min-width="120px">
<template slot-scope="scope">
{{ $fd('ArchiveType', scope.row.ArchiveTypeEnum) }}
</template>
</el-table-column>
<el-table-column
prop="SubIdentificationEnum"
:label="$t('dictionary:file:table:subIdentificationEnum')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="SubIdentificationEnum" :label="$t('dictionary:file:table:subIdentificationEnum')"
show-overflow-tooltip sortable="custom" min-width="120px">
<template slot-scope="scope">
{{ $fd('SubIdentification', scope.row.SubIdentificationEnum) }}
</template>
</el-table-column>
<el-table-column
prop="ShowOrder"
:label="$t('dictionary:file:table:ShowOrder')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
:label="$t('dictionary:file:table:IsEnable')"
prop="IsEnable"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="SysTemplateTypeEnum" :label="$t('dictionary:file:table:SysTemplateTypeEnum')"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
{{ $fd('SysTemplateType', scope.row.SysTemplateTypeEnum) }}
</template>
</el-table-column>
<el-table-column prop="ShowOrder" :label="$t('dictionary:file:table:ShowOrder')" show-overflow-tooltip
sortable="custom" />
<el-table-column :label="$t('dictionary:file:table:IsEnable')" prop="IsEnable" show-overflow-tooltip
sortable="custom" min-width="120px">
<template slot-scope="scope">
<el-tag :type="!scope.row.IsEnable ? 'info' : ''">
{{ $fd('YesOrNo', scope.row.IsEnable) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
:label="$t('dictionary:file:table:isConfirmRecord')"
prop="IsConfirmRecord"
show-overflow-tooltip
sortable="custom"
width="200"
>
<el-table-column :label="$t('dictionary:file:table:isConfirmRecord')" prop="IsConfirmRecord"
show-overflow-tooltip sortable="custom" min-width="150px">
<template slot-scope="scope">
<el-tag :type="!scope.row.IsEnable ? 'info' : ''">
{{ $fd('YesOrNo', scope.row.IsConfirmRecord) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="CreateTime"
:label="$t('dictionary:file:table:createTime')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
prop="UpdateTime"
:label="$t('dictionary:file:table:updateTime')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
:label="$t('dictionary:file:table:action')"
width="200"
fixed="right"
>
<el-table-column prop="CreateTime" :label="$t('dictionary:file:table:createTime')" show-overflow-tooltip
sortable="custom" min-width="180px" />
<el-table-column prop="UpdateTime" :label="$t('dictionary:file:table:updateTime')" show-overflow-tooltip
sortable="custom" min-width="180px" />
<el-table-column :label="$t('dictionary:file:table:action')" width="200" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">
{{ $t('dictionary:file:button:edit') }}
</el-button>
<el-button
type="text"
@click="handleDelete(scope.row)"
v-if="hasPermi(['dictionary:template:file:del'])"
>
<el-button type="text" @click="handleDelete(scope.row)" v-if="hasPermi(['dictionary:template:file:del'])">
{{ $t('dictionary:file:button:delete') }}
</el-button>
</template>
@@ -208,20 +118,9 @@
</el-table>
<!-- 分页组件 -->
<pagination
class="page"
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<File-form
:config="config"
:data="rowData"
v-if="config.visible"
@close="close"
@getList="getList"
/>
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
<File-form :config="config" :data="rowData" v-if="config.visible" @close="close" @getList="getList" />
</box-content>
</BoxContent>
</template>
@@ -238,10 +137,11 @@ const searchDataDefault = () => {
Name: null,
NameCN: null,
SubIdentificationEnum: null,
SysTemplateTypeEnum: null,
PageIndex: 1,
PageSize: 20,
asc: false,
sortField: 'CreateTime',
sortField: '',
}
}
export default {
@@ -344,4 +244,7 @@ export default {
}
</script>
<style lang="scss" scoped>
.box-body .search .base-search-form .el-form-item {
margin-bottom: 10px;
}
</style>
+24 -67
View File
@@ -9,71 +9,36 @@
<!-- Reset Password -->
<div class="box-wrapper" v-if="active === 0">
<el-form
ref="resetForm"
v-loading="formLoading"
:model="form"
label-width="130px"
:rules="rules"
class="demo-ruleForm"
size="small"
>
<el-form ref="resetForm" v-loading="formLoading" :model="form" label-width="130px" :rules="rules"
class="demo-ruleForm" size="small">
<!-- 邮箱 -->
<el-form-item
:label="$t('passwordReset:form:email')"
prop="EmailOrPhone"
>
<el-form-item :label="$t('passwordReset:form:email')" prop="EmailOrPhone">
<el-col :span="18">
<el-input
v-model="form.EmailOrPhone"
autocomplete="off"
@change="handleEmailChange"
/>
<el-input v-model="form.EmailOrPhone" autocomplete="off" @change="handleEmailChange" />
</el-col>
</el-form-item>
<!-- 验证码 -->
<el-form-item
:label="$t('trials:researchForm:form:verifyCode')"
required
>
<el-form-item :label="$t('trials:researchForm:form:verifyCode')" required>
<el-col :span="18">
<el-form-item prop="VerificationCode">
<el-input v-model="form.VerificationCode" autocomplete="off" />
</el-form-item>
</el-col>
<el-col :span="6" style="text-align: right">
<el-button
size="small"
type="primary"
style="width: 80%"
:disabled="sendDisabled"
@click="handleSendCode"
>{{ sendTitle }}</el-button
>
<el-button size="small" type="primary" style="width: 80%" :disabled="sendDisabled"
@click="handleSendCode">{{ sendTitle }}</el-button>
</el-col>
</el-form-item>
</el-form>
<!-- 校验 -->
<el-button
size="small"
type="primary"
style="width: 50%; margin: auto; display: block"
:disabled="form.VerificationCode === '' || form.EmailOrPhone === ''"
@click="verifyCode"
>
<el-button size="small" type="primary" style="width: 50%; margin: auto; display: block"
:disabled="form.VerificationCode === '' || form.EmailOrPhone === ''" @click="verifyCode">
{{ $t('passwordReset:button:verify') }}
</el-button>
</div>
<div class="box-wrapper" v-show="active === 1">
<el-form
ref="resetForm2"
v-loading="formLoading"
:model="form"
:label-width="$i18n.locale === 'en' ? '180px' : '100px'"
:rules="rules2"
class="demo-ruleForm"
size="small"
>
<el-form ref="resetForm2" v-loading="formLoading" :model="form"
:label-width="$i18n.locale === 'en' ? '180px' : '100px'" :rules="rules2" class="demo-ruleForm" size="small">
<!-- 用户名 -->
<el-form-item :label="$t('passwordReset:form:userName')" prop="UserId">
<el-input v-model="form.UserName" disabled />
@@ -105,27 +70,17 @@
<el-input v-model="form.UserType" disabled />
</el-form-item> -->
<!-- 新密码 -->
<el-form-item
class="my_new_pwd"
:label="$t('passwordReset:form:password')"
prop="NewPwd"
style="position: relative"
>
<el-form-item class="my_new_pwd" :label="$t('passwordReset:form:password')" prop="NewPwd"
style="position: relative">
<el-input v-model="form.NewPwd" show-password autocomplete="off" />
<span style="position: absolute; right: -30px">
<el-tooltip
:content="$t('passwordReset:form:passwordCentent')"
placement="top"
>
<el-tooltip :content="$t('passwordReset:form:passwordCentent')" placement="top">
<i class="el-icon-question" />
</el-tooltip>
</span>
</el-form-item>
<!-- 确认密码 -->
<el-form-item
:label="$t('passwordReset:form:confirmPassword')"
prop="CheckPass"
>
<el-form-item :label="$t('passwordReset:form:confirmPassword')" prop="CheckPass">
<el-input v-model="form.CheckPass" show-password autocomplete="off" />
</el-form-item>
</el-form>
@@ -135,12 +90,7 @@
{{ $t('passwordReset:button:cancel') }}
</el-button>
<!-- 提交 -->
<el-button
size="small"
type="primary"
@click="onSubmit"
style="width: 46%"
>
<el-button size="small" type="primary" @click="onSubmit" style="width: 46%">
{{ $t('passwordReset:button:submit') }}
</el-button>
</div>
@@ -388,10 +338,14 @@ export default {
}
},
onCancel() {
this.$refs['resetForm'].resetFields()
// this.$refs['resetForm'].resetFields()
Object.keys(this.form).forEach((key) => {
this.form[key] = ''
})
this.sendDisabled = false
this.sendTitle = this.$t('passwordReset:button:send')
countdown = 60
clearTimeout(timer)
this.active = 0
},
goBack() {
@@ -404,12 +358,14 @@ export default {
.reset-wrapper {
padding: 20px;
}
.reset-wrapper .el-page-header {
line-height: 50px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fff;
}
.reset-wrapper .box-wrapper {
width: 700px;
margin: 20px auto;
@@ -425,6 +381,7 @@ export default {
.is-error.my_new_pwd {
margin-bottom: 45px;
}
.flexBox {
display: flex;
justify-content: center;
+82 -159
View File
@@ -2,152 +2,76 @@
<div class="log">
<div ref="leftContainer" class="left">
<el-form :inline="true">
<el-form-item
:label="$t('system:loginLog:label:OptType')"
prop="OptType"
>
<el-select
v-model="searchData.OptType"
clearable
style="width: 120px"
>
<el-option
v-for="item of $d.UserOptType"
:key="'UserOptType' + item.label"
:value="item.value"
:label="item.label"
/>
<el-form-item :label="$t('system:loginLog:label:OptType')" prop="OptType">
<el-select v-model="searchData.OptTypeList" clearable style="width: 200px" multiple :collapse-tags="true">
<el-option v-for="item of $d.UserOptType" :key="'UserOptType' + item.label" :value="item.value"
:label="item.label" />
</el-select>
</el-form-item>
<el-form-item label="IP" prop="IP">
<el-input
v-model="searchData.IP"
size="small"
clearable
style="width: 120px"
/>
<el-input v-model="searchData.IP" size="small" clearable style="width: 120px" />
</el-form-item>
<el-form-item
:label="$t('system:loginLog:label:LoginFaildName')"
prop="LoginFaildName"
>
<el-input
v-model="searchData.LoginFaildName"
size="small"
clearable
style="width: 120px"
/>
<el-form-item :label="$t('system:loginLog:label:LoginFaildName')" prop="LoginFaildName">
<el-input v-model="searchData.LoginFaildName" size="small" clearable style="width: 120px" />
</el-form-item>
<el-form-item
:label="$t('system:loginLog:table:LoginUserName')"
prop="LoginFaildName"
>
<el-input
v-model="searchData.LoginUserName"
size="small"
clearable
style="width: 120px"
/>
<el-form-item :label="$t('system:loginLog:table:LoginUserName')" prop="LoginFaildName">
<el-input v-model="searchData.LoginUserName" size="small" clearable style="width: 120px" />
</el-form-item>
<el-form-item
:label="$t('system:loginLog:table:LoginUserType')"
prop="LoginUserTypeEnum"
>
<el-select
v-model="searchData.LoginUserTypeEnum"
clearable
style="width: 120px"
>
<el-option
v-for="item of $d.UserType"
:key="'UserType' + item.label"
:value="item.value"
:label="item.label"
/>
<!-- <el-form-item :label="$t('system:loginLog:table:LoginUserType')" prop="LoginUserTypeEnum">
<el-select v-model="searchData.LoginUserTypeEnum" clearable style="width: 120px">
<el-option v-for="item of $d.UserType" :key="'UserType' + item.label" :value="item.value"
:label="item.label" />
</el-select>
</el-form-item> -->
<el-form-item :label="$t('system:loginLog:label:IsLoginUncommonly')" prop="IsLoginUncommonly ">
<el-select v-model="searchData.IsLoginUncommonly" clearable style="width: 120px">
<el-option v-for="item of $d.YesOrNo" :key="item.id" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<el-form-item :label="$t('system:loginLog:label:CreateTime')">
<el-date-picker
v-model="datetimerange"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
@change="handleDatetimeChange"
style="width: 380px"
/>
<el-date-picker v-model="datetimerange" type="datetimerange" value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']" @change="handleDatetimeChange" style="width: 380px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getList">
{{ $t('common:button:search') }}
</el-button>
<el-button
type="primary"
icon="el-icon-refresh-left"
@click="handleReset"
>
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
v-adaptive="{ bottomOffset: 45 }"
height="100"
:data="list"
class="table"
@sort-change="handleSortByColumn"
>
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 45 }" height="100" :data="list" class="table"
@sort-change="handleSortByColumn">
<el-table-column type="index" width="50">
<template slot-scope="scope">
<span
style="
<span style="
display: flex;
align-items: center;
justify-content: flex-end;
"
>
<el-tooltip
class="item"
effect="dark"
:content="$t('system:loginLog:label:IsLoginUncommonly')"
placement="top"
style="margin-right: 3px"
v-if="scope.row.IsLoginUncommonly"
>
<i
v-if="scope.row.IsLoginUncommonly"
class="el-icon-warning icon-i"
></i>
">
<el-tooltip class="item" effect="dark" :content="$t('system:loginLog:label:IsLoginUncommonly')"
placement="top" style="margin-right: 3px" v-if="scope.row.IsIgnoreUncommonly">
<i v-if="scope.row.IsLoginUncommonly" class="el-icon-warning icon-i icon-success"></i>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="$t('system:loginLog:label:IsLoginUncommonly')"
placement="top" style="margin-right: 3px" v-else-if="scope.row.IsLoginUncommonly">
<i v-if="scope.row.IsLoginUncommonly" class="el-icon-warning icon-i"
@click.stop="setIsIgnoreUncommonly(scope.row)"></i>
</el-tooltip>
<span>{{ scope.$index + 1 }}</span>
</span>
</template>
</el-table-column>
<el-table-column
:label="$t('system:loginLog:table:OptType')"
prop="OptType"
min-width="150"
show-overflow-tooltip
sortable="custom"
>
<el-table-column :label="$t('system:loginLog:table:OptType')" prop="OptType" min-width="150"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
{{ $fd('UserOptType', scope.row.OptType) }}
</template>
</el-table-column>
<el-table-column
label="IP"
prop="IP"
min-width="150"
sortable="custom"
show-overflow-tooltip
/>
<el-table-column
:label="$t('trials:loginLog:table:IPRegion')"
prop="IPRegion"
min-width="120"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column label="IP" prop="IP" min-width="150" sortable="custom" show-overflow-tooltip />
<el-table-column :label="$t('trials:loginLog:table:IPRegion')" prop="IPRegion" min-width="120"
show-overflow-tooltip sortable="custom" />
<!-- <el-table-column
:label="$t('system:loginLog:table:LoginFaildName')"
prop="LoginFaildName"
@@ -155,20 +79,10 @@
show-overflow-tooltip
sortable="custom"
/> -->
<el-table-column
:label="$t('system:loginLog:table:LoginUserName')"
prop="ActionUserName"
min-width="140"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column
:label="$t('system:loginLog:table:Aetionlserlype')"
prop="ActionUserType"
min-width="100"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column :label="$t('system:loginLog:table:LoginUserName')" prop="ActionUserName" min-width="140"
show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('system:loginLog:table:Aetionlserlype')" prop="ActionUserType" min-width="100"
show-overflow-tooltip sortable="custom" />
<!-- <el-table-column
:label="$t('system:loginLog:table:LoginUserType')"
prop="LoginUserTypeEnum"
@@ -180,13 +94,8 @@
{{ $fd("UserType", scope.row.LoginUserTypeEnum) }}
</template>
</el-table-column> -->
<el-table-column
:label="$t('system:loginLog:table:OptUserName')"
prop="TargetIdentityUserName"
min-width="200"
sortable="custom"
show-overflow-tooltip
/>
<el-table-column :label="$t('system:loginLog:table:OptUserName')" prop="TargetIdentityUserName" min-width="200"
sortable="custom" show-overflow-tooltip />
<!-- <el-table-column
:label="$t('system:loginLog:table:OptUserType')"
prop="OptUserTypeEnum"
@@ -198,51 +107,36 @@
{{ $fd("UserType", scope.row.OptUserTypeEnum) }}
</template>
</el-table-column> -->
<el-table-column
:label="$t('system:loginLog:table:detail')"
min-width="120"
show-overflow-tooltip
>
<el-table-column :label="$t('system:loginLog:table:detail')" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
<el-button
v-if="scope.row.JsonObj"
type="text"
@click="handleOpenDialog(scope.row)"
>
<el-button v-if="scope.row.JsonObj" type="text" @click="handleOpenDialog(scope.row)">
<span>{{ $t('system:loginLog:message:detail') }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="$t('system:loginLog:table:CreateTime')"
prop="CreateTime"
min-width="180"
sortable="custom"
show-overflow-tooltip
/>
<el-table-column :label="$t('system:loginLog:table:CreateTime')" prop="CreateTime" min-width="180"
sortable="custom" show-overflow-tooltip />
</el-table>
<div class="pagination" style="text-align: right; margin-top: 5px">
<pagination
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<pagination :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</div>
</div>
<detail :config="config" :JsonObj="JsonObj" />
</div>
</template>
<script>
import { getUserLogList } from '@/api/user'
import { getUserLogList, setIsIgnoreUncommonly } from '@/api/user'
import Pagination from '@/components/Pagination'
import detail from './detail.vue'
import moment from 'moment'
const searchDataDefault = () => {
return {
OptType: null,
OptTypeList: [],
Ip: '',
LoginFaildName: '',
IsLoginUncommonly: null,
LoginUserName: '',
LoginUserTypeEnum: null,
BeginDate: '',
@@ -276,6 +170,26 @@ export default {
this.getList()
},
methods: {
//
async setIsIgnoreUncommonly(row) {
try {
let confirm = await this.$confirm(this.$t("system:loginLog:confirmMessage:setIsIgnoreUncommonly"))
if (!confirm) return false
let data = {
IsIgnoreUncommonly: true,
Id: row.Id
}
this.loading = true
let res = await setIsIgnoreUncommonly(data)
this.loading = false
if (res.IsSuccess) {
this.getList()
}
} catch (err) {
this.loading = false
console.log(err)
}
},
handleOpenDialog(row) {
if (!row.JsonObj)
return this.$message.warning(this.$t('system:loginLog:message:noJSON'))
@@ -334,28 +248,37 @@ export default {
display: flex;
padding: 10px;
border-radius: 5px;
.left {
display: flex;
flex-direction: column;
width: 0;
flex-grow: 4;
// border-right: 1px solid #ccc;
.filter-container {
display: flex;
align-items: center;
margin: 5px;
}
.data-table {
flex: 1;
padding: 5px 0px;
}
.pagination-container {
text-align: right;
}
}
.icon-i {
color: #f56c6c;
cursor: pointer;
}
.icon-success {
color: #67C23A
}
}
</style>
+181 -559
View File
@@ -9,64 +9,28 @@
</el-form-item>
<!-- 研究方案号 -->
<el-form-item :label="$t('trials:trials-list:table:researchNumber')">
<el-input
v-model="searchData.ResearchProgramNo"
style="width: 100px"
clearable
/>
<el-input v-model="searchData.ResearchProgramNo" style="width: 100px" clearable />
</el-form-item>
<!-- 试验名称 -->
<el-form-item :label="$t('trials:trials-list:table:experimentName')">
<el-input
v-model="searchData.ExperimentName"
style="width: 100px"
clearable
/>
<el-input v-model="searchData.ExperimentName" style="width: 100px" clearable />
</el-form-item>
<!-- Sponsor -->
<el-form-item :label="$t('trials:trials-list:table:sponsor')">
<el-select
v-model="searchData.SponsorId"
style="width: 150px"
clearable
>
<el-option
v-for="item in sponsorList"
:key="item.Id"
:label="item.SponsorName"
:value="item.Id"
/>
<el-select v-model="searchData.SponsorId" style="width: 150px" clearable>
<el-option v-for="item in sponsorList" :key="item.Id" :label="item.SponsorName" :value="item.Id" />
</el-select>
</el-form-item>
<!-- 阅片标准 -->
<el-form-item
v-if="hasPermi(['role:ir'])"
:label="$t('trials:trials-list:table:IR_ReadingCriterionList')"
>
<el-select
v-model="searchData.CriterionType"
style="width: 150px"
clearable
>
<el-option
v-for="item in $d.CriterionType"
:key="item.id"
:label="item.label"
:value="item.value"
/>
<el-form-item v-if="hasPermi(['role:ir'])" :label="$t('trials:trials-list:table:IR_ReadingCriterionList')">
<el-select v-model="searchData.CriterionType" style="width: 150px" clearable>
<el-option v-for="item in $d.CriterionType" :key="item.id" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<!-- 联系人 -->
<el-form-item
v-if="hasPermi(['role:ir'])"
:label="$t('trials:trials-list:table:IR_PMEmailList')"
>
<el-input
v-model="searchData.PM_EMail"
style="width: 100px"
clearable
/>
<el-form-item v-if="hasPermi(['role:ir'])" :label="$t('trials:trials-list:table:IR_PMEmailList')">
<el-input v-model="searchData.PM_EMail" style="width: 100px" clearable />
</el-form-item>
<el-form-item>
<!-- <el-button type="text" @click="isShow = !isShow">More</el-button> -->
@@ -75,33 +39,19 @@
{{ $t('common:button:search') }}
</el-button>
<!-- Reset -->
<el-button
type="primary"
icon="el-icon-refresh-left"
@click="handleReset"
>
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
<!-- Export -->
<el-button
v-hasPermi="['trials:trials-list:export']"
type="primary"
icon="el-icon-download"
:loading="exportLoading"
@click="handleExportTrial"
>
<el-button v-hasPermi="['trials:trials-list:export']" type="primary" icon="el-icon-download"
:loading="exportLoading" @click="handleExportTrial">
{{ $t('common:button:export') }}
</el-button>
</el-form-item>
</el-form>
<span style="margin-left: auto">
<!-- New -->
<el-button
v-hasPermi="['trials:trials-list:new']"
icon="el-icon-plus"
type="primary"
@click="handleNew"
>
<el-button v-hasPermi="['trials:trials-list:new']" icon="el-icon-plus" type="primary" @click="handleNew">
{{ $t('common:button:new') }}
</el-button>
</span>
@@ -112,190 +62,88 @@
<div style="padding: 10px">
<el-form label-width="140px">
<el-form-item label="Trial ID">
<el-input
v-model="searchData.Code"
placeholder="Trial ID"
style="width: 100%"
clearable
/>
<el-input v-model="searchData.Code" placeholder="Trial ID" style="width: 100%" clearable />
</el-form-item>
<el-form-item label="Indication">
<el-input
v-model="searchData.Indication"
placeholder="Indication"
style="width: 100%"
clearable
/>
<el-input v-model="searchData.Indication" placeholder="Indication" style="width: 100%" clearable />
</el-form-item>
<el-form-item label="Assessment Criteria">
<el-select
v-model="searchData.CriterionIds"
placeholder="Assessment Criteria"
style="width: 100%"
clearable
multiple
>
<el-select v-model="searchData.CriterionIds" placeholder="Assessment Criteria" style="width: 100%" clearable
multiple>
<!-- <el-option
v-for="(key,value) of dictionaryList.ReadingStandard"
:key="key"
:label="key"
:value="value"
/> -->
<el-option
v-for="item of dict.type.ReadingStandard"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-option v-for="item of dict.type.ReadingStandard" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="Sponsor">
<el-select
v-model="searchData.SponsorId"
placeholder="Sponsor"
style="width: 100%"
clearable
>
<el-option
v-for="item in sponsorList"
:key="item.Id"
:label="item.SponsorName"
:value="item.Id"
/>
<el-select v-model="searchData.SponsorId" placeholder="Sponsor" style="width: 100%" clearable>
<el-option v-for="item in sponsorList" :key="item.Id" :label="item.SponsorName" :value="item.Id" />
</el-select>
</el-form-item>
<el-form-item label="CRO">
<el-select
v-model="searchData.CROId"
placeholder="CRO"
style="width: 100%"
clearable
>
<el-option
v-for="item of croList"
:key="item.Id"
:label="item.CROName"
:value="item.Id"
/>
<el-select v-model="searchData.CROId" placeholder="CRO" style="width: 100%" clearable>
<el-option v-for="item of croList" :key="item.Id" :label="item.CROName" :value="item.Id" />
</el-select>
</el-form-item>
<el-form-item label="Phase">
<el-select
v-model="searchData.Phase"
placeholder="Phase"
style="width: 100%"
clearable
>
<el-option
v-for="item in phaseOptions"
:key="item.value"
:label="item.value"
:value="item.value"
/>
<el-select v-model="searchData.Phase" placeholder="Phase" style="width: 100%" clearable>
<el-option v-for="item in phaseOptions" :key="item.value" :label="item.value" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="ReviewType">
<el-select
v-model="searchData.ReviewTypeIds"
placeholder="ReviewType"
style="width: 100%"
clearable
multiple
>
<el-select v-model="searchData.ReviewTypeIds" placeholder="ReviewType" style="width: 100%" clearable
multiple>
<!-- <el-option
v-for="(key,value) of dictionaryList.ReviewType"
:key="key"
:label="key"
:value="value"
/> -->
<el-option
v-for="item of dict.type.ReviewType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-option v-for="item of dict.type.ReviewType" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="Modality">
<el-select
v-model="searchData.ModalityIds"
multiple
placeholder="Modality"
style="width: 100%"
clearable
>
<el-select v-model="searchData.ModalityIds" multiple placeholder="Modality" style="width: 100%" clearable>
<!-- <el-option
v-for="(key,value) of dictionaryList.ReadingType"
:key="key"
:label="key"
:value="value"
/> -->
<el-option
v-for="item of dict.type.ReadingType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-option v-for="item of dict.type.ReadingType" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="Expedited: ">
<el-select
v-model="searchData.Expedited"
value-key="value"
clearable
style="width: 100%"
>
<el-option
v-for="item in expeditedOption"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="searchData.Expedited" value-key="value" clearable style="width: 100%">
<el-option v-for="item in expeditedOption" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="BeginDate: ">
<el-date-picker
v-model="searchData.BeginDate"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
type="date"
:picker-options="beginPickerOption"
:clearable="false"
style="width: 100%"
/>
<el-date-picker v-model="searchData.BeginDate" value-format="yyyy-MM-dd" format="yyyy-MM-dd" type="date"
:picker-options="beginPickerOption" :clearable="false" style="width: 100%" />
</el-form-item>
<el-form-item label="EndDate: ">
<el-date-picker
v-model="searchData.EndDate"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
type="date"
:picker-options="endpickerOption"
:clearable="false"
style="width: 100%"
/>
<el-date-picker v-model="searchData.EndDate" value-format="yyyy-MM-dd" format="yyyy-MM-dd" type="date"
:picker-options="endpickerOption" :clearable="false" style="width: 100%" />
</el-form-item>
<el-form-item label="Attended Reviewer Type:">
<el-select
v-model="searchData.AttendedReviewerType"
placeholder="Attended Reviewer Type"
style="width: 100%"
clearable
>
<el-option
v-for="item of $d.AttendedReviewerType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-select v-model="searchData.AttendedReviewerType" placeholder="Attended Reviewer Type"
style="width: 100%" clearable>
<el-option v-for="item of $d.AttendedReviewerType" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSelectSearch"
>Search</el-button
>
<el-button type="primary" @click="handleSelectSearch">Search</el-button>
<el-button type="primary" @click="handleReset">Reset</el-button>
<el-button type="primary" @click="isShow = false">Back</el-button>
</el-form-item>
@@ -305,219 +153,89 @@
<!-- 项目列表 -->
<template slot="main-container">
<el-table
v-adaptive="{ bottomOffset: 60 }"
v-loading="listLoading"
:data="list"
stripe
height="100"
@selection-change="handleSelectChange"
@sort-change="handleSortChange"
@row-click="rowClick"
>
<el-table v-adaptive="{ bottomOffset: 60 }" v-loading="listLoading" :data="list" stripe height="100"
@selection-change="handleSelectChange" @sort-change="handleSortChange" @row-click="rowClick">
<el-table-column type="selection" align="left" width="45" />
<el-table-column type="index" width="40" align="left" />
<el-table-column
prop="TrialCode"
:label="$t('trials:trials-list:table:trialId')"
show-overflow-tooltip
sortable="custom"
width="120"
/>
<el-table-column
prop="ResearchProgramNo"
:label="$t('trials:trials-list:table:researchNumber')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column prop="TrialCode" :label="$t('trials:trials-list:table:trialId')" show-overflow-tooltip
sortable="custom" width="120" />
<el-table-column prop="ResearchProgramNo" :label="$t('trials:trials-list:table:researchNumber')"
show-overflow-tooltip sortable="custom" min-width="160" />
<el-table-column
prop="ExperimentName"
:label="$t('trials:trials-list:table:experimentName')"
show-overflow-tooltip
sortable="custom"
min-width="240"
/>
<el-table-column
prop="Sponsor"
:label="$t('trials:trials-list:table:sponsor')"
show-overflow-tooltip
sortable="custom"
min-width="120"
/>
<el-table-column
prop="TrialStatusStr"
:label="$t('trials:trials-list:table:status')"
show-overflow-tooltip
sortable="custom"
min-width="120"
>
<el-table-column prop="ExperimentName" :label="$t('trials:trials-list:table:experimentName')"
show-overflow-tooltip sortable="custom" min-width="240" />
<el-table-column prop="Sponsor" :label="$t('trials:trials-list:table:sponsor')" show-overflow-tooltip
sortable="custom" min-width="120" />
<el-table-column prop="TrialStatusStr" :label="$t('trials:trials-list:table:status')" show-overflow-tooltip
sortable="custom" min-width="120">
<template slot-scope="scope">
<el-tag
v-if="scope.row.TrialStatusStr === 'Initializing'"
type="info"
>{{ $fd('TrialStatusEnum', scope.row.TrialStatusStr) }}</el-tag
>
<el-tag
v-if="scope.row.TrialStatusStr === 'Ongoing'"
type="primary"
>{{ $fd('TrialStatusEnum', scope.row.TrialStatusStr) }}</el-tag
>
<el-tag
v-if="scope.row.TrialStatusStr === 'Completed'"
type="warning"
>{{ $fd('TrialStatusEnum', scope.row.TrialStatusStr) }}</el-tag
>
<el-tag
v-if="scope.row.TrialStatusStr === 'Stopped'"
type="danger"
>{{ $fd('TrialStatusEnum', scope.row.TrialStatusStr) }}</el-tag
>
<el-tag v-if="scope.row.TrialStatusStr === 'Initializing'" type="info">{{ $fd('TrialStatusEnum',
scope.row.TrialStatusStr) }}</el-tag>
<el-tag v-if="scope.row.TrialStatusStr === 'Ongoing'" type="primary">{{ $fd('TrialStatusEnum',
scope.row.TrialStatusStr) }}</el-tag>
<el-tag v-if="scope.row.TrialStatusStr === 'Completed'" type="warning">{{ $fd('TrialStatusEnum',
scope.row.TrialStatusStr) }}</el-tag>
<el-tag v-if="scope.row.TrialStatusStr === 'Stopped'" type="danger">{{ $fd('TrialStatusEnum',
scope.row.TrialStatusStr) }}</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="hasPermi(['role:pm', 'role:apm'])"
prop="ExpetiedTaskCount"
:label="$t('trials:trials-list:table:ExpetiedTaskCount')"
show-overflow-tooltip
sortable="custom"
min-width="120"
/>
<el-table-column
v-if="hasPermi(['role:pm', 'role:apm'])"
prop="ReReadingApprovalCount"
:label="$t('trials:trials-list:table:ReReadingApprovalCount')"
show-overflow-tooltip
sortable="custom"
min-width="180"
/>
<el-table-column
v-if="hasPermi(['role:pm', 'role:apm'])"
prop="PendingReconciliationCount"
:label="$t('trials:trials-list:table:PendingReconciliationCount')"
show-overflow-tooltip
sortable="custom"
min-width="140"
/>
<el-table-column
v-if="hasPermi(['role:pm', 'role:apm'])"
prop="PendingResponseCount"
:label="$t('trials:trials-list:table:PendingResponseCount')"
show-overflow-tooltip
sortable="custom"
min-width="140"
/>
<el-table-column
v-if="hasPermi(['role:crc', 'role:cra'])"
prop="CRC_UrgentCount"
:label="$t('trials:trials-list:table:CRC_UrgentCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:crc', 'role:cra'])"
prop="CRC_QCQuestionCount"
:label="$t('trials:trials-list:table:CRC_QCQuestionCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:crc', 'role:cra'])"
prop="CRC_CheckQuestionCount"
:label="$t('trials:trials-list:table:CRC_CheckQuestionCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:ir'])"
prop="IR_ReadingCriterionList"
:label="$t('trials:trials-list:table:IR_ReadingCriterionList')"
show-overflow-tooltip
min-width="160"
>
<el-table-column v-if="hasPermi(['role:pm', 'role:apm'])" prop="ExpetiedTaskCount"
:label="$t('trials:trials-list:table:ExpetiedTaskCount')" show-overflow-tooltip sortable="custom"
min-width="120" />
<el-table-column v-if="hasPermi(['role:pm', 'role:apm'])" prop="ReReadingApprovalCount"
:label="$t('trials:trials-list:table:ReReadingApprovalCount')" show-overflow-tooltip sortable="custom"
min-width="180" />
<el-table-column v-if="hasPermi(['role:pm', 'role:apm'])" prop="PendingReconciliationCount"
:label="$t('trials:trials-list:table:PendingReconciliationCount')" show-overflow-tooltip sortable="custom"
min-width="140" />
<el-table-column v-if="hasPermi(['role:pm', 'role:apm'])" prop="PendingResponseCount"
:label="$t('trials:trials-list:table:PendingResponseCount')" show-overflow-tooltip sortable="custom"
min-width="140" />
<el-table-column v-if="hasPermi(['role:crc', 'role:cra'])" prop="CRC_UrgentCount"
:label="$t('trials:trials-list:table:CRC_UrgentCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:crc', 'role:cra'])" prop="CRC_QCQuestionCount"
:label="$t('trials:trials-list:table:CRC_QCQuestionCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:crc', 'role:cra'])" prop="CRC_CheckQuestionCount"
:label="$t('trials:trials-list:table:CRC_CheckQuestionCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:ir'])" prop="IR_ReadingCriterionList"
:label="$t('trials:trials-list:table:IR_ReadingCriterionList')" show-overflow-tooltip min-width="160">
<template slot-scope="scope">
{{ scope.row.IR_ReadingCriterionList.join(', ') }}
</template>
</el-table-column>
<el-table-column
v-if="hasPermi(['role:ir'])"
prop="IR_UrgentCount"
:label="$t('trials:trials-list:table:IR_UrgentCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:ir'])"
prop="IR_UnReadCount"
:label="$t('trials:trials-list:table:IR_UnReadCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:ir'])"
prop="IR_TotalReadCount"
:label="$t('trials:trials-list:table:IR_TotalReadCount')"
show-overflow-tooltip
sortable="custom"
min-width="170"
/>
<el-table-column
v-if="hasPermi(['role:ir'])"
prop="IR_PMEmailList"
:label="$t('trials:trials-list:table:IR_PMEmailList')"
show-overflow-tooltip
min-width="170"
>
<el-table-column v-if="hasPermi(['role:ir'])" prop="IR_UrgentCount"
:label="$t('trials:trials-list:table:IR_UrgentCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:ir'])" prop="IR_UnReadCount"
:label="$t('trials:trials-list:table:IR_UnReadCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:ir'])" prop="IR_TotalReadCount"
:label="$t('trials:trials-list:table:IR_TotalReadCount')" show-overflow-tooltip sortable="custom"
min-width="170" />
<el-table-column v-if="hasPermi(['role:ir'])" prop="IR_PMEmailList"
:label="$t('trials:trials-list:table:IR_PMEmailList')" show-overflow-tooltip min-width="170">
<template slot-scope="scope">
{{ scope.row.IR_PMEmailList.join(', ') }}
</template>
</el-table-column>
<el-table-column
v-if="hasPermi(['role:iqc'])"
prop="IQC_UrgentCount"
:label="$t('trials:trials-list:table:IQC_UrgentCount')"
show-overflow-tooltip
sortable="custom"
min-width="140"
/>
<el-table-column
v-if="hasPermi(['role:iqc'])"
prop="IQC_ToBeClaimedCount"
:label="$t('trials:trials-list:table:IQC_ToBeClaimedCount')"
show-overflow-tooltip
sortable="custom"
min-width="140"
/>
<el-table-column
v-if="hasPermi(['role:iqc'])"
prop="IQC_AuditToBeDealedCount"
:label="$t('trials:trials-list:table:IQC_AuditToBeDealedCount')"
show-overflow-tooltip
sortable="custom"
min-width="140"
/>
<el-table-column
v-if="hasPermi(['role:iqc'])"
prop="IQC_QuestionToBeDealedCount"
:label="$t('trials:trials-list:table:IQC_QuestionToBeDealedCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:mim'])"
prop="MIM_UrgentCount"
:label="$t('trials:trials-list:table:MIM_UrgentCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column v-if="hasPermi(['role:iqc'])" prop="IQC_UrgentCount"
:label="$t('trials:trials-list:table:IQC_UrgentCount')" show-overflow-tooltip sortable="custom"
min-width="140" />
<el-table-column v-if="hasPermi(['role:iqc'])" prop="IQC_ToBeClaimedCount"
:label="$t('trials:trials-list:table:IQC_ToBeClaimedCount')" show-overflow-tooltip sortable="custom"
min-width="140" />
<el-table-column v-if="hasPermi(['role:iqc'])" prop="IQC_AuditToBeDealedCount"
:label="$t('trials:trials-list:table:IQC_AuditToBeDealedCount')" show-overflow-tooltip sortable="custom"
min-width="140" />
<el-table-column v-if="hasPermi(['role:iqc'])" prop="IQC_QuestionToBeDealedCount"
:label="$t('trials:trials-list:table:IQC_QuestionToBeDealedCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:mim'])" prop="MIM_UrgentCount"
:label="$t('trials:trials-list:table:MIM_UrgentCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<!-- <el-table-column
v-if="hasPermi(['role:mim'])"
prop="MIM_UrgentCount"
@@ -526,127 +244,63 @@
sortable="custom"
min-width="160"
/> -->
<el-table-column
v-if="hasPermi(['role:mim'])"
prop="MIM_PendingReviewCount"
:label="$t('trials:trials-list:table:MIM_PendingReviewCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:mim'])"
prop="MIM_PendingResponseCount"
:label="$t('trials:trials-list:table:MIM_PendingResponseCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:spm', 'role:cpm'])"
prop="SPM_ReReadingApprovalCount"
:label="$t('trials:trials-list:table:SPM_ReReadingApprovalCount')"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
v-if="hasPermi(['role:spm', 'role:cpm'])"
prop="SPM_ReviewerSelectApprovalCount"
:label="
$t('trials:trials-list:table:SPM_ReviewerSelectApprovalCount')
"
show-overflow-tooltip
sortable="custom"
min-width="160"
/>
<el-table-column
prop="CreateTime"
:label="$t('trials:trials-list:table:createDate')"
show-overflow-tooltip
sortable="custom"
width="180"
/>
<el-table-column
:label="$t('common:action:action')"
width="260"
align="left"
fixed="right"
>
<el-table-column v-if="hasPermi(['role:mim'])" prop="MIM_PendingReviewCount"
:label="$t('trials:trials-list:table:MIM_PendingReviewCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:mim'])" prop="MIM_PendingResponseCount"
:label="$t('trials:trials-list:table:MIM_PendingResponseCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:spm', 'role:cpm'])" prop="SPM_ReReadingApprovalCount"
:label="$t('trials:trials-list:table:SPM_ReReadingApprovalCount')" show-overflow-tooltip sortable="custom"
min-width="160" />
<el-table-column v-if="hasPermi(['role:spm', 'role:cpm'])" prop="SPM_ReviewerSelectApprovalCount" :label="$t('trials:trials-list:table:SPM_ReviewerSelectApprovalCount')
" show-overflow-tooltip sortable="custom" min-width="160" />
<el-table-column prop="CreateTime" :label="$t('trials:trials-list:table:createDate')" show-overflow-tooltip
sortable="custom" width="180" />
<el-table-column :label="$t('common:action:action')" width="260" align="left" fixed="right">
<template slot-scope="scope">
<!-- 详情 -->
<el-button
v-hasPermi="['trials:trials-list:panel']"
circle
icon="el-icon-info"
:disabled="
(scope.row.TrialStatusStr === 'Initializing' &&
!hasPermi(['role:pm'])) ||
scope.row.IsDeleted ||
((scope.row.TrialStatusStr === 'Completed' ||
scope.row.TrialStatusStr === 'Stopped') &&
!(
hasPermi(['role:qa']) ||
hasPermi(['role:ea']) ||
hasPermi(['role:pm'])
))
"
:title="$t('trials:trials-list:action:panel')"
@click.stop="handleDetail(scope.row)"
/>
<el-button v-hasPermi="['trials:trials-list:panel']" circle icon="el-icon-info" :disabled="(scope.row.TrialStatusStr === 'Initializing' &&
!hasPermi(['role:pm'])) ||
scope.row.IsDeleted ||
((scope.row.TrialStatusStr === 'Completed' ||
scope.row.TrialStatusStr === 'Stopped') &&
!(
hasPermi(['role:qa']) ||
hasPermi(['role:ea']) ||
hasPermi(['role:pm']) ||
hasPermi(['role:op']) ||
hasPermi(['role:dev']) ||
hasPermi(['role:admin'])
))
" :title="$t('trials:trials-list:action:panel')" @click.stop="handleDetail(scope.row)" />
<!-- 编辑项目基本信息 -->
<el-button
v-hasPermi="['trials:trials-list:edit']"
circle
icon="el-icon-edit-outline"
:disabled="scope.row.IsDeleted"
:title="$t('trials:trials-list:action:edit')"
@click.stop="handleEdit(scope.row)"
/>
<el-button v-hasPermi="['trials:trials-list:edit']" circle icon="el-icon-edit-outline"
:disabled="scope.row.IsDeleted" :title="$t('trials:trials-list:action:edit')"
@click.stop="handleEdit(scope.row)" />
<!-- 修改项目状态 -->
<el-button
v-hasPermi="['trials:trials-list:status']"
circle
icon="el-icon-edit"
:disabled="scope.row.IsDeleted"
:title="$t('trials:trials-list:action:status')"
@click.stop="handleStatus(scope.row)"
/>
<el-button v-hasPermi="['trials:trials-list:status']" circle icon="el-icon-edit"
:disabled="scope.row.IsDeleted" :title="$t('trials:trials-list:action:status')"
@click.stop="handleStatus(scope.row)" />
<!--项目文档-->
<el-button
v-hasPermi="[
'trials:trials-panel:trial-summary:trial-document:inspect',
'trials:trials-panel:trial-summary:trial-document:manage',
]"
circle
icon="el-icon-folder-opened"
:disabled="
(scope.row.TrialStatusStr === 'Initializing' &&
!hasPermi(['role:pm'])) ||
scope.row.IsDeleted ||
((scope.row.TrialStatusStr === 'Completed' ||
scope.row.TrialStatusStr === 'Stopped') &&
!(
hasPermi(['role:qa']) ||
hasPermi(['role:ea']) ||
hasPermi(['role:pm'])
))
"
:title="$t('trials:trials-list:action:trialDocument')"
@click.stop="toTrialDocument(scope.row)"
/>
<el-button v-hasPermi="[
'trials:trials-panel:trial-summary:trial-document:inspect',
'trials:trials-panel:trial-summary:trial-document:manage',
]" circle icon="el-icon-folder-opened" :disabled="(scope.row.TrialStatusStr === 'Initializing' &&
!hasPermi(['role:pm'])) ||
scope.row.IsDeleted ||
((scope.row.TrialStatusStr === 'Completed' ||
scope.row.TrialStatusStr === 'Stopped') &&
!(
hasPermi(['role:qa']) ||
hasPermi(['role:ea']) ||
hasPermi(['role:pm'])
))
" :title="$t('trials:trials-list:action:trialDocument')" @click.stop="toTrialDocument(scope.row)" />
<!-- 废除项目 -->
<el-button
v-hasPermi="['trials:trials-list:abolish']"
circle
icon="el-icon-delete"
:disabled="
scope.row.IsDeleted ||
scope.row.TrialStatusStr !== 'Initializing'
"
:title="$t('trials:trials-list:action:abolition')"
@click.stop="handleAbandon(scope.row)"
/>
<el-button v-hasPermi="['trials:trials-list:abolish']" circle icon="el-icon-delete" :disabled="scope.row.IsDeleted ||
scope.row.TrialStatusStr !== 'Initializing'
" :title="$t('trials:trials-list:action:abolition')" @click.stop="handleAbandon(scope.row)" />
<!-- 代办详情-->
<!-- <el-button-->
<!-- v-hasPermi="['trials:trials-list:abolish']"-->
@@ -661,61 +315,26 @@
</el-table>
<!-- 分页组件 -->
<pagination
class="page"
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
<!-- 新增/编辑项目 -->
<el-dialog
v-if="dialogVisible"
:visible.sync="dialogVisible"
:title="title"
:fullscreen="true"
append-to-body
custom-class="base-dialog-wrapper"
>
<TrialForm
:trial-id="currentId"
@getList="getList"
@closeDialog="closeDialog"
/>
<el-dialog v-if="dialogVisible" :visible.sync="dialogVisible" :title="title" :fullscreen="true" append-to-body
custom-class="base-dialog-wrapper">
<TrialForm :trial-id="currentId" @getList="getList" @closeDialog="closeDialog" />
</el-dialog>
<!-- 修改项目状态 -->
<el-dialog
v-if="statusVisible"
:visible.sync="statusVisible"
:close-on-click-modal="false"
:title="$t('trials:trials-list:dialogTitle:changeStatus')"
width="700px"
custom-class="base-dialog-wrapper"
append-to-body
>
<TrialStatusForm
:data="currentRow"
@closeDialog="closeStatusDialog"
@getList="getList"
/>
<el-dialog v-if="statusVisible" :visible.sync="statusVisible" :close-on-click-modal="false"
:title="$t('trials:trials-list:dialogTitle:changeStatus')" width="700px" custom-class="base-dialog-wrapper"
append-to-body>
<TrialStatusForm :data="currentRow" @closeDialog="closeStatusDialog" @getList="getList" />
</el-dialog>
<el-dialog
v-if="doneDialogVisible"
:visible.sync="doneDialogVisible"
:title="doneTitle"
:fullscreen="true"
append-to-body
custom-class="base-dialog-wrapper"
>
<DoneList
:trial-id="currentId"
@getList="getList"
@closeDialog="doneDialogVisible = false"
/>
<el-dialog v-if="doneDialogVisible" :visible.sync="doneDialogVisible" :title="doneTitle" :fullscreen="true"
append-to-body custom-class="base-dialog-wrapper">
<DoneList :trial-id="currentId" @getList="getList" @closeDialog="doneDialogVisible = false" />
</el-dialog>
</BaseContainer>
</template>
@@ -897,7 +516,7 @@ export default {
this.$confirm(res.ErrorMessage, {
type: 'warning',
showCancelButton: false,
callback: (action) => {},
callback: (action) => { },
})
}
})
@@ -922,7 +541,7 @@ export default {
this.currentRow = { ...row }
this.abandonTrial()
})
.catch(() => {})
.catch(() => { })
},
//
abandonTrial() {
@@ -954,7 +573,10 @@ export default {
!(
this.hasPermi(['role:qa']) ||
this.hasPermi(['role:ea']) ||
this.hasPermi(['role:pm'])
this.hasPermi(['role:pm']) ||
hasPermi(['role:op']) ||
hasPermi(['role:dev']) ||
hasPermi(['role:admin'])
)
) {
return
@@ -4,158 +4,108 @@
<div class="title" v-if="isDoc">
{{ TITLE }}
</div>
<el-row v-if="isSystem" style="width: 100%;">
<el-col :span="12">
<h3>{{ TITLE }}</h3>
</el-col>
<el-col :span="12" style="text-align:right;">
<h3>
<Pagination class="page" :total="total" :page.sync="searchData.pageIndex" :limit.sync="searchData.pageSize"
layout="total, sizes, prev, pager, next" :background="false" style="display: inline-block;"
@pagination="getList" />
<el-button icon="el-icon-refresh-left" size="small" circle :title="$t('common:button:reset')"
@click="handleReset" />
</h3>
</el-col>
</el-row>
</template>
<!-- 搜索框 -->
<template slot="search-container">
<el-form :inline="true">
<!-- 文件类型 -->
<el-form-item :label="$t('trials:signRecords:table:fileType')">
<el-select
v-model="searchData.FileTypeId"
clearable
style="width: 150px"
>
<el-option
v-for="item of typeOptions"
:key="item.FileTypeId"
:label="item.FileType"
:value="item.FileTypeId"
/>
<el-select v-model="searchData.FileTypeId" clearable style="width: 150px">
<template v-if="isSystem">
<el-option v-for="item of typeOptions" :key="item.id" :label="item.label" :value="item.id" />
</template>
<template v-else>
<el-option v-for="item of typeOptions" :key="item.FileTypeId" :label="item.FileType"
:value="item.FileTypeId" />
</template>
</el-select>
</el-form-item>
<!-- 文件名称 -->
<el-form-item :label="$t('trials:signRecords:table:fileName')">
<el-input v-model="searchData.Name" style="width: 120px" />
<el-input v-model="searchData.Name" clearable style="width: 120px" />
</el-form-item>
<!-- 签署人 -->
<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.UserId"
:label="item.RealName"
:value="item.UserId"
>
<span style="float: left">{{ item.RealName }}</span>
<span
style="
<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">
<span style="float: left">{{ item.RealName || item.FullName }}</span>
<span style="
float: right;
color: #8492a6;
font-size: 13px;
margin-left: 5px;
"
>
">
{{ item.UserName }}
</span>
</el-option>
</el-select>
</el-form-item>
<!-- 用户名 -->
<el-form-item :label="$t('trials:signRecords:table:userName')" v-if="isSystem">
<el-input v-model="searchData.userName" clearable style="width: 120px" />
</el-form-item>
<!-- 用户类型 -->
<el-form-item
:label="$t('trials:signRecords:table:userType')"
v-if="!isDoc"
>
<el-select
v-model="searchData.UserTypeId"
clearable
filterable
style="width: 120px"
>
<el-option
v-for="item of userTypeOptions"
v-show="item.UserTypeEnum !== 26 && item.UserTypeEnum !== 27"
:key="item.Id"
:label="item.UserTypeShortName"
:value="item.Id"
>
<el-form-item :label="$t('trials:signRecords:table:userType')" v-if="!isDoc">
<el-select v-model="searchData.UserTypeId" clearable filterable style="width: 120px">
<el-option v-for="item of userTypeOptions" v-show="item.UserTypeEnum !== 26 && item.UserTypeEnum !== 27"
:key="item.Id" :label="item.UserTypeShortName" :value="item.Id">
<span>{{ item.UserType }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item
:label="$t('trials:signRecords:table:isSign')"
v-if="!isDoc || !viewStatus"
>
<el-select
v-model="searchData.IsConfirmed"
clearable
style="width: 120px"
>
<el-option
v-for="i of $d.TrainingStatusEnum"
:key="'IsConfirmed' + i.label"
:value="i.value"
:label="i.label"
/>
<el-form-item :label="$t('trials:signRecords:table:isSign')" v-if="!isDoc || !viewStatus">
<el-select v-model="searchData.IsConfirmed" clearable style="width: 120px">
<el-option v-for="i of $d.TrainingStatusEnum" :key="'IsConfirmed' + i.label" :value="i.value"
:label="i.label" />
</el-select>
</el-form-item>
<el-form-item
:label="
isDoc
? $t('trials:signRecords:table:isDeletedFile')
: $t('trials:signRecords:table:isDeleted')
"
>
<el-select
v-model="searchData.IsDeleted"
clearable
style="width: 120px"
>
<el-option
v-for="i of $d.TrainingStatus"
:key="'IsDeleted' + i.label"
:value="i.value"
:label="i.label"
/>
<el-form-item :label="isDoc
? $t('trials:signRecords:table:isDeletedFile')
: $t('trials:signRecords:table:isDeleted')
">
<el-select v-model="searchData.IsDeleted" clearable style="width: 120px">
<el-option v-for="i of $d.TrainingStatus" :key="'IsDeleted' + i.label" :value="i.value" :label="i.label" />
</el-select>
</el-form-item>
<el-form-item
:label="$t('trials:signRecords:table:username')"
v-if="isDoc"
>
<el-form-item :label="$t('trials:signRecords:table:username')" v-if="isDoc">
<el-input v-model="searchData.UserName" style="width: 120px" />
</el-form-item>
<el-form-item
:label="$t('trials:signRecords:table:confirmTime')"
v-if="isDoc"
>
<el-date-picker
v-model="timeList"
@change="changeTimeList"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
type="datetimerange"
>
<el-form-item :label="$t('trials:signRecords:table:confirmTime')" v-if="isDoc || isSystem">
<el-date-picker v-model="timeList" @change="changeTimeList" value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']" type="datetimerange">
</el-date-picker>
</el-form-item>
<el-form-item :label="$t('trials:signRecords:table:uploadTime')" v-if="isSystem">
<el-date-picker v-model="timeListC" value-format="yyyy-MM-dd HH:mm:ss" format="yyyy-MM-dd HH:mm:ss"
type="datetimerange" :default-time="['00:00:00', '23:59:59']" @change="changeTimeListC" />
</el-form-item>
<el-form-item>
<!-- 查询 -->
<el-button icon="el-icon-search" type="primary" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<!-- 重置 -->
<el-button
icon="el-icon-refresh-left"
type="primary"
@click="handleReset"
>
<el-button icon="el-icon-refresh-left" type="primary" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
<!-- 导出 -->
<el-button
type="primary"
icon="el-icon-download"
:loading="exportLoading"
@click="handleExport"
>
<el-button type="primary" icon="el-icon-download" :loading="exportLoading" @click="handleExport"
v-if="!isSystem || !hasPermi(['role:ea'])">
{{ $t('common:button:export') }}
</el-button>
</el-form-item>
@@ -164,39 +114,25 @@
<template slot="main-container">
<!-- 系统文件列表 -->
<el-table
ref="AttachmentsManagement"
v-loading="loading"
v-adaptive="{ bottomOffset: 60 }"
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<el-table ref="AttachmentsManagement" v-loading="loading" v-adaptive="{ bottomOffset: 60 }" :data="list" stripe
height="100" @sort-change="handleSortByColumn">
<el-table-column type="index" width="40" />
<!-- 文件类型 -->
<el-table-column
prop="FileType"
:label="$t('trials:signRecords:table:fileType')"
show-overflow-tooltip
sortable="custom"
width="160"
/>
<el-table-column prop="FileType" :label="$t('trials:signRecords:table:fileType')" show-overflow-tooltip
sortable="custom" width="160" />
<!-- 文件名称 -->
<el-table-column
prop="Name"
:label="$t('trials:signRecords:table:fileName')"
show-overflow-tooltip
sortable="custom"
/>
<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>
<template slot-scope="scope">
<el-button type="text" @click.stop="openAttachment(scope.row, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<!-- 文件级别 -->
<el-table-column
prop="IsSystemDoc"
:label="$t('trials:signRecords:table:fileLevel')"
show-overflow-tooltip
width="160"
sortable="custom"
>
<el-table-column prop="IsSystemDoc" :label="$t('trials:signRecords:table:fileLevel')" show-overflow-tooltip
width="160" sortable="custom" v-if="!isSystem">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsSystemDoc" type="primary">{{
$fd('IsSystemDoc', scope.row.IsSystemDoc)
@@ -207,16 +143,10 @@
</template>
</el-table-column>
<!-- 是否废除 -->
<el-table-column
prop="IsDeleted"
:label="
isDoc
? $t('trials:signRecords:table:isDeletedFile')
: $t('trials:signRecords:table:isDeleted')
"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="IsDeleted" :label="isDoc
? $t('trials:signRecords:table:isDeletedFile')
: $t('trials:signRecords:table:isDeleted')
" show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsDeleted" type="danger">{{
$fd('TrainingStatus', scope.row.IsDeleted)
@@ -227,20 +157,11 @@
</template>
</el-table-column>
<!-- 上传时间 -->
<el-table-column
prop="CreateTime"
:label="$t('trials:signRecords:table:uploadTime')"
show-overflow-tooltip
width="160"
sortable="custom"
/>
<el-table-column prop="CreateTime" :label="$t('trials:signRecords:table:uploadTime')" show-overflow-tooltip
width="160" sortable="custom" />
<!-- 是否签署 -->
<el-table-column
prop="ConfirmTime"
:label="$t('trials:signRecords:table:isSign')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="ConfirmTime" :label="$t('trials:signRecords:table:isSign')" show-overflow-tooltip
sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsConfirmed" type="primary">{{
$fd('TrainingStatusEnum', scope.row.IsConfirmed)
@@ -251,86 +172,55 @@
</template>
</el-table-column>
<!-- 签署人 -->
<el-table-column
prop="RealName"
:label="$t('trials:signRecords:table:user')"
show-overflow-tooltip
sortable="custom"
v-if="!isDoc || !viewStatus"
/>
<el-table-column prop="RealName" :label="$t('trials:signRecords:table:user')" show-overflow-tooltip
sortable="custom" v-if="!isDoc || !viewStatus" />
<!-- 用户名 -->
<el-table-column
prop="UserName"
:label="$t('trials:signRecords:table:userName')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="UserName" :label="$t('trials:signRecords:table:userName')" show-overflow-tooltip
sortable="custom" />
<!-- 用户类型 -->
<el-table-column
prop="UserConfirmedUserTypeList"
:label="$t('trials:signRecords:table:userType')"
show-overflow-tooltip
>
<el-table-column prop="UserConfirmedUserTypeList" :label="$t('trials:signRecords:table:userType')"
show-overflow-tooltip>
<template slot-scope="scope">
<span>{{
Array.isArray(scope.row.UserConfirmedUserTypeList) &&
scope.row.UserConfirmedUserTypeList.length > 0
scope.row.UserConfirmedUserTypeList.length > 0
? scope.row.UserConfirmedUserTypeList.join(', ')
: ''
}}</span>
</template>
</el-table-column>
<!-- 签署时间 -->
<el-table-column
prop="ConfirmTime"
:label="$t('trials:signRecords:table:confirmTime')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="ConfirmTime" :label="$t('trials:signRecords:table:confirmTime')" show-overflow-tooltip
sortable="custom" />
<el-table-column :label="$t('common:action:action')">
<template slot-scope="scope">
<!-- 预览 -->
<el-button
circle
:title="$t('trials:signRecords:action:preview')"
icon="el-icon-view"
@click="handlePreview(scope.row)"
/>
<el-button circle :title="$t('trials:signRecords:action:preview')" icon="el-icon-view"
@click="handlePreview(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
class="page"
:total="total"
:page.sync="searchData.PageIndex"
:limit.sync="searchData.PageSize"
@pagination="getList"
/>
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" v-if="!isSystem" />
</template>
<!-- 预览文件 -->
<el-dialog
v-if="previewVisible"
:visible.sync="previewVisible"
:title="$t('trials:signRecords:action: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"
@getList="getList"
/>
<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;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>
<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>
@@ -339,16 +229,23 @@ import {
getTrialUserSelect,
getTrialUserTypeList,
getTrialDocAndSystemDocType,
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,
StartConfirmTime: null,
EndConfirmTime: null,
EndCreateTime: null,
BeginCreateTime: null,
userName: null,
FileTypeId: '',
Name: '',
UserId: '',
@@ -361,7 +258,7 @@ const searchDataDefault = () => {
}
export default {
name: 'AttachmentsManagement',
components: { BaseContainer, Pagination, PreviewFile },
components: { BaseContainer, Pagination, PreviewFile, attachmentPreview, attachmentList },
props: {
isDoc: {
type: Boolean,
@@ -375,6 +272,10 @@ export default {
type: Boolean,
default: false,
},
isSystem: {
type: Boolean,
default: false,
}
},
data() {
return {
@@ -393,6 +294,14 @@ export default {
trialId: this.$route.query.trialId,
exportLoading: false,
timeList: [],
timeListC: [],
SystemDocumentId: null,
perview_visible: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -401,6 +310,16 @@ export default {
this.getUserType()
},
methods: {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
changeTimeList() {
if (this.timeList) {
this.searchData.StartConfirmTime = this.timeList[0]
@@ -410,8 +329,26 @@ export default {
this.searchData.EndConfirmTime = null
}
},
changeTimeListC() {
if (this.timeListC) {
this.searchData.BeginCreateTime = this.timeListC[0]
this.searchData.EndCreateTime = this.timeListC[1]
} else {
this.searchData.BeginCreateTime = null
this.searchData.EndCreateTime = null
}
},
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
@@ -432,29 +369,52 @@ export default {
this.searchData.UserTypeId = IR.Id
}
}
this.searchData.TrialId = this.trialId
getDocumentConfirmList(this.searchData)
.then(async (res) => {
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
console.log(this.total)
})
.catch(() => {
this.loading = false
})
if (this.isSystem) {
getSysDocumentConfirmList(this.searchData)
.then(async (res) => {
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
})
.catch(() => {
this.loading = false
})
} else {
this.searchData.TrialId = this.trialId
getDocumentConfirmList(this.searchData)
.then(async (res) => {
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
})
.catch(() => {
this.loading = false
})
}
},
//
getTypeOptions() {
getTrialDocAndSystemDocType(this.trialId).then((res) => {
this.typeOptions = res.Result
})
if (this.isSystem) {
this.typeOptions = this.$d.Sys_Document
} else {
getTrialDocAndSystemDocType(this.trialId).then((res) => {
this.typeOptions = res.Result
})
}
},
//
getUserSelect() {
getTrialUserSelect(this.trialId).then((res) => {
this.userOptions = res.Result
})
if (this.isSystem) {
getSysDocSignUserList().then((res) => {
this.userOptions = res.Result
})
} else {
getTrialUserSelect(this.trialId).then((res) => {
this.userOptions = res.Result
})
}
},
//
getUserType() {
@@ -481,6 +441,7 @@ export default {
handleReset() {
this.searchData = searchDataDefault()
this.timeList = []
this.timeListC = []
this.getList()
this.$nextTick(() => {
this.$refs.AttachmentsManagement.clearSort()
@@ -291,21 +291,33 @@
:label="$t('trials:seletctedReviews:table:institution')"
show-overflow-tooltip
width="150"
/>
>
<template slot-scope="scope">
<span>{{ isEN?scope.row.HospitalName:scope.row.HospitalNameCN }}</span>
</template>
</el-table-column>
<!-- City -->
<el-table-column
prop="City"
:label="$t('trials:seletctedReviews:table:city')"
show-overflow-tooltip
width="150"
/>
>
<template slot-scope="scope">
<span>{{ isEN?scope.row.City:scope.row.CityCN }}</span>
</template>
</el-table-column>
<!-- Country -->
<el-table-column
prop="Country"
:label="$t('trials:seletctedReviews:table:country')"
show-overflow-tooltip
width="150"
/>
>
<template slot-scope="scope">
<span>{{ isEN?scope.row.Country:scope.row.CountryCN }}</span>
</template>
</el-table-column>
<!-- Selector -->
<el-table-column
prop="OptUserName"
@@ -5,17 +5,9 @@
<el-form :inline="true">
<!-- 文件类型 -->
<el-form-item :label="$t('trials:self-attachment:table:fileType')">
<el-select
v-model="searchData.FileTypeId"
clearable
style="width:150px;"
>
<el-option
v-for="item of typeOptions"
:key="item.FileTypeId"
:label="item.FileType"
:value="item.FileTypeId"
/>
<el-select v-model="searchData.FileTypeId" clearable style="width:150px;">
<el-option v-for="item of typeOptions" :key="item.FileTypeId" :label="item.FileType"
:value="item.FileTypeId" />
</el-select>
</el-form-item>
<!-- 文件名称 -->
@@ -26,7 +18,7 @@
<el-form-item :label="$t('trials:self-attachment:table:isSign')">
<el-select v-model="searchData.IsSign" clearable style="width:120px;">
<template v-for="item of $d.TrainingStatusEnum">
<el-option v-if="item.raw.ValueCN !== '无'" :label="item.label" :value="item.value" :key="item.id"/>
<el-option v-if="item.raw.ValueCN !== '无'" :label="item.label" :value="item.value" :key="item.id" />
</template>
</el-select>
</el-form-item>
@@ -44,104 +36,71 @@
</template>
<template slot="main-container">
<!-- 系统文件列表 -->
<el-table
ref="TrialAttachments"
v-loading="loading"
v-adaptive="{bottomOffset:60}"
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<el-table ref="TrialAttachments" v-loading="loading" v-adaptive="{ bottomOffset: 60 }" :data="list" stripe
height="100" @sort-change="handleSortByColumn">
<el-table-column type="selection" width="50" />
<el-table-column type="index" width="40" />
<!-- 文件类型 -->
<el-table-column
prop="FileType"
:label="$t('trials:self-attachment:table:fileType')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="FileType" :label="$t('trials:self-attachment:table:fileType')" show-overflow-tooltip
sortable="custom" />
<!-- 文件名称 -->
<el-table-column
prop="Name"
:label="$t('trials:self-attachment:table:fileName')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="Name" :label="$t('trials:self-attachment:table:fileName')" show-overflow-tooltip
sortable="custom" />
<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, true)">{{ scope.row.AttachmentCount
}}</el-button>
</template>
</el-table-column>
<!-- 是否废除 -->
<el-table-column
prop="IsDeleted"
:label="$t('trials:self-attachment:table:isDeleted')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="IsDeleted" :label="$t('trials:self-attachment:table:isDeleted')" show-overflow-tooltip
sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsDeleted" type="danger">{{ $fd('TrainingStatus', scope.row.IsDeleted) }}</el-tag>
<el-tag v-else type="primary">{{ $fd('TrainingStatus', scope.row.IsDeleted) }}</el-tag>
</template>
</el-table-column>
<!-- 上传时间 -->
<el-table-column
prop="CreateTime"
:label="$t('trials:self-attachment:table:uploadTime')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="CreateTime" :label="$t('trials:self-attachment:table:uploadTime')" show-overflow-tooltip
sortable="custom" />
<!-- 是否签署 -->
<el-table-column
prop="ConfirmTime"
:label="$t('trials:self-attachment:table:isSign')"
show-overflow-tooltip
sortable="custom"
>
<el-table-column prop="ConfirmTime" :label="$t('trials:self-attachment:table:isSign')" show-overflow-tooltip
sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsConfirmed" type="primary">{{ $fd('TrainingStatusEnum', scope.row.IsConfirmed) }}</el-tag>
<el-tag v-if="scope.row.IsConfirmed" type="primary">{{ $fd('TrainingStatusEnum', scope.row.IsConfirmed)
}}</el-tag>
<el-tag v-else type="danger">{{ $fd('TrainingStatusEnum', scope.row.IsConfirmed) }}</el-tag>
</template>
</el-table-column>
<!-- 签署时间 -->
<el-table-column
prop="ConfirmTime"
:label="$t('trials:self-attachment:table:signTime')"
show-overflow-tooltip
sortable="custom"
/>
<el-table-column prop="ConfirmTime" :label="$t('trials:self-attachment:table:signTime')" show-overflow-tooltip
sortable="custom" />
<el-table-column :label="$t('common:action:action')">
<template slot-scope="scope">
<!-- 预览 -->
<el-button
icon="el-icon-view"
circle
:title="$t('trials:self-attachment:action:preview')"
@click="handlePreview(scope.row)"
/>
<el-button icon="el-icon-view" circle :title="$t('trials:self-attachment:action:preview')"
@click="handlePreview(scope.row)" />
<!-- 签署 -->
<el-button
icon="el-icon-edit-outline"
circle
:disabled="!!scope.row.ConfirmTime"
:title="$t('trials:self-attachment:action:sign')"
@click="handleSign(scope.row)"
/>
<el-button icon="el-icon-edit-outline" circle :disabled="!!scope.row.ConfirmTime"
:title="$t('trials:self-attachment:action:sign')" @click="handleSign(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize" @pagination="getList" />
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
<!-- 预览文件 -->
<el-dialog
v-if="previewVisible"
:visible.sync="previewVisible"
:title="title"
:fullscreen="true"
append-to-body
custom-class="base-dialog-wrapper"
>
<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;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;">
<span style="color:red;margin-right:10px;">
<!-- 请仔细阅读文件并签名确认 -->
@@ -149,13 +108,8 @@
<span v-show="duration < currentMinMinutes * 60">{{ `(${currentMinMinutes * 60 - duration}s)` }}</span>
</span>
<!-- 签署 -->
<el-button
size="small"
type="primary"
:disabled="timer !== null || duration === 0"
:loading="btnLoading"
@click="handleConfirm"
>
<el-button size="small" type="primary" :disabled="timer !== null || duration === 0" :loading="btnLoading"
@click="handleConfirm">
{{ $t('common:button:sign') }}
<!-- {{ duration >= currentMinMinutes * 60?'确认':`确认(${currentMinMinutes * 60 - duration}s)` }} -->
</el-button>
@@ -167,19 +121,19 @@
</el-dialog>
<!-- 签名弹框 -->
<el-dialog
v-if="signVisible"
:visible.sync="signVisible"
:close-on-click-modal="false"
width="600px"
append-to-body
>
<el-dialog v-if="signVisible" :visible.sync="signVisible" :close-on-click-modal="false" width="600px"
append-to-body>
<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>
<span style="font-size:12px;margin-left:5px">{{ `(${$t('common:label:sign')}${currentUser})` }}</span>
</div>
<SignForm :is-system-doc="currentRow.IsSystemDoc" :document-id="currentRow.Id" :file-name="fileName" :trial-id="trialId" @closeDialog="closeSignDialog" />
<SignForm :is-system-doc="currentRow.IsSystemDoc" :document-id="currentRow.Id" :file-name="fileName"
:trial-id="trialId" @closeDialog="closeSignDialog" />
</el-dialog>
<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>
@@ -189,6 +143,8 @@ import Pagination from '@/components/Pagination'
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: '',
@@ -202,7 +158,7 @@ const searchDataDefault = () => {
}
export default {
name: 'TrialAttachments',
components: { BaseContainer, Pagination, PreviewFile, SignForm },
components: { BaseContainer, Pagination, PreviewFile, SignForm, attachmentPreview, attachmentList },
data() {
return {
searchData: searchDataDefault(),
@@ -223,7 +179,14 @@ export default {
fileName: '',
currentUser: zzSessionStorage.getItem('userName'),
typeOptions: [],
trialId: this.$route.query.trialId
trialId: this.$route.query.trialId,
SystemDocumentId: null,
perview_visible: null,
config: {
visible: false,
title: this.$t('dictionary:signature:attachmentList:title'),
width: '800px',
},
}
},
mounted() {
@@ -240,6 +203,16 @@ export default {
}
},
methods: {
openAttachment(row, isList = false) {
if (!row.AttachmentCount) return false
this.SystemDocumentId = row.Id
this.currentRow = { ...row }
if (!isList) {
this.perview_visible = true
} else {
this.config.visible = true
}
},
//
getList() {
this.loading = true
@@ -336,8 +309,8 @@ export default {
//
this.$confirm(this.$t('trials:self-attachment:message:closeDocument')).then(() => {
this.previewVisible = false
}).catch(()=>{})
}).catch(() => { })
} else {
this.handleSign(res.Result)
}
@@ -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=${
@@ -277,7 +277,7 @@ export default {
}
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.preview-wrapper{
display: flex;
flex-direction: row !important;
@@ -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}`
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)
@@ -18,7 +18,6 @@
</div>
</div>
<el-form-item
v-for="qs in questions"
v-show="qs.ShowQuestion!==2"
@@ -45,41 +44,41 @@
</template>
</el-input>
<!-- 测量 -->
<el-button
v-if="questionForm[isMeasurableId] && parseInt(questionForm[isMeasurableId]) === 1 && !questionForm[qs.Id] && readingTaskState!== 2"
size="mini"
type="text"
<el-button
v-if="questionForm[isMeasurableId] && parseInt(questionForm[isMeasurableId]) === 1 && !questionForm[qs.Id] && readingTaskState!== 2"
size="mini"
type="text"
@click="addAnnotation(qs)"
>
{{$t('trials:MRIPDFF:button:measure')}}
{{ $t('trials:MRIPDFF:button:measure') }}
</el-button>
<!-- 清除标记 -->
<el-button
v-if="getAnnotationStatus(qs) && readingTaskState!== 2"
size="mini"
type="text"
@click="removeAnnotation(qs)"
<el-button
v-if="getAnnotationStatus(qs) && readingTaskState!== 2"
size="mini"
type="text"
style="margin-left: 0px"
@click="removeAnnotation(qs)"
>
{{$t('trials:MRIPDFF:button:clear')}}
{{ $t('trials:MRIPDFF:button:clear') }}
</el-button>
<!-- 返回 -->
<el-button
v-if="questionForm[qs.Id]"
size="mini"
type="text"
@click="locateAnnotation(qs)"
<el-button
v-if="questionForm[qs.Id]"
size="mini"
type="text"
style="margin-left: 0px"
@click="locateAnnotation(qs)"
>
{{$t('trials:MRIPDFF:button:return')}}
{{ $t('trials:MRIPDFF:button:return') }}
</el-button>
<!-- 保存 -->
<el-button
v-if="questionForm[isMeasurableId] && parseInt(questionForm[isMeasurableId]) === 1 && questionForm[qs.Id] && readingTaskState!== 2"
size="mini"
type="text"
@click="saveAnnotation(qs)"
<el-button
v-if="questionForm[isMeasurableId] && parseInt(questionForm[isMeasurableId]) === 1 && questionForm[qs.Id] && readingTaskState!== 2"
size="mini"
type="text"
style="margin-left: 0px"
@click="saveAnnotation(qs)"
>
<!-- 未保存 -->
<el-tooltip v-if="getAnnotationSaveEnum(qs) === 0" class="item" effect="dark" :content="$t('trials:reading:button:unsaved')" placement="bottom">
@@ -279,7 +278,7 @@ export default {
},
mounted() {
this.trialId = this.$route.query.trialId
let digitPlaces = Number(localStorage.getItem('digitPlaces'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.initForm()
DicomEvent.$on('handleImageQualityAbnormal', () => {
@@ -295,26 +294,29 @@ export default {
this.isMeasurableId = this.getQuestionId(1105)
// const loading = this.$loading({ fullscreen: true })
this.questions.forEach(item => {
var val = this.answers[item.Id]
if (item.DictionaryCode) {
val = isNaN(parseInt(this.answers[item.Id])) ? this.answers[item.Id] : parseInt(this.answers[item.Id])
if (this.answers.hasOwnProperty(item.Id)) {
let val = this.answers[item.Id]
if (item.DictionaryCode) {
val = isNaN(parseInt(this.answers[item.Id])) ? this.answers[item.Id] : parseInt(this.answers[item.Id])
}
this.$set(this.questionForm, item.Id, val)
} else {
this.$set(this.questionForm, item.Id, '')
}
this.$set(this.questionForm, item.Id, val)
})
this.$set(this.questionForm, 'MeasureData', this.answers.MeasureData ? JSON.parse(this.answers.MeasureData) : '')
this.$set(this.questionForm, 'RowIndex', this.answers.RowIndex ? this.answers.RowIndex : '')
this.$set(this.questionForm, 'RowId', this.answers.RowId ? this.answers.RowId : '')
//
if (this.isCurrentTask && this.readingTaskState < 2) {
let arr = JSON.parse(this.answers.TableQuestionMarkList)
let isExitsMarks = arr.findIndex(i=>i.MeasureData) > -1
const arr = JSON.parse(this.answers.TableQuestionMarkList)
const isExitsMarks = arr.findIndex(i => i.MeasureData) > -1
if (isExitsMarks && parseInt(this.questionForm[this.isMeasurableId]) === 0) {
this.$set(this.questionForm, this.isMeasurableId, 1)
}
}
// saveTypeEnum 01访/2
let isMeasurable = this.getQuestionVal(1105)
const isMeasurable = this.getQuestionVal(1105)
const mean = this.getQuestionVal(1104)
if (this.questionForm.saveTypeEnum !== 1 && this.isCurrentTask && this.readingTaskState < 2) {
this.$set(this.questionForm, 'saveTypeEnum', parseInt(isMeasurable) === 1 && isNaN(parseFloat(mean)) ? 1 : 2)
@@ -325,11 +327,11 @@ export default {
this.markList = []
this.isExitsMarks = false
this.isDisabledMeasurableRadio = false
let seg = this.getQuestionVal(1106)
const seg = this.getQuestionVal(1106)
this.liverSeg = this.$fd('LiverSegmentation', seg)
if (this.answers.TableQuestionMarkList) {
let arr = JSON.parse(this.answers.TableQuestionMarkList)
arr.map(i=>{
const arr = JSON.parse(this.answers.TableQuestionMarkList)
arr.map(i => {
if (i.MeasureData) {
this.isExitsMarks = true
if (!isNaN(parseInt(isMeasurable)) && parseInt(isMeasurable) === 1 && this.isCurrentTask && this.readingTaskState < 2) {
@@ -337,15 +339,15 @@ export default {
}
i.MeasureData = JSON.parse(i.MeasureData)
}
this.markList.push({tableQuestionId: i.TableQuestionId, measureData: i, saveEnum: 1})
this.markList.push({ tableQuestionId: i.TableQuestionId, measureData: i, saveEnum: 1 })
})
}
let newMean = this.getMean()
const newMean = this.getMean()
if (newMean !== mean) {
let meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean ? newMean : '')
}
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean || '')
}
if (this.questionForm.saveTypeEnum === 1 && this.isCurrentTask && this.readingTaskState < 2) {
this.setQuestions()
}
@@ -357,13 +359,13 @@ export default {
// I II III IV V VI VII VIII
// L-I-01 L-I-02 L-I-03
// L-II-01 L-II-02 L-II-03
let segArr = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII']
const segArr = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII']
let lessionName = ''
let segmentId = this.getQuestionId(1106)
const segmentId = this.getQuestionId(1106)
let segmentVal = this.answers[segmentId]
segmentVal = segmentVal ? parseInt(segmentVal) : null
if (segmentVal) {
let i = questionMark === 1101 ? '01' : questionMark === 1102 ? '02' : questionMark === 1103 ? '03' : ''
const i = questionMark === 1101 ? '01' : questionMark === 1102 ? '02' : questionMark === 1103 ? '03' : ''
lessionName = `${orderMark}-${segArr[segmentVal - 1]}-${i}`
}
return lessionName
@@ -397,18 +399,18 @@ export default {
this.$set(this.questionForm, 'saveTypeEnum', 1)
const mean = this.getQuestionVal(1104)
if (qs.QuestionMark === 1101 || qs.QuestionMark === 1102 || qs.QuestionMark === 1103) {
let newMean = this.getMean()
const newMean = this.getMean()
if (newMean !== mean) {
let meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean ? newMean : '')
}
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean || '')
}
} else if (qs.QuestionMark === 1105) {
if (!v) {
let meanId = this.getQuestionId(1104)
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, 'NE')
} else {
let mean = this.getMean()
let meanId = this.getQuestionId(1104)
const mean = this.getMean()
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, mean)
}
}
@@ -418,10 +420,10 @@ export default {
let mean = null
let isMeasurable = this.getQuestionVal(1105)
isMeasurable = !isNaN(parseInt(isMeasurable)) ? parseInt(isMeasurable) : null
let l1 = this.getQuestionVal(1101)
let l2 = this.getQuestionVal(1102)
let l3 = this.getQuestionVal(1103)
if ( isMeasurable && !isNaN(parseFloat(l1)) && !isNaN(parseFloat(l2)) && !isNaN(parseFloat(l3))) {
const l1 = this.getQuestionVal(1101)
const l2 = this.getQuestionVal(1102)
const l3 = this.getQuestionVal(1103)
if (isMeasurable && !isNaN(parseFloat(l1)) && !isNaN(parseFloat(l2)) && !isNaN(parseFloat(l3))) {
const sum = l1 + l2 + l3
mean = sum / 3
return parseFloat(mean.toFixed(this.digitPlaces))
@@ -442,7 +444,7 @@ export default {
//
measureData.data.remark = this.getLesionName(this.orderMark, this.activeQuestionMark)
}
let val = measureData.data.cachedStats.mean / 10
const val = measureData.data.cachedStats.mean / 10
this.$set(this.questionForm, measureData.tableQuestionId, val.toFixed(this.digitPlaces))
data = {
Id: '',
@@ -461,17 +463,17 @@ export default {
}
store.dispatch('reading/addMeasuredData', { visitTaskId: this.visitTaskId, data: data })
let mean = this.getQuestionVal(1104)
let newMean = this.getMean()
const newMean = this.getMean()
if (newMean !== mean) {
mean = newMean
let meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean ? newMean : '')
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, newMean || '')
}
const isMeasurable = this.getQuestionVal(1105)
DicomEvent.$emit('refreshStudyListMeasureData')
let i = this.markList.findIndex(i=>i.tableQuestionId === measureData.tableQuestionId)
const i = this.markList.findIndex(i => i.tableQuestionId === measureData.tableQuestionId)
if (i === -1) {
this.markList.push({tableQuestionId: measureData.tableQuestionId, measureData: data, saveEnum: 0})
this.markList.push({ tableQuestionId: measureData.tableQuestionId, measureData: data, saveEnum: 0 })
} else {
this.markList[i].saveEnum = 0
this.markList[i].measureData = data
@@ -486,19 +488,19 @@ export default {
},
addAnnotation(qs) {
//
let i = this.markList.findIndex(i=>i.saveEnum === 0)
const i = this.markList.findIndex(i => i.saveEnum === 0)
if (i > -1 && this.markList[i].measureData && this.markList[i].measureData.MeasureData) {
this.$alert(this.$t('trials:MRIPDFF:message:message3'))
// this.$message.warning(this.$t('trials:MRIPDFF:message:message3'))
return
}
let orderMarkName = this.getLesionName(this.orderMark, qs.QuestionMark)
const orderMarkName = this.getLesionName(this.orderMark, qs.QuestionMark)
this.activeQuestionId = qs.Id
this.activeQuestionMark= qs.QuestionMark
DicomEvent.$emit('addAnnotation', {question: qs, locateInfo: { questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, visitTaskId: this.visitTaskId, lesionName: orderMarkName, lesionType: null, markTool: 'Probe', readingTaskState: this.readingTaskState, isMarked: true }})
this.activeQuestionMark = qs.QuestionMark
DicomEvent.$emit('addAnnotation', { question: qs, locateInfo: { questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, visitTaskId: this.visitTaskId, lesionName: orderMarkName, lesionType: null, markTool: 'Probe', readingTaskState: this.readingTaskState, isMarked: true }})
},
getAnnotationSaveEnum(qs) {
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
if (i > -1) {
return this.markList[i].saveEnum
} else {
@@ -506,7 +508,7 @@ export default {
}
},
getAnnotationStatus(qs) {
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
if (i > -1 && this.markList[i].measureData && this.markList[i].measureData.MeasureData) {
return true
} else {
@@ -516,14 +518,13 @@ export default {
getIsExitsMarks() {
const isMeasurable = this.getQuestionVal(1105)
if (!isNaN(parseInt(isMeasurable)) && parseInt(isMeasurable) === 1) {
return this.markList.findIndex(i=>i.measureData && i.measureData.MeasureData) > -1 ? true : false
return this.markList.findIndex(i => i.measureData && i.measureData.MeasureData) > -1
} else {
return false
}
},
async removeAnnotation(qs) {
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
DicomEvent.$emit('imageLocation', { questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, visitTaskId: this.visitTaskId, lesionName: this.markList[i].measureData.OrderMarkName, lesionType: null, markTool: 'Probe', readingTaskState: this.readingTaskState, isMarked: true })
//
const confirm = await this.$confirm(
@@ -534,27 +535,27 @@ export default {
}
)
if (confirm !== 'confirm') return
let measureData = Object.assign({}, this.markList[i].measureData)
const measureData = Object.assign({}, this.markList[i].measureData)
if (measureData.Id) {
await deleteSingleTableQuestionMark({Id: measureData.Id}, 11)
await deleteSingleTableQuestionMark({ Id: measureData.Id }, 11)
}
// measureData
await store.dispatch('reading/removeMeasuredData', { visitTaskId: this.visitTaskId, measureData: measureData, questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, orderMarkName: measureData.OrderMarkName})
await store.dispatch('reading/removeMeasuredData', { visitTaskId: this.visitTaskId, measureData: measureData, questionId: this.parentQsId, rowIndex: this.questionForm.RowIndex, orderMarkName: measureData.OrderMarkName })
DicomEvent.$emit('getMeasureData')
this.markList[i].measureData = null
this.markList[i].saveEnum = 0
//
this.$set(this.questionForm, this.markList[i].tableQuestionId, '')
let meanId = this.getQuestionId(1104)
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, '')
this.isDisabledMeasurableRadio = this.getIsExitsMarks()
this.$set(this.questionForm, 'saveTypeEnum', 1)
this.setQuestions()
},
locateAnnotation(qs) {
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
let measureData = this.markList[i].measureData
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
const measureData = this.markList[i].measureData
//
var markTool = 'Probe'
var readingTaskState = this.readingTaskState
@@ -569,18 +570,18 @@ export default {
async saveAnnotation(qs) {
const loading = this.$loading({ fullscreen: true })
try {
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
let params = {}
if (i > -1 && this.markList[i].measureData && this.markList[i].measureData.MeasureData) {
let measureData = this.markList[i].measureData.MeasureData
const measureData = this.markList[i].measureData.MeasureData
//
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) {
let pictureObj = await this.uploadScreenshots(`${new Date().getTime()}`, val)
const pictureObj = await this.uploadScreenshots(`${new Date().getTime()}`, val)
params.PicturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
}
let tableQuestionId = this.markList[i].tableQuestionId
const tableQuestionId = this.markList[i].tableQuestionId
params.Answer = this.questionForm[tableQuestionId]
params.MeasureData = JSON.stringify(this.markList[i].measureData.MeasureData)
loading.close()
@@ -588,13 +589,13 @@ export default {
})
} else {
params = {
Answer: "",
Answer: '',
VisitTaskId: this.visitTaskId,
QuestionId: this.parentQsId,
InstanceId: '',
SeriesId: '',
StudyId: '',
MarkTool:'',
MarkTool: '',
PicturePath: '',
NumberOfFrames: 0,
MeasureData: '',
@@ -607,9 +608,7 @@ export default {
loading.close()
this.saveTableQuestionInfo(params, qs)
}
} catch(e) {
} catch (e) {
console.log(e)
loading.close()
}
@@ -617,16 +616,16 @@ export default {
async saveTableQuestionInfo(params, qs) {
const loading = this.$loading({ fullscreen: true })
try {
let res = await saveTableQuestionMark(params, 11)
const res = await saveTableQuestionMark(params, 11)
if (res.IsSuccess) {
//
let i = this.markList.findIndex(i=>i.tableQuestionId === qs.Id)
const i = this.markList.findIndex(i => i.tableQuestionId === qs.Id)
this.markList[i].saveEnum = 1
//
let j = this.markList.findIndex(i=>!(i.saveEnum === 1 && i.measureData && i.measureData.MeasureData))
const j = this.markList.findIndex(i => !(i.saveEnum === 1 && i.measureData && i.measureData.MeasureData))
if (j === -1) {
let answers = []
let reg = new RegExp(/^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/)
const answers = []
const reg = new RegExp(/^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/)
for (const k in this.questionForm) {
if (reg.test(k)) {
if (answers.findIndex(i => i.tableQuestionId === k) === -1) {
@@ -634,7 +633,7 @@ export default {
}
}
}
let params = {
const params = {
questionId: this.parentQsId,
rowId: this.questionForm.RowId,
rowIndex: this.answers.RowIndex,
@@ -659,7 +658,7 @@ export default {
DicomEvent.$emit('setMeasuredToolsPassive')
loading.close()
}
} catch(e) {
} catch (e) {
console.log(e)
loading.close()
}
@@ -735,43 +734,43 @@ export default {
async handleSave() {
try {
const valid = await this.$refs.measurementForm.validate()
if (!valid) return
if (parseInt(this.questionForm[this.isMeasurableId]) === 1) {
if (!valid) return
if (parseInt(this.questionForm[this.isMeasurableId]) === 1) {
//
let i = this.markList.findIndex(i=>i.saveEnum === 0)
if (i > -1) {
const i = this.markList.findIndex(i => i.saveEnum === 0)
if (i > -1) {
//
this.$alert(this.$t('trials:MRIPDFF:message:message1'))
// this.$message.warning(this.$t('trials:MRIPDFF:message:message1'))
return
}
} else {
this.$alert(this.$t('trials:MRIPDFF:message:message1'))
// this.$message.warning(this.$t('trials:MRIPDFF:message:message1'))
return
}
} else {
//
// ''
const confirm = await this.$confirm(
this.$t('trials:MRIPDFF:message:message2'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
let l1Id = this.getQuestionId(1101)
this.$set(this.questionForm, l1Id, '')
let l2Id = this.getQuestionId(1102)
this.$set(this.questionForm, l2Id, '')
let l3Id = this.getQuestionId(1103)
this.$set(this.questionForm, l3Id, '')
let meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, 'NE')
}
const loading = this.$loading({ fullscreen: true })
const confirm = await this.$confirm(
this.$t('trials:MRIPDFF:message:message2'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
const l1Id = this.getQuestionId(1101)
this.$set(this.questionForm, l1Id, '')
const l2Id = this.getQuestionId(1102)
this.$set(this.questionForm, l2Id, '')
const l3Id = this.getQuestionId(1103)
this.$set(this.questionForm, l3Id, '')
const meanId = this.getQuestionId(1104)
this.$set(this.questionForm, meanId, 'NE')
}
try {
const loading = this.$loading({ fullscreen: true })
try {
// let isResetMarks = this.markList.findIndex(i=>i.measureData && i.measureData.MeasureData) > -1 ? true : false
if (parseInt(this.questionForm[this.isMeasurableId]) === 0 && this.isExitsMarks) {
await deleteTableQuestionMark({rowId: this.questionForm.RowId}, 11)
await deleteTableQuestionMark({ rowId: this.questionForm.RowId }, 11)
this.markList.forEach(i => {
if (i.measureData && i.measureData.MeasureData) {
i.measureData = ''
@@ -19,12 +19,21 @@
class="dicom-desc"
style="width: 150px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
>
<el-tooltip class="item" effect="dark" :content="`${study.StudyCode} ${study.Description?study.Description:''} ${study.Modalities} (${study.SeriesCount})`" placement="right">
<div>
<span>{{ study.StudyCode }} {{ study.Description }}</span>
<span> {{ study.Modalities }} ({{ study.SeriesCount }})</span>
<div style="text-overflow: ellipsis;overflow: hidden;" v-if="!study.StudyName">
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span style="margin-left: 5px;">{{ study.Modalities }} ({{ study.SeriesCount }})</span>
</div>
<div style="text-overflow: ellipsis;overflow: hidden;" v-else>
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="taskInfo && taskInfo.IsShowStudyName" :title="study.StudyName" style="margin: 0 5px">
{{study.StudyName}}
</span>
<div>{{ study.Modalities }} ({{ study.SeriesCount }})</div>
</div>
<div style="text-overflow: ellipsis;overflow: hidden;" :title="study.Description">{{ study.Description }}</div>
</div>
</el-tooltip>
</div>
</template>
@@ -205,7 +214,8 @@ export default {
srDialogVisible: false,
srInfo: {},
digitPlaces: 2,
visitTaskIdx: -1
visitTaskIdx: -1,
taskInfo: null
}
},
@@ -243,6 +253,7 @@ export default {
this.subjectCode = localStorage.getItem('subjectCode')
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
DicomEvent.$on('refreshStudyListMeasureData', () => {
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
this.measureData = this.visitTaskList[idx].MeasureData
@@ -76,7 +76,7 @@
<el-input
v-if="question.Type==='input'"
v-model="questionForm[question.Id]"
:disabled="question.TableQuestionType === 2"
:disabled="question.TableQuestionType === 2 || readingTaskState === 2"
/>
<!-- 多行文本输入框 -->
<el-input
@@ -84,13 +84,14 @@
v-model="questionForm[question.Id]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
:disabled="readingTaskState === 2"
/>
<!-- 下拉框 -->
<el-select
v-if="question.Type==='select'"
v-model="questionForm[question.Id]"
clearable
:disabled="(question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode"
:disabled="(question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode || readingTaskState === 2"
@change="((val)=>{formItemChange(val, question)})"
>
<template v-if="question.TableQuestionType === 1">
@@ -132,6 +133,7 @@
v-if="question.Type==='radio'"
v-model="questionForm[question.Id]"
@change="((val)=>{formItemChange(val, question)})"
:disabled="readingTaskState === 2"
>
<el-radio
v-for="val in question.TypeValue.split('|')"
@@ -145,6 +147,7 @@
<el-checkbox-group
v-if="question.Type==='checkbox'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState === 2"
>
<el-checkbox
v-for="val in question.TypeValue.split('|')"
@@ -158,12 +161,12 @@
<el-input
v-if="question.Type === 'class' && question.ClassifyShowType === 1"
v-model="questionForm[question.Id]"
:disabled="!question.ClassifyEditType"
:disabled="!question.ClassifyEditType || readingTaskState === 2"
/>
<el-select
v-if="question.Type === 'class' && question.ClassifyShowType === 2"
v-model="questionForm[question.Id]"
:disabled="!question.ClassifyEditType"
:disabled="!question.ClassifyEditType || readingTaskState === 2"
@change="(val) => { formItemChange(val, question) }"
>
<el-option
@@ -176,7 +179,7 @@
<el-radio-group
v-if="question.Type === 'class' && question.ClassifyShowType === 3"
v-model="questionForm[question.Id]"
:disabled="!question.ClassifyEditType"
:disabled="!question.ClassifyEditType || readingTaskState === 2"
@change="(val) => { formItemChange(val, question) }"
>
<el-radio
@@ -190,7 +193,7 @@
<el-input
v-if="question.Type === 'class' && question.ClassifyShowType === 4"
type="number"
:disabled="!question.ClassifyEditType"
:disabled="!question.ClassifyEditType || readingTaskState === 2"
v-model="questionForm[question.Id]"
@change="(val) => { formItemNumberChange(val, question) }"
/>
@@ -215,6 +218,7 @@
v-model="questionForm[question.Id]"
clearable
@change="(val) => { formItemNumberChange(val, question) }"
:disabled="readingTaskState === 2"
>
<el-option
v-for="val in question.TypeValue.split('|')"
@@ -229,6 +233,7 @@
@change="(val) => { formItemNumberChange(val, question) }"
@blur="handleBlur(questionForm[question.Id], questionForm, question.Id)"
v-model="questionForm[question.Id]"
:disabled="readingTaskState === 2"
>
<!-- <template slot="append">1</template> -->
<template slot="append" v-if="question.Unit !== 0">{{question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit}}</template>
@@ -238,7 +243,7 @@
type="number"
v-if="question.Type === 'number' && !question.TypeValue && question.DataSource === 1"
@blur="handleBlur(questionForm[question.Id], questionForm, question.Id)"
:disabled="question.DataSource === 1"
:disabled="question.DataSource === 1 || readingTaskState === 2"
v-model="questionForm[question.Id]"
>
<!-- <template slot="append">2</template> -->
@@ -248,6 +253,7 @@
<!-- 上传图像 -->
<el-upload
v-if="question.Type==='upload'"
:disabled="readingTaskState === 2"
action
:accept="question.FileType"
:limit="question.ImageCount === 0 ? 100 : question.ImageCount"
@@ -258,7 +264,7 @@
:file-list="fileList"
:class="{disabled:question.ImageCount === 0 ? false : fileList.length >= question.ImageCount}"
>
<el-button slot="default" class="el-icon-plus">
<el-button slot="default" class="el-icon-plus" v-if="readingTaskState < 2">
{{this.$t('common:button:upload')}}
</el-button>
</el-upload>
@@ -337,16 +343,37 @@
</el-button>
</template>
</base-model>
<!-- 预览文件 -->
<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 { uploadReadingAnswerImage, getTrialOrganList, deleteReadingRowAnswer, getCustomTableQuestionPreview, getQuestionCalculateRelation, submitTableQuestion } from '@/api/trials'
import QuestionTableFormItem from './CustomizeQuestionTableFormItem'
import BaseModel from '@/components/BaseModel'
import PreviewFile from '@/components/PreviewFile/index'
import DicomEvent from './../components/DicomEvent'
export default {
name: 'CustomizeQuestionFormItem',
components: { QuestionTableFormItem, BaseModel },
components: { QuestionTableFormItem, BaseModel, PreviewFile },
props: {
IsBaseline: {
type: Boolean,
@@ -398,7 +425,10 @@ export default {
RowId: null,
digitPlaces: 2,
CalculationTabelList: [],
classArr: []
classArr: [],
previewVisible: false,
currentPath: '',
currentType: ''
}
},
watch: {
@@ -422,6 +452,13 @@ export default {
this.formItemNumberChange(this.question.Id, false)
}
},
readingTaskState: {
deep: true,
immediate: true,
handler(v, oldv) {
// console.log(v)
}
}
},
mounted() {
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
@@ -547,6 +584,7 @@ export default {
this.$set(this.QuestionsForm, obj.key, null)
},
handleSave() {
console.log('tableQsForm')
this.$refs.tableQsForm.validate(valid => {
if (!valid) return
const loading = this.$loading({ fullscreen: true })
@@ -612,6 +650,7 @@ export default {
var index = this.AnswersList.findIndex(v => v.RowId === this.QuestionsForm.RowId)
this.AnswersList.splice(index, 1, this.QuestionsForm)
}
console.log({key: this.question.Id, val: this.AnswersList, question: this.question})
this.$emit('setFormItemData', {key: this.question.Id, val: this.AnswersList, question: this.question})
this.formItemNumberChange(this.question.Id, true)
this.addOrEdit.visible = false
@@ -935,7 +974,8 @@ export default {
let res = await this.OSSclient.put(`/${this.$route.query.trialId}/Customize/${this.visitTaskId}/${fileName}`, file)
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${this.fileList.length + 1}`, url: this.$getObjectName(res.url) })
this.urls.push(this.$getObjectName(res.url))
this.$emit('setFormItemData', { key: this.question.Id, val: this.urls.length > 0 ? this.urls.join('|') : '', question: this.question })
this.$emit("setFormItemData", { key: this.question.Id, val: this.urls.length > 0 ? this.urls.join('|') : '', question: this.question })
this.$set(this.QuestionsForm, this.question.Id, this.urls.length > 0 ? this.urls.join('|') : '')
loading.close()
// uploadReadingAnswerImage(this.$route.query.trialId, this.visitTaskId, formData).then(res => {
// if (res.IsSuccess) {
@@ -972,7 +1012,10 @@ export default {
var suffix = file.url.substring(file.url.lastIndexOf(".")+1)
suffix = suffix ? suffix.toLowerCase() : ''
if (suffix === 'doc' || suffix === 'docx' || suffix === 'pdf'){
window.open(this.OSSclientConfig.basePath + file.url,'_blank')
// window.open(this.OSSclientConfig.basePath + file.url,'_blank')
this.currentPath = file.url
this.currentType = suffix
this.previewVisible = true
}else{
this.imageUrl = this.OSSclientConfig.basePath + file.url
this.imgVisible = true
@@ -986,8 +1029,12 @@ export default {
if (file && file.status === "success") {
this.imageUrl = ''
this.fileList.splice(this.fileList.findIndex(f => f.url === file.url), 1)
this.fileList.forEach((i,index)=>{
i.name = `${this.$t('trials:emailManageCfg:title:fileName')}${index+ 1}`
})
this.urls.splice(this.fileList.findIndex(f => f === file.url), 1)
this.$emit('setFormItemData', { key: this.question.Id, val: this.urls.length > 0 ? this.urls.join('|') : '', question: this.question })
this.$set(this.QuestionsForm, this.question.Id, this.urls.length > 0 ? this.urls.join('|') : '')
}
}
}
@@ -77,6 +77,7 @@ export default {
methods: {
handleSave(isMsg) {
return new Promise(resolve => {
console.log('handleSave')
this.$refs['questions'].validate((valid) => {
if (!valid) {
resolve(false)
@@ -232,7 +233,9 @@ export default {
this.questionForm[v] = ''
},
setFormItemData(obj) {
console.log('setFormItemData', obj)
this.$set(this.questionForm, obj.key, JSON.parse(JSON.stringify(obj.val)))
console.log(this.questionForm)
this.classArr.map(i=>{
if (i.triggerId === obj.key) {
let answer = null
@@ -194,7 +194,7 @@
</template>
</template>
<template v-else-if="task.VisitTaskId === visitTaskId && scope.row.Type === 'upload'">
<CustomizeReportPageUpload
<customize-report-page-upload
v-if="scope.row.Type==='upload' && (scope.row.xfIndex || scope.row.xfIndex === 0)"
:visitTaskId="visitTaskId"
:question="scope.row"
@@ -202,8 +202,8 @@
:readingTaskState="readingTaskState"
:initUrl="questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId]"
@setImageUrl="(url) => {setImageUrl(scope.row.QuestionId, scope.row.xfIndex, scope.row.TableQuestionId, url, scope.row.RowId)}"
></CustomizeReportPageUpload>
<CustomizeReportPageUpload
></customize-report-page-upload>
<customize-report-page-upload
v-else-if="scope.row.Type==='upload'"
:visitTaskId="visitTaskId"
:question="scope.row"
@@ -211,25 +211,25 @@
:readingTaskState="readingTaskState"
:initUrl="questionForm[scope.row.QuestionId]"
@setImageUrl="(url) => {setImageUrl(scope.row.QuestionId, scope.row.xfIndex, scope.row.TableQuestionId, url)}"
></CustomizeReportPageUpload>
></customize-report-page-upload>
</template>
<template v-else-if="scope.row.Type === 'upload'">
<CustomizeReportPageUpload
<customize-report-page-upload
v-if="scope.row.Type==='upload' && (scope.row.xfIndex || scope.row.xfIndex === 0)"
:visitTaskId="visitTaskId"
:question="scope.row"
:task="task"
:readingTaskState="readingTaskState"
:initUrl="scope.row.Answers[task.VisitTaskId]"
></CustomizeReportPageUpload>
<CustomizeReportPageUpload
></customize-report-page-upload>
<customize-report-page-upload
v-else-if="scope.row.Type==='upload'"
:visitTaskId="visitTaskId"
:question="scope.row"
:task="task"
:readingTaskState="readingTaskState"
:initUrl="scope.row.Answers[task.VisitTaskId]"
></CustomizeReportPageUpload>
></customize-report-page-upload>
</template>
<template v-else-if="scope.row.QuestionType=== 22">
{{ scope.row.Answers[task.VisitTaskId] === '-1' ? '未知' : scope.row.Answers[task.VisitTaskId] }}
@@ -276,6 +276,7 @@
<script>
import { changeCalculationAnswer, getReadingReportEvaluation, changeDicomReadingQuestionAnswer, submitDicomVisitTask, verifyVisitTaskQuestions, getQuestionCalculateRelation } from '@/api/trials'
import { setSkipReadingCache } from '@/api/reading'
import { getAutoCutNextTask } from '@/api/user'
import DicomEvent from './../components/DicomEvent'
import CustomizeReportPageUpload from './CustomizeReportPageUpload'
import const_ from '@/const/sign-code'
@@ -736,15 +737,16 @@ export default {
}
},
//
signConfirm(signInfo) {
async signConfirm(signInfo) {
this.loading = true
var params = {
data: {
visitTaskId: this.visitTaskId
},
signInfo: signInfo
}
submitDicomVisitTask(params).then(res => {
try {
var params = {
data: {
visitTaskId: this.visitTaskId
},
signInfo: signInfo
}
let res = await submitDicomVisitTask(params)
if (res.IsSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (this.$refs['signForm']) {
@@ -752,41 +754,36 @@ export default {
}
this.signVisible = false
// window.location.reload()
// window.opener.postMessage('refreshTaskList', window.location)
//
this.readingTaskState = 2
store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 2 })
DicomEvent.$emit('setReadingState', 2)
window.opener.postMessage('refreshTaskList', window.location)
this.$confirm(this.$t('trials:oncologyReview:title:msg2'), {
type: 'warning',
distinguishCancelAndClose: true
})
const res = await getAutoCutNextTask()
let isAutoTask = res.Result.AutoCutNextTask
if (isAutoTask) {
window.location.reload()
} else {
this.$confirm(this.$t('trials:oncologyReview:title:msg2'), {
type: 'warning',
distinguishCancelAndClose: true
})
.then(() => {
// var token = getToken()
// var subjectCode = this.$router.currentRoute.query.subjectCode
// var subjectId = this.$router.currentRoute.query.subjectId
// var trialId = this.$router.currentRoute.query.trialId
// this.$router.push({
// path: `/readingPage?subjectCode=${subjectCode}&subjectId=${subjectId}&trialId=${trialId}&TokenKey=${token}`
// })
// DicomEvent.$emit('getNextTask')
window.location.reload()
})
.catch(action => {
})
}
}
this.loading = false
}).catch(() => {
} catch(e) {
this.loading = false
if (this.$refs['signForm'] && this.$refs['signForm'].btnLoading) {
this.$refs['signForm'].btnLoading = false
}
})
}
},
previewDicoms(task) {
var token = getToken()
@@ -19,12 +19,20 @@
class="dicom-desc"
style="width: 150px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
>
<el-tooltip class="item" effect="dark" :content="`${study.StudyCode} ${study.Description?study.Description:''} ${study.Modalities} (${study.SeriesCount})`" placement="right">
<div>
<span>{{ study.StudyCode }} {{ study.Description }}</span>
<span> {{ study.Modalities }} ({{ study.SeriesCount }})</span>
<div>
<div style="text-overflow: ellipsis;overflow: hidden;" v-if="!study.StudyName">
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span>{{ study.Modalities }} ({{ study.SeriesCount }})</span>
</div>
</el-tooltip>
<div style="text-overflow: ellipsis;overflow: hidden;" v-else>
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="taskInfo && taskInfo.IsShowStudyName" :title="study.StudyName">
{{study.StudyName}}
</span>
<div>{{ study.Modalities }} ({{ study.SeriesCount }})</div>
</div>
<div style="text-overflow: ellipsis;overflow: hidden;" :title="study.Description">{{ study.Description }}</div>
</div>
</div>
</template>
@@ -212,7 +220,8 @@ export default {
srDialogVisible: false,
srInfo: {},
digitPlaces: 2,
visitTaskIdx: -1
visitTaskIdx: -1,
taskInfo: null
}
},
@@ -250,6 +259,7 @@ export default {
this.subjectCode = localStorage.getItem('subjectCode')
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
DicomEvent.$on('refreshStudyListMeasureData', () => {
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
this.measureData = this.visitTaskList[idx].MeasureData
@@ -1,7 +1,10 @@
<template>
<div ref="container" style="width:100%;height:100%" class="dicom-container">
<!-- 访视阅片 -->
<div v-if="(isReadingTaskViewInOrder === 1 || ((isReadingTaskViewInOrder !== 1) && isShow)) && readingCategory=== 1 && CriterionType !== 0" class="reading-wrapper">
<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">
<!-- 阅片 -->
<el-tab-pane :label="$t('trials:reading:tabTitle:review')" name="read">
@@ -29,7 +32,7 @@
</el-tab-pane>
</el-tabs>
</div>
<div v-if="(isReadingTaskViewInOrder === 1 || ((isReadingTaskViewInOrder !== 1) && isShow)) && readingCategory=== 1 && CriterionType === 0" class="reading-wrapper">
<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="beforeLeaveCustomize">
<!-- 阅片 -->
<el-tab-pane :label="$t('trials:reading:tabTitle:review')" name="read">
@@ -171,6 +174,7 @@
</template>
<script>
import { getNextTask, readClinicalData, verifyDefaultQuestionBeAnswer } from '@/api/trials'
import VisitReview from './../dicoms3D/components/VisitReview'
import ReadPage from './components/ReadPage'
import CustomizeReadPage from './customize/CustomizeReadPage'
import ReportPage from './components/ReportPage'
@@ -187,6 +191,7 @@ import requestPoolManager from '@/utils/request-pool'
export default {
name: 'Reading',
components: {
VisitReview,
ReadPage,
ReportPage,
GlobalReview,
@@ -217,7 +222,7 @@ export default {
isReadingTaskViewInOrder: null,
firstTaskReadingCategory: null,
criterionType: null,
readingTool: null,
readingTool: 0,
dialogVisible: false,
TrialReadingCriterionId: null,
isNewSubject: null,
@@ -229,7 +234,8 @@ export default {
isFullscreen: false,
tipVisible: false,
closeCDVisible: false,
cdVisitTaskId: ''
cdVisitTaskId: '',
readingVersionEnum: null
}
},
mounted() {
@@ -266,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
@@ -339,7 +345,9 @@ export default {
localStorage.setItem('CriterionType', res.Result.CriterionType)
localStorage.setItem('digitPlaces', res.Result.DigitPlaces)
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) {
@@ -588,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{
@@ -66,7 +66,8 @@ export default function(evt) {
draw(context, context => {
// Configurable shadow
setShadow(context, this.configuration)
// Draw perpendicular line
const strokeWidth = lineWidth
const {
start,
end,
@@ -86,9 +87,6 @@ export default function(evt) {
// Draw the measurement line
drawLine(context, element, start, end, lineOptions)
// Draw perpendicular line
const strokeWidth = lineWidth
updatePerpendicularLineHandles(eventData, data)
drawLine(
@@ -10,9 +10,13 @@ const getNewContext = cornerstoneTools.import('drawing/getNewContext')
const draw = cornerstoneTools.import('drawing/draw')
const drawHandles = cornerstoneTools.import('drawing/drawHandles')
const drawTextBox = cornerstoneTools.import('drawing/drawTextBox')
const drawLink = cornerstoneTools.import('drawing/drawLink')
const drawLinkedTextBox = cornerstoneTools.import('drawing/drawLinkedTextBox')
// Utilities
const getRGBPixels = cornerstoneTools.import('util/getRGBPixels')
const calculateSUV = cornerstoneTools.import('util/calculateSUV')
const getROITextBoxCoords = cornerstoneTools.import('util/getROITextBoxCoords')
// import { probeCursor } from '../cursors/index.js';
// import { getLogger } from '../../util/logger.js';
const throttle = cornerstoneTools.import('util/throttle')
@@ -20,6 +24,8 @@ const getModule = cornerstoneTools.getModule
const getPixelSpacing = cornerstoneTools.import('util/getPixelSpacing')
// import numbersWithCommas from './../../util/numbersWithCommas.js';
const numbersWithCommas = cornerstoneTools.import('util/numbersWithCommas')
const clipBoxToDisplayedArea = cornerstoneTools.import('util/clip')
// const logger = getLogger('tools:annotation:ProbeTool');
import calculateEllipseStatistics from './calculateEllipseStatistics'
import getCircleCoords from './getCircleCoords'
@@ -31,6 +37,19 @@ import getCircleCoords from './getCircleCoords'
* desired position.
* @extends Tools.Base.BaseAnnotationTool
*/
const getHandle = (x, y, index, extraAttributes = {}) =>
Object.assign(
{
x,
y,
index,
drawnIndependently: false,
allowedOutsideImage: false,
highlight: true,
active: false
},
extraAttributes
)
export default class ProbeTool extends cornerstoneTools.ProbeTool {
constructor(props = {}) {
const defaultProps = {
@@ -64,7 +83,8 @@ export default class ProbeTool extends cornerstoneTools.ProbeTool {
return;
}
const { x, y } = eventData.currentPoints.image
return {
visible: true,
active: true,
@@ -84,6 +104,23 @@ export default class ProbeTool extends cornerstoneTools.ProbeTool {
active: true,
radius: 0
},
// textBox: {
// active: false,
// hasMoved: false,
// movesIndependently: false,
// drawnIndependently: true,
// allowedOutsideImage: true,
// hasBoundingBox: true,
// }
textBox: getHandle(x, y - 30, null, {
highlight: false,
hasMoved: true,
active: false,
movesIndependently: false,
drawnIndependently: true,
allowedOutsideImage: true,
hasBoundingBox: true
})
},
};
}
@@ -226,7 +263,7 @@ export default class ProbeTool extends cornerstoneTools.ProbeTool {
draw(context, context => {
const color = toolColors.getColorIfActive(data);
const lineWidth = toolStyle.getToolWidth()
if (this.configuration.drawHandles) {
// Draw the handles
let radius = getCanvasRadius(data.handles, fixedRadius, element, pixelSpacing)
@@ -273,28 +310,133 @@ export default class ProbeTool extends cornerstoneTools.ProbeTool {
}
let r = getPixelRadius(fixedRadius, pixelSpacing)
// Coords for text
const coords = {
// Translate the x/y away from the cursor
x: data.handles.end.x + r,
y: data.handles.end.y - r,
};
const textCoords = external.cornerstone.pixelToCanvas(
eventData.element,
coords
);
drawTextBox(
if (!data.handles.hasOwnProperty('textBox')) {
const coords = {
// Translate the x/y away from the cursor
x: data.handles.end.x + r,
y: data.handles.end.y - r,
};
const textCoords = external.cornerstone.pixelToCanvas(
eventData.element,
coords
);
drawTextBox(
context,
textLines,
textCoords.x,
textCoords.y ,
color
);
return
}
// const handles = {
// start: {
// x: data.handles.end.x,
// y: data.handles.end.y
// },
// end: {
// x: data.handles.end.x + r,
// y: data.handles.end.y
// }
// }
let xOffset = 0
if (data.remark && !isNaN(parseInt(data.remark.slice(-1)))) {
let i = parseInt(data.remark.slice(-1))
if (i === 1) {
xOffset = -60
} else if (i === 2) {
xOffset = 0
} else if (i=== 3) {
xOffset = 0
}
}
// const xOffset = -30
const textBoxAnchorPoints = handles => [
handles.end
]
_drawLinkedTextBox(
context,
element,
data.handles.textBox,
textLines,
textCoords.x,
textCoords.y ,
color
);
// drawTextBox(context, '', textCoords.x, textCoords.y, color);
data.handles,
color,
lineWidth,
xOffset,
false
)
}
});
}
}
}
function _drawLinkedTextBox(
context,
element,
textBox,
text,
handles,
color,
lineWidth,
xOffset,
yCenter) {
const { pixelToCanvas } = external.cornerstone;
// Convert the textbox Image coordinates into Canvas coordinates
const textCoords = pixelToCanvas(element, textBox);
if (xOffset) {
textCoords.x += xOffset;
}
const options = {
centering: {
x: false,
y: yCenter,
},
};
options.displacer = box => clipBoxToDisplayedArea(element, box);
// Draw the text box
textBox.boundingBox = drawTextBox(
context,
text,
textCoords.x,
textCoords.y,
color,
options
);
if (textBox.hasMoved) {
// Identify the possible anchor points for the tool -> text line
let arr = [
{
x: handles.end.x,
y: handles.end.y
},
{
x: handles.end.x,
y: handles.end.y
}
]
const linkAnchorPoints = arr.map(h =>
pixelToCanvas(element, h)
);
// Draw dashed link line between tool and text
drawLink(
linkAnchorPoints,
textCoords,
textBox.boundingBox,
context,
color,
lineWidth
);
}
}
function _getUnit(modality, showHounsfieldUnits) {
return modality === 'CT' && showHounsfieldUnits !== false ? 'HU' : '';
}
@@ -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
@@ -0,0 +1,325 @@
<template>
<div>
<div
v-if="!!question.GroupName && question.Type==='group'"
>
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
{{ language==='en'?question.GroupEnName:question.GroupName }}
</h4>
</div>
<template v-else>
<el-form-item
v-if="(question.ShowQuestion===1 && question.ParentTriggerValueList.includes(String(questionForm[question.ParentId]))) || question.ShowQuestion===0 "
:label="`${question.QuestionName}`"
:prop="question.Id"
:rules="[
{ required: (question.IsRequired === 0 || (question.IsRequired ===1 && question.RelevanceId && (questionForm[question.RelevanceId] === question.RelevanceValue))) && question.Type!=='group' && question.Type!=='summary',
message: ['radio', 'select', 'checkbox'].includes(question.Type) ? $t('common:ruleMessage:select') : $t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
]"
:class="[question.Type==='group' ? 'mb' : (question.Type==='upload' || question.QuestionName.length > 15) ?'uploadWrapper' : '']"
>
<!-- 输入框 -->
<el-input
v-if="question.Type==='input'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
/>
<!-- 多行文本输入框 -->
<el-input
v-else-if="question.Type==='textarea'"
v-model="questionForm[question.Id]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
maxlength="500"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
/>
<!-- 下拉框 -->
<el-select
v-else-if="question.Type==='select'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode)"
clearable
@change="((val)=>{formItemChange(val, question)})"
>
<template v-if="question.TableQuestionType === 1">
<el-option
v-for="item in organList"
:key="item.Id"
:label="item[question.DataTableColumn]"
:value="item[question.DataTableColumn]"
/>
</template>
<template v-else-if="question.TableQuestionType === 3 || question.QuestionGenre === 3">
<el-option
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:value="String(item.value)"
:label="item.label"
/>
</template>
<template v-else-if="(question.TableQuestionType === 2 || question.QuestionGenre === 2) && question.DictionaryCode">
<el-option
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:value="String(item.value)"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<!-- 单选 -->
<el-radio-group
v-else-if="question.Type==='radio'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
>
<template v-if="question.DictionaryCode">
<el-radio
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:label="String(item.value)"
>
{{ item.label }}
</el-radio>
</template>
<template v-else-if="question.TypeValue">
<el-radio
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-radio>
</template>
</el-radio-group>
<!-- 复选框 -->
<el-checkbox-group
v-else-if="question.Type==='checkbox'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
>
<el-checkbox
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-checkbox>
</el-checkbox-group>
<!-- 数值 -->
<template v-else-if="question.Type==='number'">
<el-input-number
v-if="question.ValueType === 0"
v-model="questionForm[question.Id]"
:precision="0"
:disabled="readingTaskState >= 2"
/>
<el-input-number
v-else-if="question.ValueType === 3"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
/>
<el-input-number
v-else-if="question.ValueType === 1 || question.ValueType === 2"
v-model="questionForm[question.Id]"
:precision="digitPlaces"
:disabled="readingTaskState >= 2"
/>
</template>
<el-input
v-else-if="question.Type==='calculation'"
v-model="questionForm[question.Id]"
disabled
>
<template v-if="question.Unit" slot="append">
{{ $fd('ValueUnit', parseInt(question.Unit)) }}
</template>
</el-input>
<!-- 上传图像 -->
<el-upload
v-else-if="question.Type==='upload'"
action
:accept="accept"
:limit="question.ImageCount"
:on-preview="handlePictureCardPreview"
:before-upload="handleBeforeUpload"
:http-request="uploadScreenshot"
list-type="picture-card"
:on-remove="handleRemove"
:file-list="fileList"
:class="{disabled:fileList.length >= question.ImageCount}"
:disabled="readingTaskState >= 2"
>
<i slot="default" class="el-icon-plus" />
<div slot="file" slot-scope="{file}">
<img
class="el-upload-list__item-thumbnail"
:src="OSSclientConfig.basePath + file.url"
alt=""
>
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<i class="el-icon-zoom-in" />
</span>
<span
v-if="readingTaskState < 2"
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<i class="el-icon-delete" />
</span>
</span>
</div>
</el-upload>
<el-dialog
v-if="question.Type==='upload'"
append-to-body
:visible.sync="imgVisible"
width="600px"
>
<el-image :src="imageUrl" width="100%">
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</el-dialog>
</el-form-item>
</template>
<question-form-item
v-for="(item) in question.Childrens"
:key="item.Id"
:question="item"
:question-form="questionForm"
:reading-task-state="readingTaskState"
:visitTaskId="visitTaskId"
@resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData"
/>
</div>
</template>
<script>
// import { uploadReadingAnswerImage } from '@/api/trials'
import { mapGetters } from 'vuex'
export default {
name: 'QuestionFormItem',
props: {
questionForm: {
type: Object,
default() {
return {}
}
},
question: {
type: Object,
default() {
return {}
}
},
readingTaskState: {
type: Number,
required: true
},
visitTaskId: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
accept: '.png,.jpg,.jpeg',
imgVisible: false,
imageUrl: '',
urls: [],
digitPlaces: 2
}
},
computed: {
...mapGetters(['language'])
},
watch: {
questionForm: {
deep: true,
immediate: true,
handler(v) {
// console.log(v)
}
}
},
mounted() {
let digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
},
methods: {
formItemChange(v, question) {
console.log('formItemChange')
if (question.Childrens.length > 0) {
this.resetChild(question.Childrens)
} else {
this.$emit('setFormItemData', { key: question.Id, val: v })
}
},
resetChild(obj) {
obj.forEach(i => {
this.$emit('resetFormItemData', i.Id)
if (i.Childrens && i.Childrens.length > 0) {
this.resetChild(i.Childrens)
}
})
},
resetFormItemData(v) {
this.$emit('resetFormItemData', v)
},
setFormItemData(obj) {
this.$emit('setFormItemData', obj)
},
}
}
</script>
<style lang="scss" scoped>
.mb{
margin-bottom: 0px;
}
.disabled{
::v-deep .el-upload--picture-card {
display: none;
}
}
.uploadWrapper{
display: flex;
flex-direction: column !important;
align-items: flex-start;
}
::v-deep .el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-input-group__append, .el-input-group__prepend{
padding: 0 10px;
}
::v-deep .el-form-item__content {
width: 100%;
}
::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>
@@ -0,0 +1,357 @@
<template>
<div class="table-question-form">
<div style="display: flex;justify-content: space-between;">
<h3 v-if="qsForm.LesionName" style="color: #ddd;padding: 5px 0px;margin: 0;">
{{ qsForm.LesionName }}
</h3>
<!-- 关闭 -->
<div>
<i class="el-icon-circle-close" style="font-size: 25px;cursor: pointer;" @click="close" />
</div>
</div>
<el-form-item
:label="$t('trials:reading:title:lesionType')"
prop="LesionType"
:rules="[
{ required:true,message: $t('common:ruleMessage:select'), trigger: ['blur']},
]"
>
<!-- 下拉框 -->
<el-select
v-model="qsForm.LesionType"
filterable
:disabled="!isCurrentTask || readingTaskState>=2 || !isBaseLineTask"
@change="((val)=>{lesionTypeChange(val)})"
>
<el-option
v-for="item of $d.LesionType"
v-show="!(isBaseLineTask && item.value === 2)"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item
v-for="qs in questions"
v-show="qs.ShowQuestion!==2"
:key="qs.Id"
:label="`${qs.QuestionName}`"
:prop="qs.Id"
:rules="[
{ required: (qs.IsRequired === 0 || (qs.IsRequired ===1 && qs.RelevanceId && (questionForm[qs.RelevanceId] === qs.RelevanceValue)) || (qs.QuestionMark === 6 && questionForm.IsCanEditPosition === true) || (questionForm.IsCanEditPosition && qs.QuestionMark === 10)) && qs.Type!=='group' && qs.Type!=='summary',
message:['radio', 'select', 'checkbox'].includes(qs.Type) ? $t('common:ruleMessage:select') : $t('common:ruleMessage:specify'), trigger: ['blur','change']},
]"
>
<!-- 输入框 -->
<template v-if="qs.Type==='input' || qs.Type==='number'">
<el-input
v-if="qs.Type==='input' || qs.Type==='number'"
v-model="qsForm[qs.Id]"
:disabled="!isCurrentTask || readingTaskState>=2 || qs.QuestionMark === 0 || qs.QuestionMark === 1 || qs.QuestionMark === 2 || qs.QuestionMark === 5 || (qs.QuestionMark === 6 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName))|| (qs.QuestionMark === 8 && (isCurrentTaskAdd === 'False' || !!answer.SplitOrMergeLesionName) && lesionType !== 2) || (qs.QuestionMark === 10 && (isCurrentTaskAdd === 'False' || !!answer.SplitOrMergeLesionName))"
@change="((val)=>{formItemChange(val, qs)})"
>
<template v-if="(qs.QuestionMark===0 || qs.QuestionMark===1) && qs.Unit" slot="append">
{{ $fd('ValueUnit', parseInt(qs.Unit)) }}
</template>
</el-input>
</template>
<!-- 多行文本输入框 -->
<el-input
v-if="qs.Type==='textarea'"
v-model="qsForm[qs.Id]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
:disabled="!isCurrentTask || readingTaskState>=2"
@change="((val)=>{formItemChange(val, qs)})"
/>
<!-- 下拉框 -->
<el-select
v-if="qs.Type==='select'"
v-model="qsForm[qs.Id]"
filterable
:placeholder="qs.QuestionMark === 8 ? $t('common:placeholder:selectorsearch') : qs.QuestionMark === 2 ? '' : $t('common:placeholder:select')"
:disabled="!isCurrentTask || readingTaskState>=2 || qs.QuestionMark === 0 || qs.QuestionMark === 1 || qs.QuestionMark === 2 || qs.QuestionMark === 5 || (qs.QuestionMark === 6 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName))|| (qs.QuestionMark === 8 && (isCurrentTaskAdd === 'False'|| !!answer.SplitOrMergeLesionName)) || (qs.QuestionMark === 10 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName))"
@change="((val)=>{formItemChange(val, qs)})"
>
<template v-if="qs.QuestionMark === 8" #prefix>
<span style="padding-left: 5px;">
<i class="el-icon-search" />
</span>
</template>
<template v-if="qs.TableQuestionType === 1">
<el-option
v-for="item in organList"
:key="item.Id"
:label="item[qs.DataTableColumn]"
:value="item[qs.DataTableColumn]"
/>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark === 7 && isBaseLineTask">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="(lesionType === 0 && item.value ===0) || (lesionType === 1 && (item.value ===0))"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark === 7 && !isBaseLineTask">
<el-option-group
:label="!isNaN(parseFloat(answer.LastTaskState)) ? `${$t('trials:dicomReading:tip:lastVisitStatus')} ${$fd(qs.DictionaryCode,parseFloat(answer.LastTaskState))}` : ''"
>
<!-- 首次分裂的病灶只能选择存在 -->
<template v-if="answer.IsFristAdd=== 'True' && answer.SplitOrMergeType === '0'">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="item.value === 0"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<!-- 首次添加的新病灶不能为无法评估和消失 -->
<template v-else-if="isCurrentTaskAdd=== 'True' && lesionType === 2">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="item.value === 0 || item.value === 1"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="item of filterState($d[qs.DictionaryCode])"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
</el-option-group>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark !== 7">
<el-option
v-for="item of $d[qs.DictionaryCode]"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="val in qs.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<!-- 单选 -->
<el-radio-group
v-if="qs.Type==='radio'"
v-model="qsForm[qs.id]"
:disabled="!isCurrentTask || readingTaskState>=2"
>
<el-radio
v-for="val in qs.options.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-radio>
</el-radio-group>
</el-form-item>
</div>
</template>
<script>
export default {
name: 'TableQuestionFormItem',
props: {
tableInfo: {
type: Object,
default() {
return {}
}
},
answer: {
type: Object,
default() {
return {}
}
},
questionForm: {
type: Object,
default() {
return {}
}
},
organs: {
type: Array,
default() {
return []
}
},
readingTaskState: {
type: Number,
required: true
},
isBaseLineTask: {
type: Boolean,
required: true
},
isCurrentTask: {
type: Boolean,
required: true
}
},
data() {
return {
isCurrentTaskAdd: null,
lesionType: null,
questions: [],
organList: [],
qsForm: {}
}
},
watch: {
questionForm: {
deep: true,
immediate: true,
handler(v) {
if (v.MeasureData) {
const { markTool } = v.MeasureData
if (markTool === 'Bidirectional') {
this.getOrganList(1)
} else if (markTool === 'Length') {
this.getOrganList(0)
} else {
this.getOrganList()
}
} else {
this.getOrganList()
}
for (const key in v) {
this.$set(this.qsForm, key, v[key])
}
this.isCurrentTaskAdd = v.IsCurrentTaskAdd
}
},
tableInfo: {
deep: true,
immediate: true,
handler(v) {
if (v) {
this.lesionType = v.LesionType
this.questions = v.TableQuestions.Questions
}
}
}
},
methods: {
lesionTypeChange(v) {
this.$emit('lesionTypeChange', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex, newLesionType: v })
},
formItemChange(v, qs) {
const updateArr = []
if (qs.QuestionMark === 8 && qs.RelationQuestions.length > 0) {
//
const index = this.organList.findIndex(item => item[qs.DataTableColumn] === v)
if (index > -1) {
const selected = this.organList[index]
qs.RelationQuestions.map(q => {
const val = selected[q.DataTableColumn]
updateArr.push({ questionId: q.Id, val: val, questionMark: q.QuestionMark })
})
updateArr.push({ questionId: 'OrganInfoId', val: selected.OrganInfoId })
updateArr.push({ questionId: 'IsCanEditPosition', val: selected.IsCanEditPosition })
} else {
qs.RelationQuestions.map(q => {
updateArr.push({ questionId: q.Id, val: '', questionMark: q.QuestionMark })
})
}
}
this.$emit('update', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex, questionMark: qs.QuestionMark, questionId: qs.Id, val: v, updateArr })
},
getOrganList(isLymphNodes = null) {
const idx = this.organs.findIndex(i => i.LesionType === this.questionForm.LesionType)
if (idx > -1) {
const arr = this.organs[idx].OrganList
if (!isNaN(parseInt(isLymphNodes))) {
this.organList = arr.filter((item) => item.IsLymphNodes === parseInt(isLymphNodes))
} else {
this.organList = arr
}
}
},
filterState(arr) {
if (!this.isBaseLineTask) {
const isLymphLesion = this.questionForm.IsLymphNodes
const lesionLength = this.questionForm.LesionLength
const lesionShort = this.questionForm.LesionShort
const bLesionL = !isNaN(parseFloat(this.questionForm.BaseLineMajorAxis)) ? parseFloat(this.questionForm.BaseLineMajorAxis) : 0
const bLesionS = !isNaN(parseFloat(this.questionForm.BaseLineShortAxis)) ? parseFloat(this.questionForm.BaseLineShortAxis) : 0
if (this.lesionType === 1 && isLymphLesion === 1 && this.questionForm.MeasureData && this.questionForm.MeasureData.markTool === 'Bidirectional' && lesionShort < bLesionS) {
arr = arr.filter(i => i.value !== 1)
} else if (this.lesionType === 1 && isLymphLesion === 1 && this.questionForm.MeasureData && this.questionForm.MeasureData.markTool === 'Bidirectional' && lesionShort >= 10 && lesionShort > bLesionS) {
arr = arr.filter(i => i.value === 0 || i.value === 1)
} else if (this.lesionType === 1 && isLymphLesion === 0 && this.questionForm.MeasureData && (this.questionForm.MeasureData.markTool === 'Length' || this.questionForm.MeasureData.markTool === 'Bidirectional') && lesionLength < bLesionL) {
arr = arr.filter(i => i.value !== 1)
}
return arr
} else {
return arr
}
},
close() {
this.$emit('close', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex })
}
}
}
</script>
<style lang="scss" scoped>
.table-question-form{
::v-deep .el-form-item__label{
color: #c3c3c3;
}
::v-deep .el-input .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-form-item{
display: flex;
flex-direction: row;
justify-content: flex-start;
}
::v-deep .el-form-item__content{
flex: 1;
}
::v-deep .el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-select.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-button--mini, .el-button--mini.is-round {
padding: 7px 10px;
}
.el-form-item__content
.el-select{
width: 100%;
}
.input-width1{
width: calc(100% - 60px)!important;
}
.input-width2{
width: 100% !important;
}
}
</style>
@@ -0,0 +1,914 @@
<template>
<div class="report-wrapper">
<el-card v-loading="loading" shadow="never">
<div slot="header" class="clearfix report-header">
<!-- 电子影像病例报告表eICRF -->
<h3 style="margin:0;padding:0;">{{ $t('trials:readingReport:title:eicrf') }}</h3>
<div style="margin-left:auto">
<el-switch
v-model="isShowDetail"
:active-text="$t('trials:readingReport:title:expandDetails')"
:inactive-text="$t('trials:readingReport:title:collapseDetails')"
style="margin-right:5px;"
@change="handleShowDetail"
/>
<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(false)"
>
<!-- 刷新 -->
{{ $t('trials:readingReport:button:refresh') }}
</el-button>
<el-button
v-if="readingTaskState<2 && criterionType !== 10"
type="primary"
size="small"
@click="handleSave(true)"
>
<!-- 保存 -->
{{ $t('common:button:save') }}
</el-button>
<el-button
v-if="readingTaskState<2"
type="primary"
size="small"
@click="beforeLeave"
>
<!-- 提交 -->
{{ $t('common:button:submit') }}
</el-button>
</div>
</div>
<div ref="tableWrapper" style="height:100%;overflow-y: auto;">
<el-table
v-if="height"
ref="reportList"
:data="taskQuestions"
row-key="Id"
border
:expand-row-keys="expandedRows"
:height="height"
: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" :style="{color: ((scope.row.isLymphNodes === 1 && scope.row.QuestionMark === 1) || (scope.row.isLymphNodes === 0 && scope.row.QuestionMark === 0)) && (scope.row.LesionType === 0 || scope.row.LesionType === 5) ? '#f66' : '#fff'}">{{ scope.row.QuestionName }}</span>
<span
v-else
style="font-weight: bold;font-size: 16px;color: #f44336;"
>
{{ language==='en'?scope.row.GroupEnName: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="150px"
>
<template slot="header">
<div v-if="task.IsCurrentTask">
<div>
{{ task.BlindName }}
</div>
</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">
<span :style="{color: ((scope.row.isLymphNodes === 1 && scope.row.QuestionMark === 1) || (scope.row.isLymphNodes === 0 && scope.row.QuestionMark === 0)) && (scope.row.LesionType === 0 || scope.row.LesionType === 5) || (scope.row.QuestionMark === 12) || scope.row.HighlightAnswerList.includes(`${scope.row.Answers[task.VisitTaskId]}`) ? '#f66' : '#fff'}">
<template v-if="task.VisitTaskId === visitTaskId && readingTaskState < 2 && [13,14,15,42].includes(scope.row.QuestionType)">
<!-- 是否存在疾病基线时可修改 -->
<template v-if="task.IsBaseLine && scope.row.QuestionType=== 15">
<el-select
v-if="scope.row.Type==='select' && scope.row.DictionaryCode"
v-model="currentExistDisease"
size="mini"
@change="handleExistDiseaseChange"
>
<el-option
v-for="item of $d[ scope.row.DictionaryCode]"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</el-select>
</template>
<!-- 整体肿瘤评估非基线可修改 -->
<template v-else-if="!task.IsBaseLine && (scope.row.QuestionType=== 13 || scope.row.QuestionType=== 42)">
<el-select
v-if="scope.row.Type==='select' && scope.row.DictionaryCode"
v-model="currentEvaluateResult"
size="mini"
@change="handleEvaluateResultChange"
>
<template v-if="criterionType === 1 && tLesionCount">
<el-option
v-for="item of $d[ scope.row.DictionaryCode]"
v-show="(!task.IsBaseLine && item.value > -1) && (task.CrterionDictionaryGroup.indexOf(item.raw.CrterionDictionaryGroup) > -1) && (item.value !== 1 && item.value !== 3)"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else-if="criterionType === 1 && ntLesionCount">
<el-option
v-for="item of $d[ scope.row.DictionaryCode]"
v-show="(!task.IsBaseLine && item.value > -1) && (task.CrterionDictionaryGroup.indexOf(item.raw.CrterionDictionaryGroup) > -1) && (item.value !== 1)"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="item of $d[ scope.row.DictionaryCode]"
v-show="(!task.IsBaseLine && item.value > -1) && (task.CrterionDictionaryGroup.indexOf(item.raw.CrterionDictionaryGroup) > -1)"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
</el-select>
</template>
<!-- <template v-else-if="task.IsBaseLine && scope.row.QuestionType=== 13">
{{ $fd(scope.row.DictionaryCode, currentEvaluateResult) }}
</template> -->
<!-- 访视点备注是否存在疾病与系统不一致或者整体肿瘤评估与系统不一致时必填 -->
<!-- tumorEvaluate && task.VisitTaskId === visitTaskId && scope.row.QuestionType=== 14 && (currentEvaluateResult !== tumorEvaluate || currentExistDisease !== isExistDisease) -->
<template v-else-if="task.VisitTaskId === visitTaskId && scope.row.QuestionType=== 14 ">
<!-- v-if="tumorEvaluate && (currentEvaluateResult !== tumorEvaluate || currentExistDisease !== isExistDisease)" -->
<template>
<!-- 输入框 -->
<el-input
v-if="scope.row.Type==='input'"
v-model="currentTaskReason"
size="mini"
@change="evaluateReasonChange"
/>
<el-input
v-else-if="scope.row.Type==='textarea'"
v-model="currentTaskReason"
:autosize="{ minRows: 2, maxRows: 4}"
size="mini"
maxlength="500"
@change="evaluateReasonChange"
/>
<!-- 系统评估结果为xxx,与当前调整的结果不一致请填写调整原因 -->
<p v-if="currentEvaluateResult !== tumorEvaluate" style="width: 140px;padding:0 2px;white-space: normal;word-break: break-all;word-wrap: break-word;" v-html="getWarningText()" />
<p v-else-if="currentExistDisease !== isExistDisease" style="width: 140px;padding:0 2px;white-space: normal;word-break: break-all;word-wrap: break-word;">{{ $t('trials:readingReport:title:sysEvaluationRes') }}<span style="color:red">{{ $fd('ExistDisease',isExistDisease) }}</span>{{ $t('trials:readingReport:message:msg1') }}
</p>
</template>
<!-- <template v-else>
<span>{{ currentTaskReason }}</span>
</template> -->
</template>
<template v-else-if="scope.row.DictionaryCode">
{{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}
</template>
<template v-else>
{{ scope.row.Answers[task.VisitTaskId] }}
</template>
</template>
<template v-else-if="scope.row.QuestionType=== 15">
<el-tooltip v-if="getAnswerInfo(scope.row.Answer,task.VisitTaskId,'IsGlobalChange')" :content="`${$t('trials:reading:warnning:msg55')}${$fd(scope.row.DictionaryCode, getAnswerInfo(scope.row.Answer,task.VisitTaskId,'GlobalChangeAnswer'))}`" placement="top" effect="light">
<span style="color:red;"> {{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}</span>
</el-tooltip>
<span v-else>{{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}</span>
</template>
<template v-else-if="scope.row.QuestionType=== 13 || scope.row.QuestionType=== 42">
<el-tooltip v-if="getAnswerInfo(scope.row.Answer,task.VisitTaskId,'IsGlobalChange')" :content="`${$t('trials:reading:warnning:msg55')}${$fd(scope.row.DictionaryCode, getAnswerInfo(scope.row.Answer,task.VisitTaskId,'GlobalChangeAnswer'))}`" placement="top" effect="light">
<span style="color:red;"> {{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}</span>
</el-tooltip>
<span v-else>{{ $fd(scope.row.DictionaryCode, scope.row.Answers[task.VisitTaskId]) }}</span>
</template>
<template v-else-if="scope.row.QuestionType=== 22">
{{ scope.row.Answers[task.VisitTaskId] === '-1' ? $t('trials:readingReport:title:unknow') : scope.row.Answers[task.VisitTaskId] }}
</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="(scope.row.ValueType === 0 || scope.row.ValueType === 1) && scope.row.Unit">
{{ `${scope.row.Answers[task.VisitTaskId]}` }}
<span v-if="scope.row.Answers[task.VisitTaskId] !== 'NA' && !isNaN(parseInt(scope.row.Answers[task.VisitTaskId]))">{{ `${$fd('ValueUnit',scope.row.Unit)}` }}</span>
</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>
</span>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<!-- 附加评估 -->
<el-dialog
v-if="additionalAssessmentsDig.visible"
:visible.sync="additionalAssessmentsDig.visible"
:close-on-click-modal="false"
:title="additionalAssessmentsDig.title"
width="600px"
>
<additional-assessment
:questions="assessmentQuestions"
:visit-task-id="visitTaskId"
@close="additionalAssessmentsDig.visible = false"
@sign="sign"
/>
</el-dialog>
<!-- 签名框 -->
<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 { getReadingReportEvaluation, changeDicomReadingQuestionAnswer, submitDicomVisitTask, verifyVisitTaskQuestions, getTaskAdditionalQuestion } 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 { getToken } from '@/utils/auth'
// import store from '@/store'
import { mapGetters } from 'vuex'
import { changeURLStatic } from '@/utils/history.js'
import AdditionalAssessment from '@/views/trials/trials-panel/reading/dicoms/components/AdditionalAssessment'
export default {
name: 'ReportPage',
components: { SignForm, AdditionalAssessment },
data() {
return {
currentUser: zzSessionStorage.getItem('userName'),
signVisible: false,
signCode: null,
visitTaskList: [],
taskQuestions: [],
loading: false,
answers: [],
readingTaskState: 2,
tumorEvaluate: null,
currentEvaluateResult: null,
isExistDisease: null,
currentExistDisease: null,
currentTaskReason: '',
answerArr: [],
questions: [],
isShowDetail: false,
criterionType: 0,
height: 0,
additionalAssessmentsDig: { visible: false, title: this.$t('trials:trials-panel:setting:reading-unit:TrialCriterionAdditionalAssessmentTypeList') },
assessmentQuestions: [],
tLesionCount: null,
ntLesionCount: null,
openWindow: null,
expandedRows: [],
taskInfo: {},
visitTaskId: '',
isBaselineTask: false
}
},
computed: {
...mapGetters(['language'])
},
watch: {
taskQuestions() {
this.$nextTick(() => {
this.getTableHeight()
this.$refs.reportList && this.$refs.reportList.doLayout()
this.setScrollTop()
})
}
},
created() { this.getTableHeight() },
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.criterionType = this.taskInfo.CriterionType
this.visitTaskId = this.taskInfo.VisitTaskId
this.isBaselineTask = this.taskInfo.IsBaseLine
window.addEventListener('resize', () => {
this.handleResize()
this.setScrollTop()
})
this.getReportInfo()
},
beforeDestroy() {
if (this.openWindow) {
this.openWindow.close()
}
},
methods: {
myConfirm(msg) {
return new Promise(resolve => {
this.$confirm(msg, {
type: 'warning',
showCancelButton: true
}).then(() => {
resolve(true)
}).catch(() => {
resolve(false)
})
})
},
async beforeLeave() {
this.handleConfirm()
},
getAnswerInfo(answerArr, visitTaskId, prop) {
var i = answerArr.findIndex(i => i.VisitTaskId === visitTaskId)
if (i === -1) return ''
var val = answerArr[i][prop]
return isNaN(parseInt(val)) ? val : parseInt(val)
},
getTableHeight() {
this.height = window.innerHeight - 170
},
async getReportInfo(IsCalculate) {
this.loading = true
try {
var params = {
visitTaskId: this.visitTaskId,
trialId: this.$router.currentRoute.query.trialId,
IsCalculate: IsCalculate !== false
}
const res = await getReadingReportEvaluation(params)
if (res.IsSuccess) {
this.readingTaskState = res.Result.ReadingTaskState
this.tumorEvaluate = res.Result.CalculateResult && res.Result.CalculateResult.TumorEvaluate ? parseInt(res.Result.CalculateResult.TumorEvaluate) : null
this.isExistDisease = res.Result.CalculateResult && res.Result.CalculateResult.IsExistDisease ? parseInt(res.Result.CalculateResult.IsExistDisease) : null
this.answerArr = []
this.questions = res.Result.TaskQuestions.concat()
this.visitTaskList = res.Result.VisitTaskList
var taskQuestions = this.getQuestions(res.Result.TaskQuestions, !this.isShowDetail, null, null)
this.taskQuestions = []
taskQuestions.forEach(item => {
this.$set(this.taskQuestions, this.taskQuestions.length, item)
})
const tLesion = res.Result.LesionCountList.find(i => i.LesionType === 0)
this.tLesionCount = tLesion ? tLesion.Count : 0
const ntLesion = res.Result.LesionCountList.find(i => i.LesionType === 1)
this.ntLesionCount = ntLesion ? ntLesion.Count : 0
this.setScrollTop()
}
this.loading = false
} catch (e) {
this.loading = false
console.log(e)
}
},
setScrollTop(a) {
setTimeout(() => {
this.$nextTick(() => {
if (this.$refs.reportList) {
this.getTableHeight()
this.$refs.reportList.bodyWrapper.scrollTop = this.$refs.reportList.bodyWrapper.scrollHeight
}
})
}, 50)
},
getQuestions(questions, isNTFilterLength, lesionType, isLymphNodes) {
const arr = []
if (questions.length !== 0) {
questions.forEach((item) => {
if (this.criterionType === 21) {
if (!this.isShowDetail) {
if (!item.RowId) {
this.expandedRows.push(item.Id)
}
} else {
this.expandedRows.push(item.Id)
}
} else {
this.expandedRows.push(item.Id)
}
//
//
//
lesionType = item.LesionType
var filterArr = [7]
// 0 5
// 12678irecisit
// 0123ID4567810
// 11051106
if ((item.LesionType === 1 || item.LesionType === 2 || item.LesionType === 6 || item.LesionType === 7 || item.LesionType === 8) && isNTFilterLength) {
filterArr = [0, 1, 3, 4, 5, 6, 2, 8, 10, 7, 1105]
} 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 === 7 || this.criterionType === 17)) {
filterArr.push(1)
}
if (isLymphNodes === 0) {
filterArr.push(1)
}
if (!(filterArr.includes(item.QuestionMark))) {
const obj = item
obj.Answers = {}
if (item.RowIndex > 0) {
try {
var idx = item.Childrens.findIndex(i => i.QuestionMark === 8)
var idxLoc = item.Childrens.findIndex(i => i.QuestionMark === 10)
var state = item.Childrens.findIndex(i => i.QuestionMark === 7)
if (idx > -1) {
if (item.Childrens[idx].Answer.length > 0) {
var k = item.Childrens[idx].Answer.findIndex(v => v.Answer !== '')
var part = ''
if (obj.IsCanEditPosition) {
part = `${item.Childrens[idx].Answer[k].Answer}--${item.Childrens[idxLoc].Answer[k].Answer}`
} else {
part = `${item.Childrens[idx].Answer[k].Answer}`
}
if (item.SplitOrMergeLesionName && k > -1) {
// obj.QuestionName = `${obj.QuestionName} --${part} (Split from ${item.SplitOrMergeLesionName})`
obj.QuestionName = `${obj.QuestionName} --${part}`
var Answers = {}
if (state >= 0) {
this.visitTaskList.forEach(v => {
const o = item.Childrens[state].Answer.find(v1 => {
return v1.VisitTaskId === v.VisitTaskId
})
if (o) {
Answers[o.VisitTaskId] = this.$fd(item.Childrens[state].DictionaryCode, parseInt(o.Answer))
}
})
}
this.$set(obj, 'Answers', Answers)
// obj.QuestionName = `${obj.QuestionName} `
} else if (!item.SplitOrMergeLesionName && k > -1) {
obj.QuestionName = `${obj.QuestionName}--${part}`
const Answers = {}
if (state >= 0) {
this.visitTaskList.forEach(v => {
const o = item.Childrens[state].Answer.find(v1 => {
return v1.VisitTaskId === v.VisitTaskId
})
if (o) {
Answers[o.VisitTaskId] = this.$fd(item.Childrens[state].DictionaryCode, parseInt(o.Answer))
}
})
}
this.$set(obj, 'Answers', Answers)
// obj.QuestionName = `${obj.QuestionName} `
} else {
obj.QuestionName = `${obj.QuestionName} `
}
if (item.ReportMark) {
obj.QuestionName = `${obj.QuestionName} (${item.ReportMark})`
}
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
item.Childrens.forEach(v => {
this.$set(v, 'isLymphNodes', isLymphNodes)
})
}
}
}
}
if (this.criterionType === 21) {
var liverSegmentIdx = item.Childrens.findIndex(i => i.QuestionMark === 1106)
if (liverSegmentIdx > -1) {
if (item.Childrens[liverSegmentIdx].Answer.length > 0) {
const v = item.Childrens[liverSegmentIdx].Answer[0].Answer
obj.QuestionName = this.$fd(item.Childrens[liverSegmentIdx].DictionaryCode, parseInt(v))
const Answers = {}
const mean = item.Childrens.findIndex(i => i.QuestionMark === 1104)
if (mean > -1) {
this.visitTaskList.forEach(v => {
const o = item.Childrens[mean].Answer.find(v1 => {
return v1.VisitTaskId === v.VisitTaskId
})
if (o) {
Answers[o.VisitTaskId] = isNaN(parseFloat(o.Answer)) ? o.Answer : `${o.Answer}%`
}
})
}
this.$set(obj, 'Answers', Answers)
}
}
}
} catch (e) {
console.log(e)
}
}
item.Answer.forEach(i => {
if (item.DictionaryCode) {
obj.Answers[i.VisitTaskId] = i.Answer ? parseInt(i.Answer) : null
} else {
obj.Answers[i.VisitTaskId] = i.Answer
}
})
if (item.QuestionType === 15) {
this.currentExistDisease = obj.Answers[this.visitTaskId]
this.answerArr.push({ id: item.QuestionId, answer: obj.Answers[this.visitTaskId], questionType: 15 })
}
if (item.QuestionType === 13 || item.QuestionType === 42) {
this.currentEvaluateResult = obj.Answers[this.visitTaskId]
this.answerArr.push({ id: item.QuestionId, answer: obj.Answers[this.visitTaskId], questionType: item.QuestionType })
}
if (item.QuestionType === 14) {
this.currentTaskReason = obj.Answers[this.visitTaskId]
this.answerArr.push({ id: item.QuestionId, answer: obj.Answers[this.visitTaskId], questionType: 14 })
}
if (item.Childrens.length >= 1) {
obj.Childrens = this.getQuestions(item.Childrens, isNTFilterLength, lesionType, isLymphNodes)
}
arr.push(obj)
}
})
}
return arr
},
handleShowDetail(val) {
this.getReportInfo()
// this.taskQuestions = this.getQuestions(res.Result.TaskQuestions, !this.isShowDetail, null)
},
handleExistDiseaseChange(val) {
// this.currentExistDisease = parseInt(val)
if (val === this.isExistDisease && this.tumorEvaluate === this.currentEvaluateResult) {
this.currentTaskReason = ''
this.evaluateReasonChange('')
}
this.currentTaskReason = ''
this.evaluateReasonChange('')
var idx = this.answerArr.findIndex(i => i.questionType === 15)
if (idx > -1) {
this.answerArr[idx].answer = val
}
},
handleEvaluateResultChange(val) {
// this.currentEvaluateResult = parseInt(val)
if (val === this.tumorEvaluate && this.isExistDisease === this.currentExistDisease) {
this.currentTaskReason = ''
this.evaluateReasonChange('')
}
this.currentTaskReason = ''
this.evaluateReasonChange('')
var idx = this.answerArr.findIndex(i => i.questionType === 13 || i.questionType === 42)
if (idx > -1) {
this.answerArr[idx].answer = val
}
this.setScrollTop()
},
evaluateReasonChange(val) {
var idx = this.answerArr.findIndex(i => i.questionType === 14)
if (idx > -1) {
this.answerArr[idx].answer = val
}
},
async handleConfirm() {
this.loading = true
try {
await this.handleSave(false)
await verifyVisitTaskQuestions({ visitTaskId: this.visitTaskId })
var i = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
var isBaseline = this.visitTaskList[i].IsBaseLine
if (isBaseline) {
const res = await getTaskAdditionalQuestion({ visitTaskId: this.visitTaskId })
this.assessmentQuestions = res.Result
if (this.assessmentQuestions.length > 0) {
//
this.additionalAssessmentsDig.visible = true
} else {
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
}
} else {
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
}
this.loading = false
} catch (e) {
this.loading = false
}
},
sign() {
this.additionalAssessmentsDig.visible = false
this.$nextTick(() => {
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
})
},
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
// window.location.reload()
window.opener.postMessage('refreshTaskList', window.location)
//
this.readingTaskState = 2
this.$emit('setReadingTaskState', 2)
const res = await getAutoCutNextTask()
var 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)
})
}
window.opener.postMessage('refreshTaskList', window.location)
}
this.loading = false
} catch (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(async(resolve, reject) => {
var isBeill
var evaluateResult = ''
var evaluateAjustReason = ''
this.answers = []
var isExistEvaluateResult = false
this.answerArr.map(item => {
if (item.questionType === 13 || item.questionType === 42) {
evaluateResult = item.answer
isExistEvaluateResult = true
}
if (item.questionType === 14) {
evaluateAjustReason = item.answer
}
if (item.questionType === 15) {
isBeill = item.answer
}
this.answers.push({ id: item.id, answer: item.answer })
})
console.log(this.currentExistDisease, this.isExistDisease, evaluateAjustReason, isBeill)
if (this.currentExistDisease !== this.isExistDisease && !evaluateAjustReason) {
this.$confirm(this.$t('trials:readingReport:message:msg2'), {
type: 'warning',
showCancelButton: false,
callback: action => {}
})
resolve()
return
}
if (isExistEvaluateResult && (evaluateResult === null && !this.isBaselineTask)) {
//
this.$confirm(this.$t('trials:readingReport:message:msg2'), {
type: 'warning',
showCancelButton: false,
callback: action => {}
})
resolve()
return
}
if (isExistEvaluateResult && (evaluateResult !== this.tumorEvaluate) && !evaluateAjustReason) {
//
this.$confirm(this.$t('trials:readingReport:message:msg3'), {
type: 'warning',
showCancelButton: false,
callback: action => {}
})
resolve()
return
}
this.loading = true
try {
var params = {
visitTaskId: this.visitTaskId,
answers: this.answers
}
const res = await changeDicomReadingQuestionAnswer(params)
if (res.IsSuccess && isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.loading = false
resolve()
} catch (e) {
this.loading = false
reject()
}
})
},
getWarningText() {
var sysRes = ''
var curRes = ''
if (this.CriterionType === 2) {
sysRes = this.$fd('ImagingOverallAssessment_Lugano', this.tumorEvaluate)
curRes = this.$fd('ImagingOverallAssessment_Lugano', this.currentEvaluateResult)
} else {
sysRes = this.$fd('OverallAssessment', this.tumorEvaluate)
curRes = this.$fd('OverallAssessment', this.currentEvaluateResult)
}
if (!curRes) {
return ''
}
const msg = this.$t('trials:readingReport:message:msg9').replace('xxx', '<font color="red">' + sysRes + '</font>').replace('yyy', '<font color="red">' + curRes + '</font>')
return msg
},
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)
}
}
}
}
</script>
<style lang="scss" scoped>
.report-wrapper{
height: 100%;
padding: 10px 0px;
// 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{
display:flex;flex-direction: column;height: 100%;
background-color: #000;
color: #ffffff;
border:none;
::v-deep .el-card__body{
flex: 1;
}
}
// ::v-deep .el-table__cell{
// background-color: #000;
// color: #ffffff;
// }
// ::v-deep .el-table{
// background-color: #000;
// color: #ffffff;
// }
// ::v-deep .el-table__cell{
// background-color: #000;
// color: #ffffff;
// }
::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-switch__label{
color:#fff;
}
::v-deep .el-switch__label.is-active{
color: #428bca;
}
.colorOfRed{
color: #f66;
}
</style>
@@ -0,0 +1,333 @@
<template>
<div v-loading="loading" class="study-wrapper">
<div class="study-info">
<div
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
:title="taskInfo.SubjectCode"
>
{{ taskInfo.SubjectCode }}
</div>
<div
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
:title="visitTaskInfo.TaskBlindName"
>
{{ visitTaskInfo.TaskBlindName }}
</div>
</div>
<div class="ps">
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<template slot="title">
<div
v-if="!study.IsCriticalSequence"
class="dicom-desc"
>
<template v-if="taskInfo && taskInfo.IsShowStudyName">
<div style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="study.StudyName" :title="study.StudyName" style="margin-left: 5px;">{{ study.StudyName }}</span>
<span v-else :title="study.Modalities" style="margin-left: 5px;">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
</div>
<div v-if="study.StudyName" style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.Modalities">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
</div>
</template>
<template v-else-if="taskInfo && !taskInfo.IsShowStudyName">
<div style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span :title="study.Modalities">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
</div>
</template>
</div>
<div v-else>
<!-- 关键序列 -->
{{ $t('trials:reading:title:keySeries') }}
</div>
</template>
<div class="dicom-list-container">
<div
v-for="(series, i) in study.SeriesList"
:key="series.Id"
style="position:relative;margin-top:1px;"
@click="activeSeries(series, i, index)"
>
<div
:class="{'series-active': index === activeStudyIndex && i === activeSeriesIndex}"
class="series-wrapper"
>
<div class="series-image">
<el-image
style="width: 100%;height: 100%;"
:src="`${OSSclientConfig.basePath}${series.ImageResizePath}`"
fit="fill"
crossorigin="anonymous"
/>
</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 }}
</div>
<div v-if="series.SliceThickness && !study.IsCriticalSequence" class="text-desc">
T: {{ parseFloat(series.SliceThickness).toFixed(digitPlaces) }}
</div>
<div class="text-desc">
<span v-show="series.LoadedImageCount < series.InstanceCount">
{{ series.Modality }}: {{ series.LoadedImageCount }}/{{ series.InstanceCount }} image
</span>
<span v-show="series.LoadedImageCount >= series.InstanceCount">{{ series.Modality }}: {{ series.InstanceCount }} image</span>
</div>
<div style="line-height: 12px;">
<i v-show="series.IsBeMark || markedSeriesIds.includes(series.Id)" class="el-icon-star-on" style="font-size: 12px;color: #ff5722;" />
</div>
</div>
</div>
<div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount" style="width: 100%;">
<el-progress
:percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))"
/>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script>
export default {
name: 'StudyList',
props: {
visitTaskInfo: {
type: Object,
default() {
return {}
}
},
markedSeriesIds: {
type: Array,
default() {
return []
}
}
},
data() {
return {
loading: false,
activeNames: [],
activeStudyIndex: -1,
activeSeriesIndex: -1,
taskInfo: null,
studyList: [],
annotations: [],
digitPlaces: 2
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.studyList = this.visitTaskInfo.StudyList
this.annotations = this.visitTaskInfo.Annotations
if (this.studyList.length === 0) return
this.$nextTick(() => {
this.activeStudy(this.studyList[0].StudyId)
})
},
methods: {
activeSeries(series, seriesIndex, studyIndex) {
this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex
this.$emit('activeSeries', series)
},
activeStudy(id) {
if (this.activeNames.indexOf(id) > -1) return
this.activeNames.push(id)
},
setSeriesActive(studyIndex, seriesIndex) {
this.activeStudyIndex = studyIndex
this.activeSeriesIndex = seriesIndex
const studyId = this.studyList[studyIndex].StudyId
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)
if (i === -1) return
let newIndex = null
if (type === -1) {
newIndex = i === 0 ? i : i - 1
} else {
newIndex = i >= seriseList.length - 1 ? i : i + 1
}
const studyIndex = seriseList[newIndex].StudyIndex
const seriesIndex = seriseList[newIndex].SeriesIndex
this.setSeriesActive(studyIndex, seriesIndex)
this.activeSeries(seriseList[newIndex], seriesIndex, studyIndex)
}
}
}
</script>
<style lang="scss" scoped>
.study-wrapper{
width:100%;
height: 100%;
overflow-y: hidden;
overflow-x: hidden;
display: flex;
flex-direction: column;
.study-info {
font-size: 16px;
font-weight: bold;
color: #ddd;
padding: 5px 0px;
margin: 0;
text-align: center;
background-color: #4c4c4c;
height: 50px;
}
.dicom-desc{
font-weight: bold;
font-size: 13px;
text-align: left;
color: #d0d0d0;
padding: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ps {
flex: 1;
overflow-anchor: none;
touch-action: auto;
overflow-y: auto;
}
.series-active {
background-color: #607d8b!important;
border: 1px solid #607d8b!important;
}
::v-deep.el-progress__text{
color: #ccc;
font-size: 12px;
}
.dicom-list-container{
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.series-wrapper {
width: 100%;
padding: 5px;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
background-color: #3a3a3a;
.el-progress__text{
display: none;
}
.el-progress-bar{
padding-right:0px;
}
.series-image {
width: 50px;
height: 50px;
}
.series-text {
flex: 1;
padding-left: 5px;
color: #ddd;
position: relative;
.text-desc {
width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 12px;
line-height: 16px;
}
}
}
}
::v-deep.el-collapse{
border: none;
.el-collapse-item{
background-color: #000!important;
color: #ddd;
}
.el-collapse-item__content{
padding-bottom:0px;
background-color: #000!important;
}
.el-collapse-item__header{
background-color: #000!important;
color: #ddd;
border-bottom-color:#5a5a5a;
padding-left: 5px;
// height: 50px;
line-height: 20px;
}
}
::v-deep .el-progress-bar__inner{
transition: width 0s ease;
}
}
</style>
@@ -0,0 +1,670 @@
<template>
<div
ref="viewport"
class="viewport-wrapper"
@mouseup="sliderMouseup"
@mousemove="sliderMousemove"
@mouseleave="sliderMouseleave"
>
<div v-if="series && taskInfo" class="left-top-text">
<div
v-if="taskInfo.IsExistsClinicalData"
class="cd-info"
:title="$t('trials:reading:button:clinicalData')"
>
<svg-icon
style="cursor: pointer;"
icon-class="documentation"
class="svg-icon"
@click.stop="viewCD(series.TaskInfo.VisitTaskId)"
/>
</div>
<h2
v-if="taskInfo.IsReadingShowSubjectInfo"
class="subject-info"
>
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
</h2>
<div>Series: #{{ series.SeriesNumber }}</div>
<div v-if="series.Stack">Image: #{{ `${series.SliceIndex + 1}/${series.Stack.length}` }}</div>
<div>{{ series.Modality }}</div>
</div>
<div
v-if="series && taskInfo && taskInfo.IsReadingTaskViewInOrder === 1"
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" class="right-top-text">
<div>{{ series.Description }}</div>
</div>
<div v-if="series" class="left-bottom-text">
<div v-show="mousePosition.index.length > 0">
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}
</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)">
{{mousePosition.modalityUnit}}: {{ digitPlaces === -1 ?mousePosition.value.toFixed(3) :mousePosition.value.toFixed(digitPlaces) }}
</div>
<div v-else-if="mousePosition.value">
Density: {{ mousePosition.value }}
</div>
<div v-show="imageInfo.size">
W*H: {{ imageInfo.size }}
</div>
<div 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>
<div v-show="series.SliceThickness">Slice Thickness: {{ `${Number(series.SliceThickness).toFixed(digitPlaces)} mm` }}</div>
<div v-show="imageInfo.wwwc ">WW/WL: {{ imageInfo.wwwc }}</div>
</div>
<div class="orientation-top">
{{ markers.top }}
</div>
<div class="orientation-right">
{{ markers.right }}
</div>
<div class="orientation-bottom">
{{ markers.bottom }}
</div>
<div class="orientation-left">
{{ markers.left }}
</div>
<div ref="sliderBox" class="right-slider-box" @click.stop="clickSlider($event)">
<div :style="{top: sliderInfo.height + '%'}" class="slider" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
</div>
</div>
</template>
<script>
// import * as cornerstonejs from '@cornerstonejs/core'
import {
// RenderingEngine,
// Enums,
// imageLoader,
metaData,
getRenderingEngine,
// eventTarget,
utilities as csUtils
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
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',
props: {
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
viewportIndex: {
type: Number,
required: true
}
},
data() {
return {
element: '',
series: null,
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
}
},
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']
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_STACK_NEW_IMAGE', this.stackNewImage)
// 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)
},
stackNewImage(e) {
const { detail } = e
this.series.SliceIndex = detail.imageIdIndex
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()
this.imageInfo.zoom = zoom.toFixed(4)
this.imageInfo.size = `${detail.image.columns}*${detail.image.rows}`
const imagePlaneModule = metaData.get('imagePlaneModule', detail.imageId)
this.imageInfo.location = imagePlaneModule.sliceLocation
// this.imageInfo.wwwc = `${Math.round(detail.image.windowWidth)}/${Math.round(detail.image.windowCenter)}`
this.getOrientationMarker()
this.$emit('renderAnnotations', this.series)
// const toolGroupId = `viewport-${this.viewportIndex}`
// const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
// 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)
const viewport = renderingEngine.getViewport(this.viewportId)
const properties = viewport.getProperties()
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
lower,
upper
)
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
const zoom = viewport.getZoom()
this.imageInfo.zoom = zoom.toFixed(4)
},
getOrientationMarker() {
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()
}
},
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) {
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)
}
},
cornerstoneToolsMouseMove(e) {
const { currentPoints } = e.detail
const worldPoint = currentPoints.world
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let referencedImageId = viewport.getCurrentImageId()
const data = viewport.getImageData()
if (!data) return
const { dimensions, imageData, metadata, voxelManager } = data
const index = imageData.worldToIndex(worldPoint)
index[0] = Math.floor(index[0])
index[1] = Math.floor(index[1])
index[2] = Math.floor(index[2])
this.mousePosition.index = index
const modality = metadata.Modality
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
ijk = vec3.round(ijk, ijk);
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
this.isHandleOutsideImage = false
let value = voxelManager.getAtIJKPoint(ijk)
ijk[2] = viewport.getCurrentImageIdIndex()
let modalityUnit
if (modality === 'US') {
const calibratedResults = 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)
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
},
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;
}
}
</style>
@@ -0,0 +1,101 @@
<template>
<div class="visit-review-container">
<el-tabs v-model="activeName">
<!-- 阅片 -->
<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
v-if="taskInfo && !taskInfo.IseCRFShowInDicomReading"
:label="$t('trials:reading:tabTitle:report')"
name="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>
</el-tabs>
</div>
</template>
<script>
import ReadPage from './ReadPage'
import ReportPage from './ReportPage'
import CustomizeReportPage from './customize/ReportPage'
export default {
name: 'VisitReview',
components: {
ReadPage,
ReportPage,
CustomizeReportPage
},
props: {
readingTool: {
type: Number,
default: 0
}
},
data() {
return {
activeName: 'read',
taskInfo: null
}
},
mounted() {
console.log(this.readingTool)
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
},
methods: {
setReadingTaskState(state) {
this.$refs['readPage'].setReadingTaskState(state)
}
}
}
</script>
<style lang="scss" scoped>
.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;
.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>
@@ -0,0 +1,324 @@
<template>
<div>
<div
v-if="!!question.GroupName && question.Type==='group'"
>
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
{{ language==='en'?question.GroupEnName:question.GroupName }}
</h4>
</div>
<template v-else>
<el-form-item
v-if="(question.ShowQuestion===1 && question.ParentTriggerValueList.includes(String(questionForm[question.ParentId]))) || question.ShowQuestion===0 "
:label="`${question.QuestionName}`"
:prop="question.Id"
:rules="[
{ required: (question.IsRequired === 0 || (question.IsRequired ===1 && question.RelevanceId && (questionForm[question.RelevanceId] === question.RelevanceValue))) && question.Type!=='group' && question.Type!=='summary',
message: ['radio', 'select', 'checkbox'].includes(question.Type) ? $t('common:ruleMessage:select') : $t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
]"
:class="[question.Type==='group' ? 'mb' : (question.Type==='upload' || question.QuestionName.length > 15) ?'uploadWrapper' : '']"
>
<!-- 输入框 -->
<el-input
v-if="question.Type==='input'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
/>
<!-- 多行文本输入框 -->
<el-input
v-else-if="question.Type==='textarea'"
v-model="questionForm[question.Id]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
maxlength="500"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
/>
<!-- 下拉框 -->
<el-select
v-else-if="question.Type==='select'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode)"
clearable
@change="((val)=>{formItemChange(val, question)})"
>
<template v-if="question.TableQuestionType === 1">
<el-option
v-for="item in organList"
:key="item.Id"
:label="item[question.DataTableColumn]"
:value="item[question.DataTableColumn]"
/>
</template>
<template v-else-if="question.TableQuestionType === 3 || question.QuestionGenre === 3">
<el-option
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:value="String(item.value)"
:label="item.label"
/>
</template>
<template v-else-if="(question.TableQuestionType === 2 || question.QuestionGenre === 2) && question.DictionaryCode">
<el-option
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:value="String(item.value)"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<!-- 单选 -->
<el-radio-group
v-else-if="question.Type==='radio'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
@change="((val)=>{formItemChange(val, question)})"
>
<template v-if="question.DictionaryCode">
<el-radio
v-for="item of $d[question.DictionaryCode]"
:key="item.id"
:label="String(item.value)"
>
{{ item.label }}
</el-radio>
</template>
<template v-else-if="question.TypeValue">
<el-radio
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-radio>
</template>
</el-radio-group>
<!-- 复选框 -->
<el-checkbox-group
v-else-if="question.Type==='checkbox'"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
>
<el-checkbox
v-for="val in question.TypeValue.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-checkbox>
</el-checkbox-group>
<!-- 数值 -->
<template v-else-if="question.Type==='number'">
<el-input-number
v-if="question.ValueType === 0"
v-model="questionForm[question.Id]"
:precision="0"
:disabled="readingTaskState >= 2"
/>
<el-input-number
v-else-if="question.ValueType === 3"
v-model="questionForm[question.Id]"
:disabled="readingTaskState >= 2"
/>
<el-input-number
v-else-if="question.ValueType === 1 || question.ValueType === 2"
v-model="questionForm[question.Id]"
:precision="digitPlaces"
:disabled="readingTaskState >= 2"
/>
</template>
<el-input
v-else-if="question.Type==='calculation'"
v-model="questionForm[question.Id]"
disabled
>
<template v-if="question.Unit" slot="append">
{{ $fd('ValueUnit', parseInt(question.Unit)) }}
</template>
</el-input>
<!-- 上传图像 -->
<el-upload
v-else-if="question.Type==='upload'"
action
:accept="accept"
:limit="question.ImageCount"
:on-preview="handlePictureCardPreview"
:before-upload="handleBeforeUpload"
:http-request="uploadScreenshot"
list-type="picture-card"
:on-remove="handleRemove"
:file-list="fileList"
:class="{disabled:fileList.length >= question.ImageCount}"
:disabled="readingTaskState >= 2"
>
<i slot="default" class="el-icon-plus" />
<div slot="file" slot-scope="{file}">
<img
class="el-upload-list__item-thumbnail"
:src="OSSclientConfig.basePath + file.url"
alt=""
>
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<i class="el-icon-zoom-in" />
</span>
<span
v-if="readingTaskState < 2"
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<i class="el-icon-delete" />
</span>
</span>
</div>
</el-upload>
<el-dialog
v-if="question.Type==='upload'"
append-to-body
:visible.sync="imgVisible"
width="600px"
>
<el-image :src="imageUrl" width="100%">
<div slot="placeholder" class="image-slot">
加载中<span class="dot">...</span>
</div>
</el-image>
</el-dialog>
</el-form-item>
</template>
<question-form-item
v-for="(item) in question.Childrens"
:key="item.Id"
:question="item"
:question-form="questionForm"
:reading-task-state="readingTaskState"
:visitTaskId="visitTaskId"
@resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData"
/>
</div>
</template>
<script>
// import { uploadReadingAnswerImage } from '@/api/trials'
import { mapGetters } from 'vuex'
export default {
name: 'QuestionFormItem',
props: {
questionForm: {
type: Object,
default() {
return {}
}
},
question: {
type: Object,
default() {
return {}
}
},
readingTaskState: {
type: Number,
required: true
},
visitTaskId: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
accept: '.png,.jpg,.jpeg',
imgVisible: false,
imageUrl: '',
urls: [],
digitPlaces: 2
}
},
computed: {
...mapGetters(['language'])
},
watch: {
questionForm: {
deep: true,
immediate: true,
handler(v) {
// console.log(v)
}
}
},
mounted() {
let digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
},
methods: {
formItemChange(v, question) {
if (question.Childrens.length > 0) {
this.resetChild(question.Childrens)
} else {
this.$emit('setFormItemData', { key: question.Id, val: v })
}
},
resetChild(obj) {
obj.forEach(i => {
this.$emit('resetFormItemData', i.Id)
if (i.Childrens && i.Childrens.length > 0) {
this.resetChild(i.Childrens)
}
})
},
resetFormItemData(v) {
this.$emit('resetFormItemData', v)
},
setFormItemData(obj) {
this.$emit('setFormItemData', obj)
},
}
}
</script>
<style lang="scss" scoped>
.mb{
margin-bottom: 0px;
}
.disabled{
::v-deep .el-upload--picture-card {
display: none;
}
}
.uploadWrapper{
display: flex;
flex-direction: column !important;
align-items: flex-start;
}
::v-deep .el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-input-group__append, .el-input-group__prepend{
padding: 0 10px;
}
::v-deep .el-form-item__content {
width: 100%;
}
::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>
@@ -0,0 +1,365 @@
<template>
<div class="table-question-form">
<div style="display: flex;justify-content: space-between;">
<h3 v-if="qsForm.LesionName" style="color: #ddd;padding: 5px 0px;margin: 0;">
{{ qsForm.LesionName }}
</h3>
<!-- 关闭 -->
<div>
<i class="el-icon-circle-close" style="font-size: 25px;cursor: pointer;" @click="close" />
</div>
</div>
<el-form-item
:label="$t('trials:reading:title:lesionType')"
prop="LesionType"
:rules="[
{ required:true,message: $t('common:ruleMessage:select'), trigger: ['blur']},
]"
>
<!-- 下拉框 -->
<el-select
v-model="qsForm.LesionType"
filterable
:disabled="!isCurrentTask || readingTaskState>=2 || !isBaseLineTask"
@change="((val)=>{lesionTypeChange(val)})"
>
<el-option
v-for="item of $d.LesionType"
v-show="!(isBaseLineTask && item.value === 2)"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<!-- 靶病灶非靶病灶的器官为肝脏/Liver时,需要选择典型肝内病灶该值默认为否如果选择是对于靶病灶需要提示按照独立影像评估章程的相关规则对于典型肝内靶病灶请测量肿瘤活性部分最长径提示方式为在长径属性上显示浮框或者增加
-->
<el-form-item
v-for="qs in questions"
v-show="qs.ShowQuestion!==2"
:key="qs.Id"
:prop="qs.Id"
:rules="[
{ required: (qs.IsRequired === 0 || (qs.IsRequired ===1 && qs.RelevanceId && (questionForm[qs.RelevanceId] === qs.RelevanceValue)) || (qs.QuestionMark === 6 && questionForm.IsCanEditPosition === true) || (questionForm.IsCanEditPosition && qs.QuestionMark === 10) || (qs.QuestionMark === 21 && (questionForm.LesionOrgan === '肝脏' || questionForm.LesionOrgan === 'Liver') && (lesionType === 0 || lesionType === 1 || lesionType === 2))) && qs.Type!=='group' && qs.Type!=='summary',
message:['radio', 'select', 'checkbox'].includes(qs.Type) ? $t('common:ruleMessage:select') : $t('common:ruleMessage:specify'), trigger: ['blur','change']},
]"
>
<span slot="label" style="display:inline-block;" >
<el-tooltip v-if="qs.QuestionMark === 0 && (questionForm.LesionOrgan === '肝脏' || questionForm.LesionOrgan === 'Liver') && (lesionType === 0 || lesionType === 1 || lesionType === 2) && questionForm.IntrahepaticLesion" effect="dark" :content="$t('trials:mRecist:warnning:msg1')" placement="bottom">
<i class='el-icon-warning' />
</el-tooltip>
{{ qs.QuestionName }}
</span>
<!-- 输入框 -->
<template v-if="qs.Type==='input' || qs.Type==='number'">
<el-input
v-if="qs.Type==='input' || qs.Type==='number'"
v-model="qsForm[qs.Id]"
:disabled="!isCurrentTask || readingTaskState>=2 || qs.QuestionMark === 0 || qs.QuestionMark === 1 || qs.QuestionMark === 2 || qs.QuestionMark === 5 || (qs.QuestionMark === 6 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName))|| (qs.QuestionMark === 8 && (isCurrentTaskAdd === 'False' || !!answer.SplitOrMergeLesionName) && lesionType !== 2) || (qs.QuestionMark === 10 && (isCurrentTaskAdd === 'False' || !!answer.SplitOrMergeLesionName))"
@change="((val)=>{formItemChange(val, qs)})"
>
<template v-if="(qs.QuestionMark===0 || qs.QuestionMark===1) && qs.Unit" slot="append">
{{ $fd('ValueUnit', parseInt(qs.Unit)) }}
</template>
</el-input>
</template>
<!-- 多行文本输入框 -->
<el-input
v-if="qs.Type==='textarea'"
v-model="qsForm[qs.Id]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
:disabled="!isCurrentTask || readingTaskState>=2"
@change="((val)=>{formItemChange(val, qs)})"
/>
<!-- 下拉框 -->
<el-select
v-if="qs.Type==='select'"
v-model="qsForm[qs.Id]"
filterable
:placeholder="qs.QuestionMark === 8 ? $t('common:placeholder:selectorsearch') : qs.QuestionMark === 2 ? '' : $t('common:placeholder:select')"
:disabled="!isCurrentTask || readingTaskState>=2 || qs.QuestionMark === 0 || qs.QuestionMark === 1 || qs.QuestionMark === 2 || qs.QuestionMark === 5 || (qs.QuestionMark === 6 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName))|| (qs.QuestionMark === 8 && (isCurrentTaskAdd === 'False'|| !!answer.SplitOrMergeLesionName)) || (qs.QuestionMark === 10 && (isCurrentTaskAdd === 'False' || (isCurrentTaskAdd === 'True' && !questionForm.IsCanEditPosition) || !!answer.SplitOrMergeLesionName)) || (qs.QuestionMark === 21 && (isCurrentTaskAdd === 'False'|| !!answer.SplitOrMergeLesionName)) || (questionForm.LesionOrgan && questionForm.LesionOrgan !== '肝脏' && questionForm.LesionOrgan !== 'Liver' && qs.QuestionMark === 21)"
@change="((val)=>{formItemChange(val, qs)})"
>
<template v-if="qs.QuestionMark === 8" #prefix>
<span style="padding-left: 5px;">
<i class="el-icon-search" />
</span>
</template>
<template v-if="qs.TableQuestionType === 1">
<el-option
v-for="item in organList"
:key="item.Id"
:label="item[qs.DataTableColumn]"
:value="item[qs.DataTableColumn]"
/>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark === 7 && isBaseLineTask">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="(lesionType === 0 && item.value ===0) || (lesionType === 1 && (item.value ===0))"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark === 7 && !isBaseLineTask">
<el-option-group
:label="!isNaN(parseFloat(answer.LastTaskState)) ? `${$t('trials:dicomReading:tip:lastVisitStatus')} ${$fd(qs.DictionaryCode,parseFloat(answer.LastTaskState))}` : ''"
>
<!-- 首次分裂的病灶只能选择存在 -->
<template v-if="answer.IsFristAdd=== 'True' && answer.SplitOrMergeType === '0'">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="item.value === 0"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<!-- 首次添加的新病灶不能为无法评估和消失 -->
<template v-else-if="isCurrentTaskAdd=== 'True' && lesionType === 2">
<el-option
v-for="item of $d[qs.DictionaryCode]"
v-show="item.value === 0 || item.value === 1"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="item of filterState($d[qs.DictionaryCode])"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
</el-option-group>
</template>
<template v-else-if="qs.DictionaryCode && qs.QuestionMark !== 7">
<el-option
v-for="item of $d[qs.DictionaryCode]"
:key="item.id"
:value="item.value"
:label="item.label"
/>
</template>
<template v-else>
<el-option
v-for="val in qs.TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
</el-select>
<!-- 单选 -->
<el-radio-group
v-if="qs.Type==='radio'"
v-model="qsForm[qs.id]"
:disabled="!isCurrentTask || readingTaskState>=2"
>
<el-radio
v-for="val in qs.options.split('|')"
:key="val"
:label="val"
>
{{ val }}
</el-radio>
</el-radio-group>
</el-form-item>
</div>
</template>
<script>
export default {
name: 'TableQuestionFormItem',
props: {
tableInfo: {
type: Object,
default() {
return {}
}
},
answer: {
type: Object,
default() {
return {}
}
},
questionForm: {
type: Object,
default() {
return {}
}
},
organs: {
type: Array,
default() {
return []
}
},
readingTaskState: {
type: Number,
required: true
},
isBaseLineTask: {
type: Boolean,
required: true
},
isCurrentTask: {
type: Boolean,
required: true
}
},
data() {
return {
isCurrentTaskAdd: null,
lesionType: null,
questions: [],
organList: [],
qsForm: {}
}
},
watch: {
questionForm: {
deep: true,
immediate: true,
handler(v) {
if (v.MeasureData) {
const { markTool } = v.MeasureData
if (markTool === 'Bidirectional') {
this.getOrganList(1)
} else if (markTool === 'Length') {
this.getOrganList(0)
} else {
this.getOrganList()
}
} else {
this.getOrganList()
}
for (const key in v) {
this.$set(this.qsForm, key, v[key])
}
this.isCurrentTaskAdd = v.IsCurrentTaskAdd
}
},
tableInfo: {
deep: true,
immediate: true,
handler(v) {
if (v) {
this.lesionType = v.LesionType
this.questions = v.TableQuestions.Questions
}
}
}
},
methods: {
lesionTypeChange(v) {
this.$emit('lesionTypeChange', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex, newLesionType: v })
},
formItemChange(v, qs) {
const updateArr = []
if (qs.QuestionMark === 8 && qs.RelationQuestions.length > 0) {
//
const index = this.organList.findIndex(item => item[qs.DataTableColumn] === v)
if (index > -1) {
const selected = this.organList[index]
qs.RelationQuestions.map(q => {
const val = selected[q.DataTableColumn]
updateArr.push({ questionId: q.Id, val: val, questionMark: q.QuestionMark })
})
updateArr.push({ questionId: 'OrganInfoId', val: selected.OrganInfoId })
updateArr.push({ questionId: 'IsCanEditPosition', val: selected.IsCanEditPosition })
} else {
qs.RelationQuestions.map(q => {
updateArr.push({ questionId: q.Id, val: '', questionMark: q.QuestionMark })
})
}
}
this.$emit('update', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex, questionMark: qs.QuestionMark, questionId: qs.Id, val: v, updateArr })
},
getOrganList(isLymphNodes = null) {
const idx = this.organs.findIndex(i => i.LesionType === this.questionForm.LesionType)
if (idx > -1) {
const arr = this.organs[idx].OrganList
if (!isNaN(parseInt(isLymphNodes))) {
this.organList = arr.filter((item) => item.IsLymphNodes === parseInt(isLymphNodes))
} else {
this.organList = arr
}
}
},
filterState(arr) {
if (!this.isBaseLineTask) {
const isLymphLesion = this.questionForm.IsLymphNodes
const lesionLength = this.questionForm.LesionLength
const lesionShort = this.questionForm.LesionShort
const bLesionL = !isNaN(parseFloat(this.questionForm.BaseLineMajorAxis)) ? parseFloat(this.questionForm.BaseLineMajorAxis) : 0
const bLesionS = !isNaN(parseFloat(this.questionForm.BaseLineShortAxis)) ? parseFloat(this.questionForm.BaseLineShortAxis) : 0
if (this.lesionType === 1 && isLymphLesion === 1 && this.questionForm.MeasureData && this.questionForm.MeasureData.markTool === 'Bidirectional' && lesionShort < bLesionS) {
arr = arr.filter(i => i.value !== 1)
} else if (this.lesionType === 1 && isLymphLesion === 1 && this.questionForm.MeasureData && this.questionForm.MeasureData.markTool === 'Bidirectional' && lesionShort >= 10 && lesionShort > bLesionS) {
arr = arr.filter(i => i.value === 0 || i.value === 1)
} else if (this.lesionType === 1 && isLymphLesion === 0 && this.questionForm.MeasureData && (this.questionForm.MeasureData.markTool === 'Length' || this.questionForm.MeasureData.markTool === 'Bidirectional') && lesionLength < bLesionL) {
arr = arr.filter(i => i.value !== 1)
}
return arr
} else {
return arr
}
},
close() {
this.$emit('close', { tableId: this.tableInfo.Id, rowIndex: this.answer.RowIndex })
}
}
}
</script>
<style lang="scss" scoped>
.table-question-form{
::v-deep .el-form-item__label{
color: #c3c3c3;
}
::v-deep .el-input .el-input__inner{
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
::v-deep .el-form-item{
display: flex;
flex-direction: row;
justify-content: flex-start;
}
::v-deep .el-form-item__content{
flex: 1;
}
::v-deep .el-input.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-select.is-disabled .el-input__inner{
background-color: #646464a1;
}
::v-deep .el-button--mini, .el-button--mini.is-round {
padding: 7px 10px;
}
.el-form-item__content
.el-select{
width: 100%;
}
.input-width1{
width: calc(100% - 60px)!important;
}
.input-width2{
width: 100% !important;
}
}
</style>
@@ -0,0 +1,283 @@
const config = {
'standards': [
{
'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': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'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': ''
},
{
'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': ''
}
]
},
{
'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': 'rectangle',
'toolName': 'RectangleROI',
'i18nKey': 'trials:reading:button:rectangle',
'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': ''
},
{
'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': ''
}
]
},
{
'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': ''
}
]
},
{
'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': 'arrow',
'toolName': 'ArrowAnnotate',
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
'type': 19,
'name': 'IVUS定量评估',
'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': ''
}
]
}
],
'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 || []
}
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;
@@ -0,0 +1,113 @@
<template>
<div v-loading="loading" class="reading-viewer-container">
<!-- 访视阅片 -->
<visit-review
v-if="taskInfo && taskInfo.ReadingCategory=== 1"
/>
<!-- 临床数据 -->
<el-dialog
:visible.sync="clinicalDataVisible"
:custom-class="isClinicalDataFullscreen?'full-dialog-container':'dialog-container'"
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isClinicalDataFullscreen"
>
<span slot="title" class="dialog-footer">
<!-- 当前阅片任务存在临床数据请查看若已查看请点击确认 -->
<span v-if="!closeCDVisible">{{ $t('trials:reading:dagTitle:msg1') }}</span>
<div style="position: absolute;right: 20px;top: 10px;">
<svg-icon :icon-class="isClinicalDataFullscreen?'exit-fullscreen':'fullscreen'" style="cursor: pointer;font-size: 20px;" @click="isClinicalDataFullscreen=!isClinicalDataFullscreen" />
<svg-icon v-if="closeCDVisible" icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;" @click="clinicalDataVisible = false" />
</div>
</span>
<div style="height: 100%;margin:0;display: flex;flex-direction: column;">
<clinical-data
v-if="clinicalDataVisible"
style="flex: 1"
:trial-id="trialId"
:subject-id="taskInfo.SubjectId"
:visit-task-id="cdVisitTaskId"
:is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
/>
<div v-if="!closeCDVisible" style="text-align:right">
<el-button type="primary" @click="handleConfirmCD">{{ $t('trials:reading:button:confirm') }}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import VisitReview from './components/VisitReview'
import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
export default {
name:'Dicoms3d',
components: { VisitReview,ClinicalData },
data() {
return {
taskInfo: null,
trialId: '',
loading: false,
clinicalDataVisible: false,
isClinicalDataFullscreen: false,
closeCDVisible: false,
cdVisitTaskId: ''
}
},
mounted() {
this.trialId = this.$route.query.trialId
this.getTaskInfo()
},
methods: {
async getTaskInfo() {
this.loading = true
try {
const params = {
subjectId: this.$route.query.subjectId,
trialId: this.$route.query.trialId,
subjectCode: this.$route.query.subjectCode,
visitTaskId: this.$route.query.visitTaskId,
trialReadingCriterionId: this.$route.query.TrialReadingCriterionId
}
const res = await getNextTask(params)
this.taskInfo = res.Result
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
localStorage.setItem('digitPlaces', JSON.stringify(res.Result.DigitPlaces))
this.loading = false
this.$nextTick(() => {
if (this.taskInfo.IsExistsClinicalData && this.taskInfo.IsNeedReadClinicalData && !this.taskInfo.IsReadClinicalData) {
this.isClinicalDataFullscreen = false
this.clinicalDataVisible = true
this.cdVisitTaskId = this.taskInfo.VisitTaskId
}
})
} catch (e) {
console.log(e)
this.loading = false
}
},
}
}
</script>
<style lang="scss" scoped>
.reading-viewer-container {
width: 100%;
height: 100%;
// ::v-deep .dialog-container{
// margin-top: 50px !important;
// width:75%;
// height:80%;
// }
// ::v-deep .el-dialog__body{
// padding: 10px;
// height: calc(100% - 70px);
// }
// .el-dialog__header{
// position: relative;
// }
// .full-dialog-container{
// ::v-deep .is-fullscreen .el-dialog__body{
// height: calc(100% - 70px);
// }
// }
}
</style>
@@ -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}`
@@ -317,8 +317,9 @@ export default {
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -347,22 +348,23 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
reject(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},
@@ -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}`
@@ -373,9 +373,9 @@ export default {
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -404,22 +404,24 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},
getBeforeAnswer(qsId, row) {
@@ -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}`
@@ -377,8 +377,9 @@ export default {
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -407,22 +408,23 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},
@@ -0,0 +1,481 @@
<template>
<el-form v-if="globalForm.taskList.length > 0" ref="globalRuleForm" :model="globalForm" class="global-form">
<el-table
v-loading="loading"
:data="globalForm.taskList"
>
<!-- 访视名称 -->
<el-table-column
prop="BlindName"
:label="$t('trials:globalReview:table:visitName')"
show-overflow-tooltip
width="150"
/>
<!-- 评估结果 -->
<el-table-column
:label="$t('trials:globalReview:table:evaluationRes')"
align="center"
prop=""
>
<template>
<el-table-column
v-for="(qs,index) in globalInfo.evaluationQsList"
:key="index"
prop=""
:label="qs"
show-overflow-tooltip
width="150"
>
<!-- <template slot-scope="scope">
<div v-if="scope.row.BeforeQuestionList.length>index && scope.row.BeforeQuestionList[index].Answer" :style="{color: scope.row.BeforeQuestionList[index].IsGlobalAnswer ? '#f66' : null}">
<span v-if="scope.row.BeforeQuestionList[index].DictionaryCode">
{{ $fd(scope.row.BeforeQuestionList[index].DictionaryCode,parseInt(scope.row.BeforeQuestionList[index].Answer)) }}
</span>
<span v-else>{{ scope.row.BeforeQuestionList[index].Answer }}</span>
</div>
</template> -->
<template slot-scope="scope">
<template v-if="(scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===1 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===5)) || (!scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===2 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===6)) || (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===0 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===4)">
<div v-if="scope.row.BeforeQuestionList.length>index && scope.row.BeforeQuestionList[index].Answer" :style="{color: scope.row.BeforeQuestionList[index].IsGlobalAnswer ? '#f66' : null}">
<span v-if="scope.row.BeforeQuestionList[index].DictionaryCode">
{{ $fd(scope.row.BeforeQuestionList[index].DictionaryCode,parseInt(scope.row.BeforeQuestionList[index].Answer)) }}
</span>
<span v-else>{{ scope.row.BeforeQuestionList[index].Answer }}</span>
</div>
</template>
</template>
</el-table-column>
</template>
</el-table-column>
<!-- 是否同意访视结果 -->
<el-table-column
prop=""
:label="$t('trials:globalReview:table:isAgreeEvaluationRes')"
show-overflow-tooltip
width="170"
>
<template slot-scope="scope">
<el-form-item
v-if="readingTaskState<2"
:prop="`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`"
label=""
:rules="[
{ required: true,message: $t('common:ruleMessage:select'), trigger: ['change','blur']},
]"
class="form-item"
>
<el-radio-group
v-model="globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]"
@change="handleAgreeOrNotChange(scope.$index,scope.row.AgreeOrNot[0].GlobalAnswerType)"
>
<el-radio
v-for="item of $d.ReadingYesOrNo"
:key="'AgreeOrNot' + item.value"
:label="String(item.value)"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 1" type="primary">
{{ $fd('ReadingYesOrNo',parseInt(scope.row.AgreeOrNot[0].Answer)) }}
</el-tag>
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 0" type="danger"> {{ $fd('ReadingYesOrNo',parseInt(scope.row.AgreeOrNot[0].Answer)) }}</el-tag>
<span v-else />
</template>
</el-table-column>
<!-- 调整后结果 -->
<el-table-column
:label="$t('trials:globalReview:table:adjustedRes')"
align="center"
prop=""
>
<template>
<el-table-column
v-for="(qs,index) in globalInfo.adjustedQsList"
v-if="qs.isShow"
:key="index"
prop=""
:label="qs.questionName"
show-overflow-tooltip
:min-width="index === 3 ? '200' : '200'"
>
<template slot-scope="scope">
<div v-if="readingTaskState<2 && (scope.row.AfterQuestionList[index].GlobalReadingShowType === 0 || (scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 1) || (!scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 2))">
<!-- <span v-if="(scope.row.IsBaseLine && scope.row.AfterQuestionList[index].LimitEdit === 2) || (!scope.row.IsBaseLine && scope.row.AfterQuestionList[index].LimitEdit === 1)">
{{ $fd(scope.row.AfterQuestionList[index].DictionaryCode, parseInt(scope.row.AfterQuestionList[index].VisitAnswer)) }}
</span> -->
<el-form-item
style="margin-bottom: 0;"
:prop="`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:scope.row.AfterQuestionList[index].GlobalAnswerType}`"
label=""
:rules="[
{ required:parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) === 0,message: $t('common:ruleMessage:specify'), trigger: ['change','blur']},
]"
>
<label v-if="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) === 0" />
<!-- 裁判问题 -->
<template v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 0 ">
<el-tooltip v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])" class="item" effect="dark" :content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])" placement="top-start">
<el-select
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<template v-if="scope.row.AfterQuestionList[index].TypeValue">
<el-option
v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
:key="item.id"
:value="String(item.value)"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
:label="item.label"
/>
</template>
</el-select>
</el-tooltip>
<el-select
v-else
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<template v-if="scope.row.AfterQuestionList[index].TypeValue">
<el-option
v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<template v-if="getLesionCount(scope.row.LesionCountList,0)">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1 && item.value !== 1 && item.value !== 3"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
</template>
<template v-else-if="getLesionCount(scope.row.LesionCountList,1)">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1 && item.value !== 1 && item.value !== 6"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
</template>
<template v-else>
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value === 1 || item.value === 2 || item.value === 4"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
</template>
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== 6"
:key="item.id"
:value="String(item.value)"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
:label="item.label"
/>
</template>
</el-select>
</template>
<!-- 评估更新类型 GlobalAnswerType:3 -->
<template v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 3">
<el-tooltip v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)" class="item" effect="dark" :content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)" placement="top-start">
<el-select
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<el-option
v-for="val in globalInfo.assessTypeList"
v-show="(scope.row.IsBaseLine && val.IsBaseLineUse) || (!scope.row.IsBaseLine && val.IsFollowVisitUse)"
:key="val.Code"
:label="language === 'en'?val.Value:val.ValueCN"
:value="val.Code"
/>
</el-select>
</el-tooltip>
<el-select
v-else
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<el-option
v-for="val in globalInfo.assessTypeList"
v-show="(scope.row.IsBaseLine && val.IsBaseLineUse) || (!scope.row.IsBaseLine && val.IsFollowVisitUse)"
:key="val.Code"
:label="language === 'en'?val.Value:val.ValueCN"
:value="val.Code"
/>
</el-select>
</template>
<el-input
v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 1"
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
type="textarea"
maxlength="100"
show-word-limit
style="width:90%;"
:autosize="{ minRows: 2 }"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
/>
</el-form-item>
</div>
<div v-else>
<span v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 3">
{{ getAssessType(scope.row.AfterQuestionList[index].Answer) }}
</span>
<span v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
{{ $fd(scope.row.AfterQuestionList[index].DictionaryCode,parseInt(scope.row.AfterQuestionList[index].Answer)) }}
</span>
<span v-else>{{ scope.row.AfterQuestionList[index].Answer }}</span>
</div>
</template>
</el-table-column>
</template>
</el-table-column>
<el-table-column
:label="$t('common:action:action')"
width="100"
>
<template slot-scope="scope">
<el-button
circle
:title="$t('trials:globalReview:table:view')"
icon="el-icon-view"
@click="handleView(scope.row)"
/>
</template>
</el-table-column>
</el-table>
</el-form>
</template>
<script>
import { getToken } from '@/utils/auth'
import { batchSubmitGlobalReadingInfo } from '@/api/trials'
import { mapGetters } from 'vuex'
export default {
props: {
globalInfo: {
type: Object,
default() {
return {}
}
},
globalForm: {
type: Object,
default() {
return {}
}
},
readingTaskState: {
type: Number,
required: true
}
},
data() {
return {
loading: false,
assessTypes: []
}
},
computed: {
...mapGetters(['language'])
},
mounted() {
this.assessTypes = this.globalInfo.assessTypeList.filter(i => i.IsBaseLineUse === this.isBaseline)
},
methods: {
getText(val, row, type) {
if (type === 3) {
var o = this.globalInfo.assessTypeList.find(v => {
return parseInt(v.Code) === parseInt(val)
})
if (!o) {
return ''
}
return this.language === 'en' ? o.Value : o.ValueCN
} else if (row.TypeValue) {
return val
} else if (row.DictionaryCode) {
return this.$fd(row.DictionaryCode, parseInt(val))
}
},
handleView(row) {
var token = getToken()
var visitTaskId = row.VisitTaskId
var criterionType = this.$router.currentRoute.query.criterionType
var readingTool = this.$router.currentRoute.query.readingTool
readingTool = isNaN(parseInt(readingTool)) ? null : parseInt(readingTool)
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
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}`
}
const routeData = this.$router.resolve({ path })
var newWindow = window.open(routeData.href, '_blank')
this.$emit('setOpenWindow', newWindow)
},
getLesionCount(lesionList, lesionType) {
const lesion = lesionList.find(i => i.LesionType === lesionType)
return lesion ? lesion.Count : 0
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
var answerList = []
item.AfterQuestionList.map(i => {
var obj = {}
if (i.QuestionId) {
obj.questionId = i.QuestionId
obj.globalAnswerType = i.GlobalAnswerType
obj.answer = this.globalForm[ `${index}${i.QuestionId}`]
} else {
obj.questionId = ''
obj.globalAnswerType = i.GlobalAnswerType
obj.answer = this.globalForm[ `${index}${i.GlobalAnswerType}`]
}
answerList.push(obj)
})
answerList.push({ questionId: '', globalAnswerType: item.AgreeOrNot[0].GlobalAnswerType,
answer: this.globalForm[ `${index}${item.AgreeOrNot[0].GlobalAnswerType}`] })
visitTaskAnswerList.push({ visitTaskId: item.VisitTaskId, answerList: answerList })
})
var params = {
globalTaskId: this.globalInfo.visitTaskId,
subjectId: this.globalInfo.subjectId,
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},
getBeforeAnswer(qsId, row) {
var i = row.BeforeQuestionList.findIndex(item => item.QuestionName === qsId)
if (i > -1 && row.BeforeQuestionList[i].Answer) {
var answer = ''
if (row.BeforeQuestionList[i].DictionaryCode) {
answer = row.BeforeQuestionList[i].Answer
} else {
answer = row.BeforeQuestionList[i].Answer
}
return answer
} else {
return ''
}
},
handleAgreeOrNotChange(index, globalAnswerType) {
var agreeOrNot = parseInt(this.globalForm[`${index}${globalAnswerType}`])
if (agreeOrNot === 1) {
var qsList = this.globalForm.taskList[index].AfterQuestionList
qsList.map(v => {
if (v.QuestionId) {
this.globalForm[`${index}${v.QuestionId}`] = ''
} else if (v.GlobalAnswerType !== globalAnswerType) {
this.globalForm[`${index}${v.GlobalAnswerType}`] = ''
}
})
}
console.log(this.globalForm)
},
getAssessType(v) {
console.log(this.language)
var i = this.globalInfo.assessTypeList.findIndex(i => String(i.Code) === String(v))
if (i > -1) {
return this.language === 'en' ? this.globalInfo.assessTypeList[i].Value : this.globalInfo.assessTypeList[i].ValueCN
} else {
return ''
}
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-form-item label:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
::v-deep .el-textarea .el-input__count{
background: rgba(0,0,0,0);
line-height: normal;
}
::v-deep .el-form-item{
margin-bottom: 0px;
}
.global-form{
::v-deep .el-form-item__content{
padding-bottom: 10px;
}
::v-deep .form-item .el-form-item__error{
top: 60%;
}
}
</style>
@@ -220,8 +220,9 @@ export default {
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -247,21 +248,24 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
}
}
@@ -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}`
@@ -369,9 +369,10 @@ export default {
return lesion ? lesion.Count : 0
},
handleSave(isPrompt = true) {
return new Promise(async(resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
return new Promise(async (resolve, reject) => {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -400,22 +401,24 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},
getBeforeAnswer(qsId, row) {
@@ -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}`
@@ -364,8 +364,9 @@ export default {
},
handleSave(isPrompt = true) {
return new Promise(async (resolve, reject) => {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
try {
let valid = await this.$refs['globalRuleForm'].validate()
if (valid) {
this.loading = true
var visitTaskAnswerList = []
this.globalForm.taskList.forEach((item, index) => {
@@ -394,22 +395,23 @@ export default {
trialId: this.globalInfo.trialId,
visitTaskAnswerList
}
try {
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
console.log(isPrompt)
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve()
} catch (e) {
this.loading = false
reject()
await batchSubmitGlobalReadingInfo(params)
this.loading = false
if (isPrompt) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
this.$emit('getGlInfo')
resolve(true)
} else {
reject()
resolve(false)
}
} catch(e) {
console.log(e)
this.loading = false
resolve(false)
}
})
},

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