151 Commits

Author SHA1 Message Date
wangxiaoshuang 474c0dca70 医学审核问题配置查询条件可清除
continuous-integration/drone/push Build encountered an error
2025-08-13 14:35:29 +08:00
wangxiaoshuang a7806d07bb 自定义标准(旧)报告数值问题默认值修改
continuous-integration/drone/push Build is passing
2025-08-12 17:55:37 +08:00
wangxiaoshuang f6e87596ee 1
continuous-integration/drone/push Build is passing
2025-08-12 17:18:55 +08:00
wangxiaoshuang d1448d4cc6 质控下载工具
continuous-integration/drone/push Build is passing
2025-08-12 14:44:52 +08:00
wangxiaoshuang bf985bcbc8 添加工具文件上传
continuous-integration/drone/push Build is passing
2025-08-12 14:26:22 +08:00
wangxiaoshuang 70db0d9258 简历部分信息修改
continuous-integration/drone/push Build is passing
2025-08-12 09:47:45 +08:00
wangxiaoshuang 8f1ab432b2 自定义表格问题数值下拉框自动计算修改
continuous-integration/drone/push Build is passing
2025-08-12 09:20:53 +08:00
wangxiaoshuang 3f0573eeb3 简历日期添加修改
continuous-integration/drone/push Build is passing
2025-08-11 16:59:53 +08:00
wangxiaoshuang 2ed051e25b 下载文件转换
continuous-integration/drone/push Build is passing
2025-08-11 16:06:48 +08:00
wangxiaoshuang dde53103f9 自定义提示信息修改
continuous-integration/drone/push Build is passing
2025-08-11 15:50:37 +08:00
wangxiaoshuang 042a5fe59b 表单必填项校验修改
continuous-integration/drone/push Build is passing
2025-08-11 15:37:09 +08:00
wangxiaoshuang 8dda50f78e 影像汇总dicom下载路径修改
continuous-integration/drone/push Build is passing
2025-08-11 13:46:40 +08:00
wangxiaoshuang 7c06c94177 1
continuous-integration/drone/push Build is passing
2025-08-08 17:34:56 +08:00
wangxiaoshuang 42652cd9ee 后处理下载处理
continuous-integration/drone/push Build is passing
2025-08-08 17:30:20 +08:00
wangxiaoshuang 2370c25ed7 质控下载修改
continuous-integration/drone/push Build is passing
2025-08-08 17:08:26 +08:00
wangxiaoshuang cdefe6d640 1
continuous-integration/drone/push Build is passing
2025-08-08 14:11:35 +08:00
wangxiaoshuang 9ef348efbf 1
continuous-integration/drone/push Build is passing
2025-08-07 17:42:39 +08:00
wangxiaoshuang 06e876619f 影像汇总新增阅片影像大小字段、导出阅片影像功能
continuous-integration/drone/push Build is passing
2025-08-07 15:51:56 +08:00
wangxiaoshuang 3334e24c66 上传影像添加字段
continuous-integration/drone/push Build is passing
2025-08-07 15:24:34 +08:00
wangxiaoshuang 9b3e169ef2 阅片单元外层问题添加配置排除访视显示
continuous-integration/drone/push Build is passing
2025-08-07 13:53:49 +08:00
wangxiaoshuang fa30c68153 阅片页面提交时自动保存
continuous-integration/drone/push Build is passing
2025-08-06 15:33:21 +08:00
wangxiaoshuang e08f094435 质控下载影像按钮不与复核状态相关联
continuous-integration/drone/push Build is passing
2025-08-06 15:05:17 +08:00
wangxiaoshuang d047df807e 1
continuous-integration/drone/push Build is passing
2025-08-06 14:18:33 +08:00
wangxiaoshuang 2c5e33903d 邮箱校验规则默认值问题
continuous-integration/drone/push Build is passing
2025-08-06 13:52:14 +08:00
wangxiaoshuang b024252f98 1
continuous-integration/drone/push Build is passing
2025-08-06 10:18:23 +08:00
wangxiaoshuang c1be3bbf4d 稽查修改
continuous-integration/drone/push Build is passing
2025-08-06 09:54:50 +08:00
wangxiaoshuang f2b07131aa 部分页面增加排序
continuous-integration/drone/push Build is passing
2025-08-05 17:17:15 +08:00
wangxiaoshuang 6c71803a11 影像指控添加导出质控问题功能
continuous-integration/drone/push Build is passing
2025-08-05 14:07:10 +08:00
wangxiaoshuang 91078abd15 影像统计小于等于0时默认赋值为0
continuous-integration/drone/push Build is passing
2025-08-05 10:34:00 +08:00
wangxiaoshuang 04f8a938c3 阅片报告预览其他访视时切换关闭问题
continuous-integration/drone/push Build is passing
2025-08-04 17:53:45 +08:00
wangxiaoshuang 2c85013008 部分问题修复
continuous-integration/drone/push Build is passing
2025-08-04 17:39:51 +08:00
wangxiaoshuang 9d839e46cd 标记保存名称未填写校验
continuous-integration/drone/push Build is passing
2025-08-04 15:10:14 +08:00
wangxiaoshuang 509f8f02b6 已阅片后阅片页面可添加临时标记
continuous-integration/drone/push Build is passing
2025-08-04 14:51:34 +08:00
wangxiaoshuang 9cc1962b3b 1
continuous-integration/drone/push Build is passing
2025-08-04 14:01:47 +08:00
wangxiaoshuang 774ab78a7c CDISC导出配置修改
continuous-integration/drone/push Build is running
2025-08-04 14:00:30 +08:00
wangxiaoshuang 16794dfe1d 医学审核问题导表
continuous-integration/drone/push Build is passing
2025-08-04 13:49:49 +08:00
wangxiaoshuang 1c69ed08cd 1
continuous-integration/drone/push Build is passing
2025-08-04 13:37:01 +08:00
wangxiaoshuang 833324e1ac 全局替换IRC规则修改
continuous-integration/drone/push Build is passing
2025-08-04 13:14:42 +08:00
wangxiaoshuang f36a00cf81 部分问题修复
continuous-integration/drone/push Build is passing
2025-08-04 11:46:52 +08:00
wangxiaoshuang 233bcbb1d0 培训文件限制只能上传pdf
continuous-integration/drone/push Build is passing
2025-08-01 17:02:55 +08:00
wangxiaoshuang b644098203 阅片导入接口修改
continuous-integration/drone/push Build is passing
2025-08-01 16:57:02 +08:00
wangxiaoshuang 424a59c4d9 质控质疑提交后刷新外部列表
continuous-integration/drone/push Build is passing
2025-08-01 16:20:36 +08:00
wangxiaoshuang a3e3db3d5b 复核任务自动跳转下一个任务功能开启
continuous-integration/drone/push Build is passing
2025-08-01 11:31:52 +08:00
wangxiaoshuang a803ee2c00 1
continuous-integration/drone/push Build is passing
2025-07-31 16:16:43 +08:00
wangxiaoshuang 5a8ec13c2f 在影像质控完成后,可以更正影像检查的基本信息,并且需要签名
continuous-integration/drone/push Build is passing
2025-07-31 16:09:44 +08:00
wangxiaoshuang d911d55129 【一致性分析】ir申请重阅一致性分析任务后,pm在重阅跟踪看到该任务的受试者编号为原始编号而不是虚拟编号
continuous-integration/drone/push Build is passing
2025-07-31 14:46:04 +08:00
wangxiaoshuang ebedae85e7 稽查附件展示
continuous-integration/drone/push Build is passing
2025-07-31 14:33:44 +08:00
wangxiaoshuang d54dfae99f 1
continuous-integration/drone/push Build is passing
2025-07-31 09:31:41 +08:00
wangxiaoshuang 00396f3c9d 问题测量时点击取消清除标记
continuous-integration/drone/push Build is passing
2025-07-30 16:02:55 +08:00
wangxiaoshuang 2138bd3345 自定义标记名称保存提示信息位置更改
continuous-integration/drone/push Build is passing
2025-07-30 15:59:45 +08:00
wangxiaoshuang 0416b1bca8 1
continuous-integration/drone/push Build is passing
2025-07-30 15:37:09 +08:00
wangxiaoshuang 6dd579e910 复核质控去除重传功能
continuous-integration/drone/push Build is passing
2025-07-30 14:26:15 +08:00
wangxiaoshuang 98dc6beba5 自定义外层问题上传报错
continuous-integration/drone/push Build is passing
2025-07-30 13:56:38 +08:00
wangxiaoshuang f7a62b2016 自定义dicom增加自由曲线工具
continuous-integration/drone/push Build is passing
2025-07-30 11:48:58 +08:00
wangxiaoshuang eb50f24204 画标记增加提示语
continuous-integration/drone/push Build is passing
2025-07-30 11:45:02 +08:00
wangxiaoshuang 3c4cce70c5 用户列表增加用户参与项目的统计数字
continuous-integration/drone/push Build is passing
2025-07-30 11:28:34 +08:00
wangxiaoshuang a566c1a0b8 重传审批新增导出功能
continuous-integration/drone/push Build is passing
2025-07-30 11:24:17 +08:00
wangxiaoshuang 3bdd567f1e Merge branch 'uat'
continuous-integration/drone/push Build is passing
2025-07-30 09:20:13 +08:00
wangxiaoshuang cbbbb227ea 1
continuous-integration/drone/push Build encountered an error
2025-07-29 17:33:18 +08:00
wangxiaoshuang 91dc08493b 项目邮件配置开放系统标准修改
continuous-integration/drone/push Build is passing
2025-07-29 14:52:00 +08:00
wangxiaoshuang bacdbd9fa1 1
continuous-integration/drone/push Build is passing
2025-07-29 14:42:45 +08:00
wangxiaoshuang 3d096f397f DICOM影像上传过程中,对于匿名字段,只有文件存在时才需要匿名,如果文件本身不存在需要匿名DICOM元素,则不需要做匿名字段
continuous-integration/drone/push Build is running
2025-07-29 14:40:43 +08:00
wangxiaoshuang 4f7a308cf1 项目邮件配置查询参数变更
continuous-integration/drone/push Build is passing
2025-07-29 12:10:15 +08:00
wangxiaoshuang bb3e3e52a5 阅片相关文件上传路径修改
continuous-integration/drone/push Build is passing
2025-07-29 11:54:22 +08:00
wangxiaoshuang f918004dda 影像上传终止状态显示位置修改
continuous-integration/drone/push Build is passing
2025-07-29 11:40:05 +08:00
wangxiaoshuang fd683d74f9 对于通过DICOM AE推送的影像支持按检查查看
continuous-integration/drone/push Build is passing
2025-07-28 14:31:50 +08:00
wangxiaoshuang d7838051e8 质控通过刷新页面
continuous-integration/drone/push Build is passing
2025-07-25 16:18:34 +08:00
wangxiaoshuang b727d6b068 1
continuous-integration/drone/push Build is passing
2025-07-25 16:04:20 +08:00
wangxiaoshuang 3b14944671 邮箱校验修改
continuous-integration/drone/push Build is passing
2025-07-25 15:55:23 +08:00
wangxiaoshuang 2b820a114b 稽查增加配置‘是否修改前展示’
continuous-integration/drone/push Build is passing
2025-07-25 15:27:27 +08:00
wangxiaoshuang 3f69434251 1
continuous-integration/drone/push Build is passing
2025-07-25 15:04:10 +08:00
wangxiaoshuang ee545589af 邮箱校验修改
continuous-integration/drone/push Build is passing
2025-07-25 14:57:18 +08:00
wangxiaoshuang feed991971 非dicom上传下载
continuous-integration/drone/push Build is passing
2025-07-25 14:52:59 +08:00
wangxiaoshuang a56b536e95 数组图片稽查校验修改
continuous-integration/drone/push Build is passing
2025-07-25 14:42:03 +08:00
wangxiaoshuang eaf856a755 【后处理上传】当阅片单元配置关闭报告页时,在阅片页的提交按钮没有校验后处理上传
continuous-integration/drone/push Build is passing
2025-07-25 11:17:40 +08:00
wangxiaoshuang 8f497ed363 Merge branch 'uat'
continuous-integration/drone/push Build is passing
2025-07-25 10:18:14 +08:00
wangxiaoshuang c33c2d6a09 【非dicom工具】更多中的工具画出的标记应该为临时标记
continuous-integration/drone/push Build is passing
2025-07-25 10:05:49 +08:00
wangxiaoshuang cc788b9833 1
continuous-integration/drone/push Build encountered an error
2025-07-24 17:48:00 +08:00
wangxiaoshuang 0aaf2f3edb tableQuestionId为空时增加判断
continuous-integration/drone/push Build encountered an error
2025-07-24 16:41:00 +08:00
wangxiaoshuang 2a80893932 1
continuous-integration/drone/push Build is passing
2025-07-24 15:58:45 +08:00
wangxiaoshuang 6a0859aaab 影像质控的自动任务需要增加开关和任务分组
continuous-integration/drone/push Build is passing
2025-07-24 15:44:55 +08:00
wangxiaoshuang 690b29c5c5 影像查看工具中,增加受试者、访视信息
continuous-integration/drone/push Build is passing
2025-07-24 15:20:13 +08:00
wangxiaoshuang 0d3cb15806 1
continuous-integration/drone/push Build is passing
2025-07-24 11:28:54 +08:00
wangxiaoshuang 4b32c290e7 复核通过时验证是否有历史质疑
continuous-integration/drone/push Build is passing
2025-07-24 09:52:20 +08:00
wangxiaoshuang 390df569c9 邮箱校验规则从后端获取
continuous-integration/drone/push Build is passing
2025-07-24 09:45:36 +08:00
wangxiaoshuang 05c6e9727f 稽查配置children上下移动
continuous-integration/drone/push Build is passing
2025-07-23 18:04:24 +08:00
wangxiaoshuang 87eac9f478 Merge branch 'uat'
continuous-integration/drone/push Build is passing
2025-07-23 17:47:05 +08:00
wangxiaoshuang ef489552ea 1
continuous-integration/drone/push Build encountered an error
2025-07-23 17:41:18 +08:00
wangxiaoshuang dd80abed02 1
continuous-integration/drone/push Build is running
2025-07-23 14:31:22 +08:00
wangxiaoshuang 6ce18843ee 稽查展示新增数组图片分类
continuous-integration/drone/push Build is running
2025-07-23 14:30:13 +08:00
wangxiaoshuang fb48b59587 稽查配置新增数组图片
continuous-integration/drone/push Build is passing
2025-07-23 14:20:10 +08:00
wangxiaoshuang ed2b75d331 稽查阅片结果token获取 2025-07-23 14:19:40 +08:00
wangxiaoshuang 0c040719b0 复核按钮添加置灰条件
continuous-integration/drone/push Build encountered an error
2025-07-23 13:48:45 +08:00
wangxiaoshuang 5dc5213202 标记问题移除绑定标记后标记变更可以保存
continuous-integration/drone/push Build is passing
2025-07-22 17:40:02 +08:00
wangxiaoshuang bdfef06b5f 复核图标变更
continuous-integration/drone/push Build encountered an error
2025-07-22 17:20:19 +08:00
wangxiaoshuang 1d009c8e18 非DICOM工具的测量工具,能否也是和DICOM的测量工具一样,选择了哪些才展示那些
continuous-integration/drone/push Build is passing
2025-07-22 16:21:12 +08:00
wangxiaoshuang c03e85ff22 复核时也可以指派
continuous-integration/drone/push Build encountered an error
2025-07-22 15:36:35 +08:00
wangxiaoshuang 0a752ed92f 非dicom阅片工具配置
continuous-integration/drone/push Build is passing
2025-07-22 14:00:54 +08:00
wangxiaoshuang eab207fc35 阅片页面表格内层问题,部分问题无法使用(截图、附件修改)
continuous-integration/drone/push Build is passing
2025-07-22 11:53:28 +08:00
wangxiaoshuang 5ef755249c 用户账号邮箱格式验证
continuous-integration/drone/push Build is passing
2025-07-21 18:00:16 +08:00
wangxiaoshuang 8da7b81235 标记问题不是数值NE的也能保存
continuous-integration/drone/push Build is passing
2025-07-21 17:40:43 +08:00
wangxiaoshuang 276d8adac0 非dicom画标记问题
continuous-integration/drone/push Build is passing
2025-07-21 17:28:28 +08:00
wangxiaoshuang 12da4c5d9f 1
continuous-integration/drone/push Build is passing
2025-07-21 17:03:00 +08:00
wangxiaoshuang 850d64f0aa tab切换问题
continuous-integration/drone/push Build is passing
2025-07-21 16:59:17 +08:00
wangxiaoshuang 541d44b4f2 标记问题不是数值NE的也能保存
continuous-integration/drone/push Build is passing
2025-07-21 16:44:45 +08:00
wangxiaoshuang a1137c0a5b 数值问题配置为可选择,数值输入框在未绑定标记时需要可手动输入保存
continuous-integration/drone/push Build is passing
2025-07-21 15:34:51 +08:00
wangxiaoshuang e2e20a24fd forrest表单问题
continuous-integration/drone/push Build is passing
2025-07-21 15:18:32 +08:00
wangxiaoshuang 2f1fcfa81d mp4文件图标更换
continuous-integration/drone/push Build is passing
2025-07-21 14:57:03 +08:00
wangxiaoshuang ba4a5c0d1d 数值问题没有绑定标记,数值输入框的提示必填,下方保存按钮无法保存
continuous-integration/drone/push Build is passing
2025-07-21 14:18:38 +08:00
wangxiaoshuang face77f949 标记删除问题
continuous-integration/drone/push Build is passing
2025-07-21 14:14:09 +08:00
wangxiaoshuang de80b75c0b 输入框与按钮的间距需要调整增加
continuous-integration/drone/push Build is passing
2025-07-21 14:09:35 +08:00
wangxiaoshuang f050df725e 圆形测量area小数位取2位
continuous-integration/drone/push Build is running
2025-07-21 14:07:01 +08:00
wangxiaoshuang 290210c27e 数值问题测量后,提示输入标记名称,点击弹窗外部弹窗会被关闭,此时标记无名称仍可以被绑定
continuous-integration/drone/push Build is running
2025-07-21 14:04:40 +08:00
wangxiaoshuang be2651501d 质控页面中检查的影像为0,检查列表没有渲染。点击DICOM影像时,会出现
continuous-integration/drone/push Build is passing
2025-07-21 13:52:08 +08:00
wangxiaoshuang 2df6c37fd0 RECIST1.1标准,在基线时,矩形工具应该是可用的
continuous-integration/drone/push Build is passing
2025-07-21 10:49:02 +08:00
wangxiaoshuang 34cc69abb0 Merge branch 'uat'
continuous-integration/drone/push Build is passing
# Conflicts:
#	src/components/clinicalDataQuestions/components/QuestionFormItem.vue
#	src/components/clinicalDataQuestions/components/QuestionsForm.vue
#	src/components/clinicalDataQuestions/index.vue
#	src/views/trials/trials-panel/visit/qc-check/components/qualityAssurance.vue
#	src/views/trials/trials-panel/visit/qc-check/components/questions.vue
2025-07-18 17:17:03 +08:00
wangxiaoshuang 26914c2b2b 影像为加载前网速计算文件大小取值更改
continuous-integration/drone/push Build encountered an error
2025-07-18 17:08:18 +08:00
wangxiaoshuang 98ce2b27da 【质控复核】复核页面,去除指派按钮
continuous-integration/drone/push Build encountered an error
2025-07-18 15:28:32 +08:00
wangxiaoshuang 5805ea0778 关联访视影像过滤删除和不阅片影像
continuous-integration/drone/push Build encountered an error
2025-07-18 13:23:16 +08:00
wangxiaoshuang 17737d901c 影像浏览页面-关联访视,左侧的影像列表按照访视-检查-序列的层级展示;点击单个序列时,自动下载当前序列影像
continuous-integration/drone/push Build encountered an error
2025-07-18 11:43:29 +08:00
wangxiaoshuang 51a94ad89e 排序问题 2025-07-18 11:43:16 +08:00
wangxiaoshuang a2491c4ba5 影像汇总导出权限拆分为导出影像、导出关键图
continuous-integration/drone/push Build is passing
2025-07-18 10:02:11 +08:00
wangxiaoshuang 9187c9db8c 复核报错提示信息修改
continuous-integration/drone/push Build encountered an error
2025-07-18 09:40:41 +08:00
wangxiaoshuang 5e67f8b65f 1
continuous-integration/drone/push Build encountered an error
2025-07-17 16:50:32 +08:00
wangxiaoshuang 6218b536f9 【质控复核】A领取质控任务后指派给B,A点击替换按钮将此任务完成,B已进入质控页面点击指派时无响应
continuous-integration/drone/push Build encountered an error
2025-07-17 16:23:44 +08:00
wangxiaoshuang 7493c1a98a 【质控复核】同一个复核任务可以同时被2个iqc打开,当一个iqc复核完成后,另一个iqc也可以复核完成 2025-07-17 16:23:17 +08:00
wangxiaoshuang d91a9170de 【系统文档】新增可上传的mp4格式缺少格式icon 2025-07-17 16:22:45 +08:00
wangxiaoshuang 3d0a3116e1 删除标记时接口判断标记与问题是否有绑定关系
continuous-integration/drone/push Build is passing
2025-07-17 15:31:06 +08:00
wangxiaoshuang 76e41f4d5f 表格问题数据同步保存 2025-07-17 15:30:31 +08:00
wangxiaoshuang 57bec076b7 影像质控问题配置未确认时禁止质控操作
continuous-integration/drone/push Build encountered an error
2025-07-17 14:41:44 +08:00
wangxiaoshuang 36ea567229 影像质控问题配置未确认时禁止质控操作
continuous-integration/drone/push Build encountered an error
2025-07-17 14:20:03 +08:00
wangxiaoshuang d3a4842504 外层问题数据同步保存
continuous-integration/drone/push Build is passing
2025-07-17 14:07:44 +08:00
wangxiaoshuang e9dd669d48 1
continuous-integration/drone/push Build encountered an error
2025-07-17 11:40:29 +08:00
wangxiaoshuang 4d27cbfe41 【质控复核】历史审核人页面中复核问题需要默认展开
continuous-integration/drone/push Build encountered an error
2025-07-17 11:12:59 +08:00
wangxiaoshuang 13cfcb0935 删除标记的时候 有markid的时候 传markid 没markid的时候 传id
continuous-integration/drone/push Build is passing
2025-07-17 11:05:13 +08:00
wangxiaoshuang 8c40241658 稽查加一个 替换字符 可以自定义拼接
continuous-integration/drone/push Build is passing
2025-07-17 10:37:40 +08:00
wangxiaoshuang b8208dffed 1
continuous-integration/drone/push Build encountered an error
2025-07-17 10:12:35 +08:00
wangxiaoshuang 10b8cc6a5e 复核按钮显示判断
continuous-integration/drone/push Build encountered an error
2025-07-16 17:39:33 +08:00
wangxiaoshuang edf1377374 阅片页面加载网速监控
continuous-integration/drone/push Build encountered an error
2025-07-16 17:29:54 +08:00
wangxiaoshuang f8b4dd70c8 部分问题修复
continuous-integration/drone/push Build encountered an error
2025-07-16 16:58:38 +08:00
wangxiaoshuang 71acbc5f78 删除标记的时候 有markid的时候 传markid 没markid的时候 传id
continuous-integration/drone/push Build is passing
2025-07-16 10:20:51 +08:00
wangxiaoshuang 33a8d8743e 影像质控添加指派功能
continuous-integration/drone/push Build encountered an error
2025-07-16 10:11:50 +08:00
wangxiaoshuang a357fe7ce6 质控复核
continuous-integration/drone/push Build encountered an error
2025-07-15 17:51:42 +08:00
wangxiaoshuang 07fbbf5a74 质控添加复核
continuous-integration/drone/push Build encountered an error
2025-07-15 15:25:00 +08:00
wangxiaoshuang 79b14eee67 配置国际化修改
continuous-integration/drone/push Build encountered an error
2025-07-15 10:05:16 +08:00
wangxiaoshuang 9f76310101 【影像下载】原始检查数中可以查看影像数量,该数量需要过滤不阅片的数量
continuous-integration/drone/push Build encountered an error
2025-07-14 16:32:54 +08:00
wangxiaoshuang 1bacf16361 Merge branch 'uat'
continuous-integration/drone/push Build is passing
# Conflicts:
#	src/components/Dicom/DicomCanvas.vue
#	src/utils/index.js
#	src/views/trials/trials-panel/reading/dicoms3D/components/PetCtViewport.vue
#	src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue
#	src/views/trials/trials-panel/reading/dicoms3D/components/Viewport.vue
2025-07-14 16:17:52 +08:00
wangxiaoshuang 4b700b5db3 【影像下载】质控设置为不阅片的影像,在下载页面预览中需要过滤
continuous-integration/drone/push Build encountered an error
2025-07-14 16:10:51 +08:00
wangxiaoshuang b7f4cccbfa 在影像上传、影像质控该过程的影像浏览页面,影像阅片任务页面,在影像为加载前的下载过程,需要展示下载过程数据,包括网速、文件大小等
continuous-integration/drone/push Build encountered an error
2025-07-14 16:07:27 +08:00
wangxiaoshuang 1483a8c670 表格问题编辑标记问题未保存不可关闭弹框、外层问题标记不可绑定到表格问题、拖拽表格问题标记打开对应表格问题弹框
continuous-integration/drone/push Build is passing
2025-07-14 10:30:42 +08:00
wangxiaoshuang dab5a44f2d 拖拽标记延时保存 2025-07-14 10:28:35 +08:00
126 changed files with 5222 additions and 4162 deletions
+8
View File
@@ -11,6 +11,14 @@
<meta http-equiv="Cache-control" content="no-store,no-cache,must-revalidate">
<meta http-equiv="Cache" content="no-cache">
<title><%= webpackConfig.name %></title>
<style>
.customPromptTip {
position: absolute;
left: 0;
bottom: -100px;
z-index: 99999;
}
</style>
<% if (eval(process.env.VUE_APP_LOGIN_FOR_PERMISSION)) { %>
<script>
window.zzSessionStorage = {
+7 -1
View File
@@ -281,7 +281,13 @@ export function getCommonDocumentList(param) {
data: param
})
}
export function getCommonDocument(param) {
return request({
url: `/CommonDocument/getCommonDocument`,
method: 'get',
params: param
})
}
export function addOrUpdateCommonDocument(param) {
return request({
url: `/CommonDocument/addOrUpdateCommonDocument`,
+27
View File
@@ -203,6 +203,15 @@ export function qCVisitList_Export(data) {
data
})
}
// 导出影像指控问题和答案
export function GetTrialQCQuestionAnserList_Export(data) {
return requestDownload({
url: `/ExcelExport/GetTrialQCQuestionAnserList_Export`,
responseType: 'blob',
method: 'post',
data
})
}
// 导出下载记录
export function getTrialDownloadList_Export(data) {
return requestDownload({
@@ -273,3 +282,21 @@ export function GetEmailNoticeConfigList_Export(data) {
data
})
}
// 导出重传审批
export function GetImageBackList_Export(data) {
return requestDownload({
url: `/ExcelExport/GetImageBackList_Export`,
responseType: 'blob',
method: 'post',
data
})
}
// 导出医学审核配置
export function GetReadingMedicineSystemQuestionList_Export(data) {
return requestDownload({
url: `/ExcelExport/GetReadingMedicineSystemQuestionList_Export`,
responseType: 'blob',
method: 'post',
data
})
}
+15 -6
View File
@@ -45,10 +45,11 @@ export function saveImageLabelList(param) {
})
}
export function getStudyInfo(studyId) {
export function getStudyInfo(studyId, params) {
return request({
url: `/Study/item/${studyId}`,
method: 'get'
method: 'get',
params
})
}
export function getSeriesList(url) {
@@ -75,10 +76,11 @@ export function getRelationVisitList(visitNum, tpCode) {
method: 'get'
})
}
export function getAllRelationStudyList(studyId) {
export function getAllRelationStudyList(studyId, params) {
return request({
url: `/study/getAllRelationStudyList/${studyId}`,
method: 'get'
method: 'get',
params
})
}
export function getTrialListByReviewer(param) {
@@ -134,7 +136,7 @@ export function AddAdjudicationReport(param) {
}
export function getVisitStudyList(trialId, subjectVisitId, isReading, visitTaskId) {
let url = `/SubjectVisit/getVisitStudyList/${trialId}/${subjectVisitId}/${isReading}`
let url = `/SubjectVisit/getVisitStudyList/${trialId}/${subjectVisitId}/${isReading}`
url = visitTaskId ? `${url}?visitTaskId=${visitTaskId}` : url
return request({
url: url,
@@ -284,4 +286,11 @@ export function deleteSingleTableQuestionMark(param, type) {
})
}
// 阅片导入
export function readingImport(param) {
return request({
url: `/ReadingCalculate/readingImport`,
method: 'post',
data: param
})
}
+34 -3
View File
@@ -969,13 +969,20 @@ export function takeOrReleaseQCTask(trialId, subjectVisitId, obtainOrCancel) {
method: 'put'
})
}
export function replaceQCTaskActionUser(trialId, subjectVisitId) {
export function replaceQCTaskActionUser(trialId, subjectVisitId, params = {}) {
return request({
url: `/QCOperation/replaceQCTaskActionUser/${trialId}/${subjectVisitId}`,
method: 'put'
method: 'put',
params
})
}
export function getTrialUserRoleList(params) {
return request({
url: `/TrialMaintenance/getTrialUserRoleList`,
method: 'get',
params
})
}
export function verifyReuploadIsCanJump(trialId, qcChallengeId) {
return request({
url: `/QCOperation/verifyReuploadIsCanJump/${trialId}/${qcChallengeId}`,
@@ -4197,4 +4204,28 @@ export function getExportSubjectVisitImageList(data) {
method: 'post',
data
})
}
// 影像质控-更正dicom
export function updateDicomStudyInfo(data) {
return request({
url: `/Inspection/QCOperation/UpdateDicomStudyInfo`,
method: 'post',
data
})
}
// 影像质控-更正非dicom
export function updateNoneDicomStudy(data) {
return request({
url: `/Inspection/NoneDicomStudy/UpdateNoneDicomStudy`,
method: 'post',
data
})
}
// 阅片单元-获取访视计划
export function getVisitStage(data) {
return request({
url: `/VisitPlan/getVisitStage`,
method: 'post',
data
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+8 -1
View File
@@ -1,5 +1,5 @@
<template>
<div id="canvas" ref="canvas" v-loading="loading" element-loading-text="Loading..."
<div id="canvas" ref="canvas" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" style="width:100%;height:100%;position:relative;"
class="cornerstone-element" @contextmenu.prevent="onContextmenu" @mouseup="sliderMouseup">
<div v-show="dicomInfo.series" class="info-series">
@@ -39,6 +39,8 @@
</div>
<div class="info-subject">
<div v-if="series.subjectCode">{{ series.subjectCode }}</div>
<div v-if="series.visitName">{{ series.visitName }}</div>
<div>{{ stack.description }}</div>
<!-- <div>{{ dicomInfo.hospital }}</div> -->
<!-- <div v-show="dicomInfo.pid">{{ dicomInfo.pid }}</div> -->
@@ -112,6 +114,11 @@ import DicomTags from './DicomTags'
export default {
name: 'DicomCanvas',
components: { DicomTags },
computed: {
NSTip() {
return `${this.$store.state.trials.uploadSize}, NS: ${this.$store.state.trials.uploadTip}`
}
},
data() {
return {
loading: false,
@@ -16,7 +16,7 @@
{{ question.QuestionName }}
</span>
<!-- 添加 -->
<el-button type="primary" size="mini" @click="openAddTableCol(question)">
<el-button type="primary" size="mini" @click="openAddTableCol(question)" v-if="SecondReviewState <= 0">
{{ $t('trials:readingUnit:qsList:title:add') }}
</el-button>
</div>
@@ -29,7 +29,8 @@
: scope.row[item.Id] }}
</template>
</el-table-column>
<el-table-column :label="$t('common:action:action')" min-width="100" show-overflow-tooltip>
<el-table-column :label="$t('common:action:action')" min-width="100" show-overflow-tooltip
v-if="SecondReviewState <= 0">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openAddTableCol(question, scope.$index)">
{{ $t('common:button:edit') }}
@@ -164,7 +165,6 @@
import { getTrialClinicalQuestionCalculateRelation } from '@/api/dictionary'
import QuestionTableFormItem from './QuestionTableFormItem'
import { mapGetters } from 'vuex'
import DicomEvent from "../../../views/trials/trials-panel/reading/dicoms/components/DicomEvent";
export default {
name: 'QuestionFormItem',
@@ -176,6 +176,10 @@ export default {
return ''
}
},
SecondReviewState: {
type: Number,
default: 0
},
questionForm: {
type: Object,
default() {
@@ -4,11 +4,12 @@
:disabled="openType === 'look'">
<template>
<QuestionFormItem class="father" v-for="question of questions" :key="question.Id" :question="question"
:question-form="questionForm" :trial-clinical-id="trialClinicalId" @resetFormItemData="resetFormItemData"
@setFormItemData="setFormItemData" />
:question-form="questionForm" :trial-clinical-id="trialClinicalId" :SecondReviewState="SecondReviewState"
@resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" />
</template>
</el-form>
<div class="base-dialog-footer" v-if="!isViewer && openType !== 'look' && [2, 3].includes(clinicalDataLevel)"
<div class="base-dialog-footer"
v-if="!isViewer && openType !== 'look' && [2, 3].includes(clinicalDataLevel) && SecondReviewState <= 0"
style="text-align:right;margin-top:10px;">
<!-- 取消 -->
<el-button size="small" type="primary" @click="close"
@@ -55,6 +56,10 @@ export default {
return 2
}
},
SecondReviewState: {
type: Number,
default: 0
},
openType: {
type: String,
},
+10 -4
View File
@@ -3,11 +3,13 @@
<QuestionsForm v-if="clinicalUploadType === 2" :data="data" :trial-clinical-id="trialClinicalId"
:is-viewer="isViewer" :visit-id="visitId" :subject-id="subjectId" :open-type="openType"
:system-clinical-id="systemClinicalId" :trial-id="trialId" :reading-id="readingId"
:clinical-form-id="clinicalFormId" :clinical-data-level="clinicalDataLevel" @close="close"></QuestionsForm>
<uploadClinicalData v-else :subject-visit-id="subjectVisitId" :data="data" :enum-type="0" :allow-add-or-edit="true"
@getList="() => { }">
:clinical-form-id="clinicalFormId" :SecondReviewState="SecondReviewState" :clinical-data-level="clinicalDataLevel"
@close="close"></QuestionsForm>
<uploadClinicalData v-else :subject-visit-id="subjectVisitId" :SecondReviewState="SecondReviewState" :data="data"
:enum-type="0" :allow-add-or-edit="true" @getList="() => { }">
</uploadClinicalData>
<div class="base-dialog-footer" v-if="!isViewer && openType !== 'look' && [0, 1].includes(clinicalDataLevel)"
<div class="base-dialog-footer"
v-if="!isViewer && openType !== 'look' && [0, 1].includes(clinicalDataLevel) && SecondReviewState <= 0"
style="text-align:right;margin-top:10px;">
<!-- 保存 -->
<el-button size="small" type="primary" @click="submitClinicalForm">
@@ -92,6 +94,10 @@ export default {
type: String,
default: () => ''
},
SecondReviewState: {
type: Number,
default: 0
},
clinicalUploadType: {
type: Number,
default: () => {
@@ -24,8 +24,7 @@
</div>
<!--上传列表@selection-change="handleSelectionChange" @sort-change="handleSortByColumn"-->
<el-table ref="dicomFilesTable" v-adaptive="{ bottomOffset: 85 }" height="100" :data="list" :loading="loading"
class="dicomFiles-table"
:default-sort="{ prop: 'TaskBlindName', order: 'descending' }">
class="dicomFiles-table" :default-sort="{ prop: 'TaskBlindName', order: 'descending' }">
<!-- <el-table-column
type="selection"
width="55"
@@ -298,24 +297,47 @@ export default {
if (data.StudyList && data.StudyList.length > 0) {
let StudyList = data.StudyList
StudyList.forEach((study) => {
if (study.StudyDIRPath) {
let obj = {
name: `${data.SubjectCode}/${data.TaskBlindName
}/${this.$fd('IsDicom', true)}/${study.StudyCode
}/DICOMDIR`,
url: this.OSSclientConfig.basePath + study.StudyDIRPath,
}
if (this.IsReadingTaskViewInOrder === 0) {
obj = {
name: `${data.TaskBlindName}/${this.$fd(
'IsDicom',
true
)}/DICOMDIR`,
url: this.OSSclientConfig.basePath + study.StudyDIRPath,
}
}
files.push(obj)
}
if (study.SeriesList.length > 0) {
study.SeriesList.forEach((series) => {
if (series.InstancePathList.length > 0) {
series.InstancePathList.forEach((instance) => {
if (series.InstanceList.length > 0) {
series.InstanceList.forEach((instance) => {
let fileName = instance.Path.split('/').pop()
if (instance.FileName) {
fileName = instance.FileName
}
let obj = {
name: `${data.SubjectCode}/${data.TaskBlindName
}/${this.$fd('IsDicom', true)}/${study.StudyCode
}/${fileName}`,
}/IMAGE/${fileName}`,
url: this.OSSclientConfig.basePath + instance.Path,
IsEncapsulated: instance.IsEncapsulated
}
if (this.IsReadingTaskViewInOrder === 0) {
obj = {
name: `${data.TaskBlindName}/${this.$fd(
'IsDicom',
true
)}/${fileName}`,
)}/IMAGE/${fileName}`,
url: this.OSSclientConfig.basePath + instance.Path,
IsEncapsulated: instance.IsEncapsulated
}
}
files.push(obj)
@@ -406,7 +428,7 @@ export default {
let trialId = this.$route.query.trialId
var token = getToken()
const routeData = this.$router.resolve({
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${row.SourceSubjectVisitId}&TokenKey=${token}`,
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${row.SourceSubjectVisitId}&TokenKey=${token}&isReading=true`,
})
this.open = window.open(routeData.href, '_blank')
},
@@ -731,6 +731,8 @@ export default {
fileList: [],
dicomInfo: {
studyId: data.string('x00200010'),
DicomStudyDate: data.string('x00080020'),
DicomStudyTime: data.string('x00080030'),
studyUid: studyUid,
patientId: data.string('x00100020'),
patientName: patientNameStr,
@@ -841,6 +843,8 @@ export default {
)
seriesItem = {
seriesUid: seriesUid,
DicomSeriesDate: data.string('x00080021'),
DicomSeriesTime: data.string('x00080031'),
seriesNumber: data.intString('x00200011') || 1,
modality: data.string('x00080060') || '',
description: seriesDescriptionStr,
@@ -880,6 +884,10 @@ export default {
}
instanceItem = {
instanceUid: instanceUid,
SOPClassUID: data.string('x00080016'),
TransferSytaxUID: data.string('x00020010'),
MediaStorageSOPInstanceUID: instanceUid,
MediaStorageSOPClassUID: data.string('x00080016'),
instanceNumber: data.intString('x00200013') || 1,
frameCount: data.intString('x00280008') || 1,
instanceTime: instanceTime,
@@ -1088,6 +1096,8 @@ export default {
acquisitionNumber: dicomInfo.acquisitionNumber,
triggerTime: dicomInfo.triggerTime,
bodyPartExamined: '',
DicomStudyDate: dicomInfo.DicomStudyDate,
DicomStudyTime: dicomInfo.DicomStudyTime,
seriesList: [],
},
}
@@ -1112,6 +1122,10 @@ export default {
instanceList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
SOPClassUID: o.SOPClassUID,
TransferSytaxUID: o.TransferSytaxUID,
MediaStorageSOPInstanceUID:o.MediaStorageSOPInstanceUID,
MediaStorageSOPClassUID: o.MediaStorageSOPClassUID,
sopInstanceUid: o.instanceUid,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
@@ -1193,6 +1207,10 @@ export default {
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid,
SOPClassUID: o.SOPClassUID,
TransferSytaxUID: o.TransferSytaxUID,
MediaStorageSOPInstanceUID:o.MediaStorageSOPInstanceUID,
MediaStorageSOPClassUID: o.MediaStorageSOPClassUID,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
imageRows: o.imageRows,
@@ -1238,6 +1256,8 @@ export default {
seriesInstanceUid: v.seriesUid,
seriesNumber: v.seriesNumber,
seriesTime: v.seriesTime,
DicomSeriesDate: v.DicomSeriesDate,
DicomSeriesTime: v.DicomSeriesTime,
sliceThickness: v.sliceThickness,
imagePositionPatient: v.imagePositionPatient,
imageOrientationPatient: v.imageOrientationPatient,
@@ -206,7 +206,6 @@ export default {
default: true,
},
isUpload: {
required: true,
type: Boolean,
default: false,
},
@@ -245,13 +244,13 @@ export default {
if (this.IsDicom) {
var token = getToken()
routeData = this.$router.resolve({
path: `/showdicom?studyId=${row.Id}&TokenKey=${token}&type=Study&visitTaskId=${this.visitTaskId}`,
path: `/showdicom?studyId=${row.Id}&TokenKey=${token}&type=Study&visitTaskId=${this.visitTaskId}&isReading=true`,
})
} else {
let trialId = this.$route.query.trialId
var token = getToken()
routeData = this.$router.resolve({
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${row.SourceSubjectVisitId}&studyId=${row.Id}&TokenKey=${token}`,
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${row.SourceSubjectVisitId}&studyId=${row.Id}&TokenKey=${token}&isReading=true`,
})
}
window.open(routeData.href, '_blank')
+4
View File
@@ -29,6 +29,10 @@ export default {
MedicalAudit: 215, // 医学审核
HeavyReadingApproval: 216, // 重阅审批
ResetAndAsyncCriterion: 218, //同步签名
ResetImageQualityControlQuestion: 112, // 重置影像质控问题
CreateReviewTask: 113, // 生成复核任务
ReviewImageQualityControlQuestion: 219, // 复核质控问题
CorrectImageExaminationInformation: 220, // 更正影像检查信息
},
};
</script>
+16 -1
View File
@@ -202,6 +202,7 @@ async function VueInit() {
// 获取版本信息
let PublishInfo = await getCurrentPublishInfo();
Vue.prototype.$version = PublishInfo.Result;
Vue.prototype.$companyInfo = PublishInfo.OtherInfo;
// 获取检查部位
Vue.prototype.$getBodyPart = (id) => {
return new Promise(async (resolve, reject) => {
@@ -275,6 +276,20 @@ async function VueInit() {
}
return companyName;
}
Vue.prototype.$reg = () => {
if (localStorage.getItem('CompanyInfo')) {
let { EmailRegexStr } = JSON.parse(localStorage.getItem('CompanyInfo'))
if (EmailRegexStr) {
return { EmailRegexStr }
}
} else if (Vue.prototype.$companyInfo) {
let { EmailRegexStr } = Vue.prototype.$companyInfo
if (EmailRegexStr) {
return { EmailRegexStr }
}
}
return { EmailRegexStr: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' };
}
Vue.prototype.$updateDictionary = function () {
Vue.prototype.$d = function (code) {
var dictInfo = res.Result
@@ -316,7 +331,7 @@ async function VueInit() {
})
}
let CompanyInfo = JSON.parse(localStorage.getItem('CompanyInfo'))
if (CompanyInfo && CompanyInfo.SystemShortName) {
if (CompanyInfo && CompanyInfo.SystemShortName && text.toUpperCase() !== "CIRCLE") {
let test = new RegExp('IRC', 'ig')
text = text.replace(test, CompanyInfo.SystemShortName)
}
+21 -14
View File
@@ -12,6 +12,7 @@ import { getCustomTag } from '@/api/reading'
import requestPoolManager from '@/utils/request-pool'
import { getReadingVisitStudyList } from '@/api/trials'
import { getNetWorkSpeed, setNetWorkSpeedSizeAll, workSpeedclose } from "@/utils"
const hangingAgreement = [
{ name: 'A', row: 1, col: 1 },
{ name: 'A|A', row: 1, col: 2 },
@@ -644,15 +645,15 @@ const actions = {
resolve(noneDicomMeasureData)
})
},
addMeasuredData({ state }, obj) {
addMeasuredData({ state }, obj) {
return new Promise(resolve => {
const criterionType = parseInt(localStorage.getItem('CriterionType'))
var index = state.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
var measureData = state.visitTaskList[index].MeasureData
// var idx = measureData.findIndex(item => item.MeasureData.uuid === obj.data.MeasureData.data.uuid)
if (criterionType === 21) {
let i = measureData.findIndex(i=>i.TableQuestionId === obj.data.TableQuestionId)
let i = measureData.findIndex(i => i.TableQuestionId === obj.data.TableQuestionId)
if (i > -1) {
for (const k in state.visitTaskList[index].MeasureData[i]) {
if (k !== 'Id' && obj.data[k]) {
@@ -676,7 +677,7 @@ const actions = {
console.log('新增标记成功')
}
}
// sessionStorage.setItem('visitTaskList', state.visitTaskList.length > 0 ? JSON.stringify(state.visitTaskList) : '')
resolve()
})
@@ -775,15 +776,15 @@ const actions = {
const criterionType = parseInt(localStorage.getItem('CriterionType'))
if (criterionType === 21) {
const i = measureData.findIndex(item => item.QuestionId === obj.questionId && item.OrderMarkName === obj.orderMarkName)
if (i > -1) {
if (measureData[i].FristAddTaskId) {
measureData[i].MeasureData = ''
console.log('清除标记成功', i)
} else {
measureData.splice(i, 1)
console.log('移除标记成功', i)
}
if (i > -1) {
if (measureData[i].FristAddTaskId) {
measureData[i].MeasureData = ''
console.log('清除标记成功', i)
} else {
measureData.splice(i, 1)
console.log('移除标记成功', i)
}
}
state.visitTaskList[index].MeasureData = measureData
} else {
var idx = measureData.findIndex(item => item.QuestionId === obj.questionId && item.RowIndex === obj.rowIndex)
@@ -810,7 +811,7 @@ const actions = {
state.visitTaskList[index].MeasureData = measureData
}
}
// if (idx > -1) {
// measureData.splice(idx, 1)
@@ -1072,16 +1073,22 @@ const actions = {
}
}
}
let file = series.instanceInfoList.find(item => item.ImageId === obj.imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSizeAll(obj.percentComplete, obj.FileTotal, obj.imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
series.prefetchInstanceCount = instanceCount * 100
// 设置当前序列状态为已下载完成
series.loadStatus = true
workSpeedclose()
}
if (prefetchInstanceCount2 !== null && instanceCount2 !== null && prefetchInstanceCount2 >= instanceCount2 * 100) {
pSeries.prefetchInstanceCount = instanceCount2 * 100
// 设置当前序列状态为已下载完成
pSeries.loadStatus = true
workSpeedclose()
}
} catch (e) {
console.log('error')
+1
View File
@@ -10,6 +10,7 @@ const getDefaultState = () => {
unlock: false,
config: {},
uploadTip: '0.00KB/s',
uploadSize: '',
timer: null,
whiteList: [],
checkTaskId: null
+5 -3
View File
@@ -15,6 +15,7 @@ export const anonymization = function (file, config) {
let dataset = dcmjs.data.DicomMessage.readFile(buffer)
for (var i = 0; i < AnonymizeFixedList.length; i++) {
let AnonymizeFixed = AnonymizeFixedList[i]
if (!dataset.dict.hasOwnProperty(`${AnonymizeFixed.Group + AnonymizeFixed.Element}`)) continue
if (dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element]) {
dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element].Value[0] = AnonymizeFixed.ReplaceValue
} else {
@@ -28,6 +29,7 @@ export const anonymization = function (file, config) {
}
for (var i = 0; i < AnonymizeNotFixedList.length; i++) {
let AnonymizeNotFixed = AnonymizeNotFixedList[i]
// if (!dataset.dict.hasOwnProperty(`${AnonymizeNotFixed.Group + AnonymizeNotFixed.Element}`)) continue
if (AnonymizeNotFixed.Group + AnonymizeNotFixed.Element === '00100020') {
console.log(`${DicomStoreInfo.TrialCode}_${DicomStoreInfo.SubjectCode}`)
if (dataset.dict[AnonymizeNotFixed.Group + AnonymizeNotFixed.Element]) {
@@ -42,7 +44,7 @@ export const anonymization = function (file, config) {
}
} else {
if (dataset.dict[AnonymizeNotFixed.Group + AnonymizeNotFixed.Element]) {
dataset.dict[AnonymizeNotFixed.Group + AnonymizeNotFixed.Element].Value[0] = DicomStoreInfo[AnonymizeNotFixed.ReplaceValue] ? DicomStoreInfo[AnonymizeNotFixed.ReplaceValue].toString() : ''
dataset.dict[AnonymizeNotFixed.Group + AnonymizeNotFixed.Element].Value[0] = DicomStoreInfo[AnonymizeNotFixed.ReplaceValue] || Number(DicomStoreInfo[AnonymizeNotFixed.ReplaceValue]) === 0 ? DicomStoreInfo[AnonymizeNotFixed.ReplaceValue].toString() : ''
} else {
dataset.dict[AnonymizeNotFixed.Group + AnonymizeNotFixed.Element] = {
vr: AnonymizeNotFixed.ValueRepresentation,
@@ -53,9 +55,9 @@ export const anonymization = function (file, config) {
}
}
}
// console.log(dataset)
try {
let newDicomFile = dataset.write()
let newDicomFile = dataset.write() // fragmentMultiframe 原始数据是否进行分割
const bufferArray = new Uint8Array(newDicomFile)
const blob = new Blob([bufferArray], { type: 'application/octet-stream' })
resolve({ blob, pixelDataElement })
+96 -1
View File
@@ -1,4 +1,5 @@
import store from "@/store";
/**
* Parse the time to string
* @param {(Object|string|number)} time
@@ -92,7 +93,6 @@ export function deepClone(source, map = new WeakMap()) {
return target;
}
export function formatSize(size, fixed = 2) {
if (isNaN(parseFloat(size))) return ''
let kbSize = size / 1024
@@ -101,4 +101,99 @@ export function formatSize(size, fixed = 2) {
}
let mbSize = kbSize / 1024
return `${mbSize.toFixed(fixed)}MB`
}
let timer = null, // 网速定时器
lastPercentage = 0,
percentageById = {},
imageId = null,
bytesReceivedPerSecond = {}; // 时间节点上传文件总量
// 获取网速
export function getNetWorkSpeed() {
if (timer) return false;
// if (lastPercentage < 100) return false;
if (imageId && imageId !== Id) return false
imageId = null
timer = setInterval(() => {
let timeList = Object.keys(bytesReceivedPerSecond).sort((a, b) => a - b);
if (timeList.length > 0) {
let totalBytes = timeList.reduce((sum, bytes) => sum + bytesReceivedPerSecond[bytes], 0) / (5 * 1024);
let unit = 'KB/s';
if (totalBytes > 1024) {
totalBytes = totalBytes / 1024;
unit = "MB/s";
}
store.state.trials.uploadTip = totalBytes.toFixed(3) + unit;
}
if (timeList.length >= 5) {
delete bytesReceivedPerSecond[timeList[0]]
}
let time = new Date().getTime();
bytesReceivedPerSecond[time] = 0;
}, 1000)
}
export function setNetWorkSpeedSize(totalPercentage, total, Id) {
if (imageId && imageId !== Id) return false
imageId = Id
let percentage = totalPercentage - lastPercentage
percentage = percentage / 100
lastPercentage = totalPercentage
// console.log(percentage, totalPercentage, total)
let time = new Date().getTime();
let timeList = Object.keys(bytesReceivedPerSecond).sort((a, b) => a - b);
let bytesTime = timeList.find(item => time - item < 1000);
if (bytesTime) {
bytesReceivedPerSecond[bytesTime] += total * percentage;
} else {
// console.log("未查询到时间")
if (timeList.length > 0) {
bytesReceivedPerSecond[timeList[timeList.length - 1]] += total * percentage;
} else {
bytesReceivedPerSecond[time] = total * percentage;
}
}
store.state.trials.uploadSize = `${formatSize(totalPercentage / 100 * total)}/${formatSize(total)}`
}
export function setNetWorkSpeedSizeAll(totalPercentage, total, Id) {
if (!percentageById[Id]) {
percentageById[Id] = 0
}
let percentage = totalPercentage - percentageById[Id]
percentage = percentage / 100
percentageById[Id] = totalPercentage
// console.log(percentage, totalPercentage, total)
let time = new Date().getTime();
let timeList = Object.keys(bytesReceivedPerSecond).sort((a, b) => a - b);
let bytesTime = timeList.find(item => time - item < 1000);
if (bytesTime) {
bytesReceivedPerSecond[bytesTime] += total * percentage;
} else {
// console.log("未查询到时间")
if (timeList.length > 0) {
bytesReceivedPerSecond[timeList[timeList.length - 1]] += total * percentage;
} else {
bytesReceivedPerSecond[time] = total * percentage;
}
}
let isComplete = Object.keys(percentageById).every(key => percentageById[key >= 100])
if (isComplete) {
workSpeedclose(true)
}
}
export function workSpeedclose(isForce = false) {
if (!isForce && lastPercentage < 100) {
return false
}
console.log('workSpeedclose')
if (timer) {
clearInterval(timer);
timer = null;
store.state.trials.uploadTip = '0KB/s'
store.state.trials.uploadSize = ''
}
bytesReceivedPerSecond = {};
lastPercentage = 0;
imageId = null;
percentageById = {};
}
+23 -1
View File
@@ -1,6 +1,7 @@
import streamSaver from "streamsaver";
import "streamsaver/examples/zip-stream.js";
import store from '@/store'
import dcmjs from './dcmUpload/dcmjs'
streamSaver.mitm = `${window.location.origin}/mitm.html?version=2.0.0`
// 下载文件并压缩
function zipFiles(zipName, files) {
@@ -19,7 +20,10 @@ function zipFiles(zipName, files) {
if (fileInfo.done) {//迭代终止
ctrl.close();
} else {
let { name, url } = fileInfo.value;
let { name, url, IsEncapsulated = false } = fileInfo.value;
if (IsEncapsulated) {
url = await downloadFileToUrl(url)
}
// url = decodeUtf8(url); // 待定,可能做过特殊处理
return fetch(url).then(res => {
ctrl.enqueue({
@@ -99,6 +103,24 @@ function decodeUtf8(bytes) {
str2.pop();
return str2.join("/") + '/' + name;
}
// 下载文件进行修改并输出本地url
function downloadFileToUrl(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
const arraybuffer = xhr.response;
let dataset = dcmjs.data.DicomMessage.readFile(arraybuffer)
let newDicomFile = dataset.write({ fragmentMultiframe: false }) // fragmentMultiframe 原始数据是否进行分割
const bufferArray = new Uint8Array(newDicomFile)
const blob = new Blob([bufferArray], { type: 'application/octet-stream' })
const href = URL.createObjectURL(blob)
resolve(href)
};
xhr.send();
})
}
export async function downLoadFile(file, name, type = 'file') {
if (type === 'zip') return await zipFiles(name, file);
return await updateFile(file, name)
+2 -1
View File
@@ -179,7 +179,8 @@ export default {
trigger: 'blur',
},
{
type: 'email',
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address',
trigger: ['blur'],
},
+210 -164
View File
@@ -19,14 +19,9 @@
</div>
<div class="viewerSidethumbs ps" style="position: relative;">
<div class="viewerSidethumbinner">
<div
v-for="(item, index) in seriesList"
:key="index"
:class="{'viewerSideActive': index==0}"
<div v-for="(item, index) in seriesList" :key="index" :class="{ 'viewerSideActive': index == 0 }"
style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;"
series-type="current"
@click="showSeriesImage($event,index,item)"
>
series-type="current" @click="showSeriesImage($event, index, item)">
<div class="viewernavigatorwrapper">
<!-- <el-image
class="image-preview"
@@ -34,14 +29,8 @@
:src="item.previewImageUrl"
fit="fill"
/> -->
<img
class="image-preview"
:src="item.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 72px;height:72px;"
fit="fill"
>
<img class="image-preview" :src="item.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 72px;height:72px;" fit="fill">
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="item.keySeries" style="color:red">
@@ -51,23 +40,17 @@
#{{ item.seriesNumber }}
</div>
<div v-if="item.isExistMutiFrames && item.instanceCount > 1">
<el-popover
v-model="item.isShowPopper"
placement="right-start"
trigger="manual"
popper-class="instance_frame_wrapper"
>
<el-popover v-model="item.isShowPopper" placement="right-start" trigger="manual"
popper-class="instance_frame_wrapper">
<div style="text-align: right;">
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;" @click="item.isShowPopper = false" />
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;"
@click="item.isShowPopper = false" />
</div>
<div class="frame_list">
<div
v-for="(instance, idx) in item.instanceInfoList"
:key="instance.Id"
<div v-for="(instance, idx) in item.instanceInfoList" :key="instance.Id"
class="frame_content"
:style="{'margin-bottom':idx<item.instanceInfoList.length-1? '5px':'0px'}"
@click="showMultiFrames(item, index, instance)"
>
:style="{ 'margin-bottom': idx < item.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(item, index, instance)">
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>
@@ -75,22 +58,17 @@
</div>
<div v-if="showDelete">
<span>{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="instance.IsDeleted"
size="mini"
@change="changeInstanceDeleteStatus($event, item, instance)"
/>
<el-switch v-model="instance.IsDeleted" size="mini"
@change="changeInstanceDeleteStatus($event, item, instance)" />
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="instance.IsReading"
size="mini"
@change="changeInstanceReadingStatus($event, item, instance)"
/>
<el-switch v-model="instance.IsReading" size="mini"
@change="changeInstanceReadingStatus($event, item, instance)" />
</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" @click="popperClick(seriesList, item)" />
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;"
@click="popperClick(seriesList, item)" />
</el-popover>
</div>
</div>
@@ -100,7 +78,8 @@
<div v-show="!item.keySeries && item.sliceThickness" style="padding: 1px;">
T: {{ parseFloat(item.sliceThickness).toFixed(2) }}
</div>
<div v-show="!item.keySeries &&item.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<div v-show="!item.keySeries && item.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ item.description }}
</div>
<div v-if="!item.keySeries" style="padding: 1px;">
@@ -109,27 +88,23 @@
</div>
</div>
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;"
@click.stop="">
<div>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="item.isReading"
size="mini"
@change="changeReadingStatus($event, item)"
/>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isReading')
}}</span>
<el-switch v-model="item.isReading" size="mini" @change="changeReadingStatus($event, item)" />
</div>
<div>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="item.isDeleted"
size="mini"
@change="changeDeleteStatus($event, item)"
/>
<el-switch v-model="item.isDeleted" size="mini" @change="changeDeleteStatus($event, item)" />
</div>
</div>
<div v-if="item.prefetchInstanceCount>0 && item.prefetchInstanceCount<item.instanceCount * 100">
<el-progress :percentage="parseInt((item.prefetchInstanceCount/item.instanceCount).toFixed(2))" />
<div
v-if="item.prefetchInstanceCount > 0 && item.prefetchInstanceCount < item.instanceCount * 100">
<el-progress
:percentage="parseInt((item.prefetchInstanceCount / item.instanceCount).toFixed(2))" />
</div>
</div>
@@ -164,6 +139,7 @@ import requestPoolManager from '@/utils/request-pool'
import store from '@/store'
import { changeURLStatic } from '@/utils/history.js'
import metaDataProvider from '@/utils/metaDataProvider'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 })
var config = {
maxWebWorkers: 4,
@@ -184,7 +160,7 @@ export default {
'dicom-viewer': dicomViewer
},
data: function() {
data: function () {
return {
trialId: '',
studyId: '',
@@ -216,10 +192,13 @@ export default {
imageList: [],
showSeriesList: [],
currentLoadIns: [],
isFromCRCUpload: false
isFromCRCUpload: false,
isReading: null,
activeSeriesId: null,
isPacs: false
}
},
created: function() {
created: function () {
requestPoolManager.resetRequestPool()
this.type = this.$router.currentRoute.query.type ? this.$router.currentRoute.query.type : ''
@@ -230,6 +209,12 @@ export default {
store.dispatch('user/setToken', this.$router.currentRoute.query.TokenKey)
changeURLStatic('TokenKey', '')
}
if (this.$router.currentRoute.query.isReading) {
this.isReading = this.$router.currentRoute.query.isReading
}
if (this.$router.currentRoute.query.isPacs) {
this.isPacs = Boolean(this.$router.currentRoute.query.isPacs)
}
this.studyId = this.$router.currentRoute.query.studyId
this.isFromCRCUpload = !!this.$router.currentRoute.query.isFromCRCUpload
if (this.type === 'Series') {
@@ -256,10 +241,15 @@ export default {
cornerstone.imageCache.purgeCache()
requestPoolManager.resetRequestPool()
})
workSpeedclose(true)
},
methods: {
async loadStudy() {
const data = await getStudyInfo(this.studyId)
let params = {}
if (this.isPacs) {
params.IsPacs = true
}
const data = await getStudyInfo(this.studyId, params)
if (data.IsSuccess) {
if (data.Result) {
this.studyCode = data.Result.StudyCode
@@ -267,7 +257,13 @@ export default {
this.seriesCount = data.Result.SeriesCount
this.description = data.Result.Description
}
const url = `/series/list/${this.studyId}`
let isReading = !!this.isReading ? `?IsReading=true` : ''
if (isReading && this.isPacs) {
isReading += `&IsPacs=true`
} else if (!isReading && this.isPacs) {
isReading = `?IsPacs=true`
}
const url = `/series/list/${this.studyId}${isReading}`
this.getSeriesList(url)
}
},
@@ -317,7 +313,9 @@ export default {
loadStatus: false,
imageloadedArr: [],
isExistMutiFrames: item.IsExistMutiFrames,
isShowPopper: false
isShowPopper: false,
subjectCode: item.SubjectCode,
visitName: item.VisitName
})
})
this.seriesList = seriesList
@@ -377,7 +375,9 @@ export default {
loadStatus: false,
imageloadedArr: [],
isExistMutiFrames: item.IsExistMutiFrames,
isShowPopper: false
isShowPopper: false,
subjectCode: item.SubjectCode,
visitName: item.VisitName
})
})
this.seriesList = seriesList
@@ -447,7 +447,9 @@ export default {
loadStatus: false,
imageloadedArr: [],
isExistMutiFrames: isExistMutiFrames,
isShowPopper: false
isShowPopper: false,
subjectCode: res.OtherInfo.SubjectCode,
visitName: res.OtherInfo.VisitName
})
this.seriesList = seriesList
if (this.seriesList.length > 0) {
@@ -462,6 +464,8 @@ export default {
}
},
showSeriesImage(e, seriesIndex, series) {
this.activeSeriesId = series.seriesId
workSpeedclose(true)
// if (seriesIndex === this.currentSeriesIndex) return
const element = e.currentTarget
const elements = document.querySelectorAll('[series-type]')
@@ -531,7 +535,9 @@ export default {
isReading: series.isReading,
isDeleted: series.isDeleted,
previewImageUrl: series.previewImageUrl,
instanceCount: series.instanceCount
instanceCount: series.instanceCount,
subjectCode: series.SubjectCode,
visitName: series.VisitName
}
this.$refs.dicomViewer.loadImageStack(seriesInfo)
if (!series.loadStatus) {
@@ -597,7 +603,9 @@ export default {
keySeries: seriesInfo.KeySeries,
loadStatus: false,
imageloadedArr: [],
isShowPopper: false
isShowPopper: false,
subjectCode: seriesInfo.SubjectCode,
visitName: seriesInfo.VisitName
})
this.seriesList = seriesList
if (this.seriesList.length > 0) {
@@ -851,6 +859,9 @@ export default {
const seriesIndex = params.seriesIndex
var prefetchInstanceCount = this.seriesList[seriesIndex].prefetchInstanceCount
var instanceCount = this.seriesList[seriesIndex].instanceCount
if (!this.activeSeriesId) {
this.activeSeriesId = this.seriesList[seriesIndex].seriesId
}
if (this.seriesList[seriesIndex].imageloadedArr.indexOf(imageId) < 0 && this.seriesList[seriesIndex].imageloadedArr.length < this.seriesList[seriesIndex].instanceCount) {
const i = this.currentLoadIns.findIndex(i => i.imageId === imageId)
if (i > -1) {
@@ -870,10 +881,16 @@ export default {
this.seriesList[seriesIndex].imageloadedArr.push(imageId)
}
}
let file = this.seriesList[seriesIndex].instanceInfoList.find(item => item.ImageId === imageId)
if (file && this.activeSeriesId === this.seriesList[seriesIndex].seriesId) {
getNetWorkSpeed()
setNetWorkSpeedSize(percentComplete, e.detail.total, imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
this.seriesList[seriesIndex].prefetchInstanceCount = instanceCount * 100
// 设置当前序列状态为已下载完成
this.seriesList[seriesIndex].loadStatus = true
workSpeedclose()
if (!this.isFromCRCUpload) {
this.loadAllImages()
}
@@ -881,90 +898,102 @@ export default {
}
}
}
}
</script>
<style>
.viewerContainer .el-tabs--border-card{
background:none;
.viewerContainer .el-tabs--border-card {
background: none;
border: none;
}
.viewerContainer .el-tabs__item{
.viewerContainer .el-tabs__item {
height: 35px;
line-height: 35px;
}
.viewerContainer .el-tabs--border-card>.el-tabs__content{
.viewerContainer .el-tabs--border-card>.el-tabs__content {
padding: 5px 0px 0px 0px;
}
.viewerContainer ::-webkit-scrollbar {
width: 7px;
height: 7px;
width: 7px;
height: 7px;
}
.viewerContainer ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
border-radius: 10px;
background: #d0d0d0;
}
.viewerContainer .relationVisit{
.viewerContainer .relationVisit {
line-height: 20px;
cursor: pointer;
}
.viewerContainer{
display: block;
height: 100%;
margin-left: auto;
margin-right: auto;
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0,#444,#222);
min-height: 28px;
font-size: 16px;
margin: 0;
height: 30px;
padding-top: 0;
padding-left: 5px;
padding-right: 5px;
font-weight: bold;
.viewerContainer {
display: block;
height: 100%;
margin-left: auto;
margin-right: auto;
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0, #444, #222);
min-height: 28px;
font-size: 16px;
margin: 0;
height: 30px;
padding-top: 0;
padding-left: 5px;
padding-right: 5px;
font-weight: bold;
}
.viewerContainer .viewerContentWrapper {
display: flex;
flex-direction: row;
width: 100%;
padding: 5px;
/* height: 95%; */
height: 99%;
overflow: hidden;
text-overflow: clip;
white-space: nowrap;
width: 100%;
padding: 5px;
/* height: 95%; */
height: 99%;
overflow: hidden;
text-overflow: clip;
white-space: nowrap;
}
.viewerContainer .viewerContentWrapper>div {
display: inline-block;
white-space: normal;
display: inline-block;
white-space: normal;
}
.viewerContainer .viewerLeftSidePanel {
width: 215px;
background-color: #323232;
box-sizing: border-box;
margin: 0;
padding: 0;
margin-right: 2px;
color: #D0D0D0;
overflow-y: auto;
width: 215px;
background-color: #323232;
box-sizing: border-box;
margin: 0;
padding: 0;
margin-right: 2px;
color: #D0D0D0;
overflow-y: auto;
}
.viewerContainer .viewerContentWrapper>div>.sidePanelBody {
background: rgba(50,50,50,1);
word-break: break-all;
display: table;
width: 100%;
border: 1px solid #3e3f3a;
background: rgba(50, 50, 50, 1);
word-break: break-all;
display: table;
width: 100%;
border: 1px solid #3e3f3a;
}
.viewerContainer .viewerContentWrapper>div>div.sidePanelBody>div {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}
.viewerContainer .studyDesc{
.viewerContainer .studyDesc {
font-weight: bold;
font-size: 13px;
text-align: center;
@@ -972,81 +1001,96 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.viewerContainer .ps {
overflow: hidden !important;
overflow-anchor: none;
-ms-overflow-style: none;
touch-action: auto;
-ms-touch-action: auto;
overflow: hidden !important;
overflow-anchor: none;
-ms-overflow-style: none;
touch-action: auto;
-ms-touch-action: auto;
}
.viewerContainer .viewerLeftSidePanel .viewernavigatorwrapper {
display: flex;
width: 200px;
height: 84px;
padding: 1px 2px 1px 8px;
margin: 6px 0 6px 1px;
/* border-radius: 2px;
display: flex;
width: 200px;
height: 84px;
padding: 1px 2px 1px 8px;
margin: 6px 0 6px 1px;
/* border-radius: 2px;
border: 1px solid #404040; */
}
.viewerContainer .viewernavigatorwrapper .el-progress__text{
.viewerContainer .viewernavigatorwrapper .el-progress__text {
display: none;
}
.viewerContainer .viewernavigatorwrapper .el-progress-bar{
padding-right:0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
flex:1;
padding: 3px 1px 3px 4px;
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
.viewerContainer .viewernavigatorwrapper .el-progress-bar {
padding-right: 0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
flex: 1;
padding: 3px 1px 3px 4px;
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
}
.viewerContainer .viewerSideActive {
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
}
.viewerContainer .viewerContent {
flex: 1;
height: 100%;
display: block;
background-color: black;
color: #D0D0D0;
font-size: 13px;
flex: 1;
height: 100%;
display: block;
background-color: black;
color: #D0D0D0;
font-size: 13px;
}
.instance_frame_wrapper{
.instance_frame_wrapper {
min-width: 120px;
background-color: #2c2c2c;
border: 1px solid #2c2c2c;
padding: 5px;
}
.frame_list{
.frame_list {
max-height: 500px;
overflow-y: auto;
}
.instance_frame_wrapper ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.frame_content{
.frame_content {
/* height: 50px; */
padding: 10px;
display: flex;
@@ -1055,6 +1099,7 @@ export default {
font-size: 12px;
border: 1px solid #404040;
}
.frame_content:hover {
/* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@@ -1063,6 +1108,7 @@ export default {
border-color: #213a54 !important;
background-color: #213a54;
}
/* .viewerRightSidePanel {
width: 300px;
height: 100%;
+336 -261
View File
@@ -16,7 +16,8 @@
:key="index"
> -->
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`"
:name="`${study.StudyId}`">
<template slot="title">
<div class="text-desc">
@@ -25,8 +26,10 @@
<!-- <div v-show="study.Description" class="text-desc">
{{ study.Description }}
</div> -->
<el-tooltip v-show="study.Description" class="item" effect="dark" :content="study.Description" placement="bottom">
<div v-show="study.Description" style="width: 50px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<el-tooltip v-show="study.Description" class="item" effect="dark" :content="study.Description"
placement="bottom">
<div v-show="study.Description"
style="width: 50px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ study.Description }}
</div>
</el-tooltip>
@@ -37,44 +40,29 @@
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
{{ study.Description }}
</div>
<div
v-for="(series, i) in study.SeriesList"
:key="i"
:class="{'viewerSideActive': i==0 && index === 0}"
<div v-for="(series, i) in study.SeriesList" :key="i"
:class="{ 'viewerSideActive': i == 0 && index === 0 }"
style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;"
series-type="current"
@click="showSeriesImage($event,index,i,series)"
>
series-type="current" @click="showSeriesImage($event, index, i, series)">
<div class="viewernavigatorwrapper">
<img
class="image-preview"
:src="series.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 85px;height:85px;"
fit="fill"
>
<img class="image-preview" :src="series.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 85px;height:85px;" fit="fill">
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div>#{{ series.seriesNumber }}</div>
<div v-if="series.isExistMutiFrames && series.instanceCount > 1">
<el-popover
v-model="series.isShowPopper"
placement="right-start"
trigger="manual"
popper-class="instance_frame_wrapper"
>
<el-popover v-model="series.isShowPopper" placement="right-start" trigger="manual"
popper-class="instance_frame_wrapper">
<div style="text-align: right;">
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;" @click="series.isShowPopper = false" />
<i class="el-icon-circle-close"
style="font-size: 20px;cursor: pointer;color:#ddd;"
@click="series.isShowPopper = false" />
</div>
<div class="frame_list">
<div
v-for="(instance, idx) in series.instanceInfoList"
:key="instance.Id"
<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="showMultiFrames(index,series, i, instance)"
>
:style="{ 'margin-bottom': idx < series.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(index, series, i, instance)">
<!-- <div>
<img
class="image-preview"
@@ -92,23 +80,20 @@
</div>
<div v-if="showDelete">
<span>{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="instance.IsDeleted"
size="mini"
@change="changeInstanceDeleteStatus($event, series, instance)"
/>
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="instance.IsReading"
size="mini"
@change="changeInstanceReadingStatus($event, series, instance)"
/>
<el-switch v-model="instance.IsDeleted" size="mini"
@change="changeInstanceDeleteStatus($event, series, instance)" />
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading')
}}</span>
<el-switch v-model="instance.IsReading" size="mini"
@change="changeInstanceReadingStatus($event, series, instance)" />
</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" @click="popperClick(studyList, series)" />
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;"
@click="popperClick(studyList, series)" />
</el-popover>
</div>
@@ -116,12 +101,14 @@
<div v-show="series.InstanceCount" style="padding: 1px;">
{{ series.modality }}: {{ series.instanceCount }} image
</div>
<div v-show=" series.sliceThickness" style="padding: 1px;">
<div v-show="series.sliceThickness" style="padding: 1px;">
T: {{ series.sliceThickness }}
</div>
<el-tooltip v-show="series.description" class="item" effect="dark" :content="series.description" placement="bottom">
<div v-show="series.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<el-tooltip v-show="series.description" class="item" effect="dark"
:content="series.description" placement="bottom">
<div v-show="series.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ series.description }}
</div>
</el-tooltip>
@@ -131,30 +118,27 @@
<div />
</div>
</div>
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div v-if="showDelete"
style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div>
<span style="font-size: 12px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="series.isReading"
size="mini"
@change="changeReadingStatus($event, series)"
/>
<el-switch v-model="series.isReading" size="mini"
@change="changeReadingStatus($event, series)" />
</div>
<div>
<span style="font-size: 12px;">{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="series.isDeleted"
size="mini"
@change="changeDeleteStatus($event, series)"
/>
<el-switch v-model="series.isDeleted" size="mini"
@change="changeDeleteStatus($event, series)" />
</div>
</div>
<!-- <div style="position: absolute;bottom: -10px;left: 0;width: 100%;">
<el-progress v-if="series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount" :percentage="Number(series.prefetchInstanceCount/series.instanceCount)*100" />
</div> -->
<div v-if="series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount * 100">
<el-progress :percentage="parseInt((series.prefetchInstanceCount/series.instanceCount).toFixed(2))" />
<div
v-if="series.prefetchInstanceCount > 0 && series.prefetchInstanceCount < series.instanceCount * 100">
<el-progress
:percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" />
</div>
</div>
@@ -165,102 +149,109 @@
</div>
</div>
</el-tab-pane>
<el-tab-pane v-if="!visitTaskId" :label="$t('trials:dicom-show:relatedVisit')" name="relation-study" class="pane-relation-wrapper">
<el-tab-pane v-show="!visitTaskId" :label="$t('trials:dicom-show:relatedVisit')" name="relation-study"
class="pane-relation-wrapper">
<div class="viewerSidethumbinner">
<el-collapse v-model="relationActiveName" @change="handelRelationActiveChange">
<el-collapse-item v-for="(study,studyIndex) in relationStudyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<template slot="title">
<div class="text-desc">
{{ study.StudyCode }}
<el-collapse v-model="relationActiveName" @change="handelRelationActiveChange">
<template v-for="item in relationStudyListByVisitName">
<div :key="`${item.VisitName}`">
<div v-show="item.VisitName" class="text-desc" style="background-color: #1f1f1f;">
{{ item.VisitName }}
</div>
<!-- <div v-show="study.Description" class="text-desc">
<template v-for="(study, studyIndex) in relationStudyList">
<el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`"
v-if="study.VisitName === item.VisitName">
<template slot="title">
<div class="text-desc">
{{ study.StudyCode }}
</div>
<!-- <div v-show="study.Description" class="text-desc">
{{ study.Description }}
</div> -->
<div v-show="study.SeriesCount" class="text-desc">
{{ study.Modalities }} : {{ study.SeriesCount }} Series
</div>
</template>
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
{{ study.Description }}
</div>
<div v-if="study.seriesList" class="viewerSidethumbs ps" style="position: relative;">
<div class="viewerSidethumbinner">
<div
v-for="(seriesItem, index) in study.seriesList"
:key="index"
class="viewernavigatorwrapper"
style="position: relative;border:1px solid #434343;"
series-type="relation"
@click="showRelationSeriesImage($event,seriesItem,studyIndex,index)"
>
<!-- <el-image
<div v-show="study.SeriesCount" class="text-desc">
{{ study.Modalities }} : {{ study.SeriesCount }} Series
</div>
</template>
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
{{ study.Description }}
</div>
<div class="viewerSidethumbs ps" style="position: relative;">
<div class="viewerSidethumbinner">
<div v-for="(seriesItem, index) in study.seriesList" :key="seriesItem.seriesId">
<div class="viewernavigatorwrapper"
style="position: relative;border:1px solid #434343;" series-type="relation"
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
<img class="image-preview" :src="seriesItem.previewImageUrl" crossorigin="anonymous"
alt="" style="width: 85px;height:85px;" fit="fill">
<div class="viewernavitextwrapper">
<div
style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="seriesItem.keySeries" style="color:red">
Key Images
</div>
<div v-else>#{{ seriesItem.seriesNumber }}</div>
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
<el-popover placement="right-start" trigger="click"
popper-class="instance_frame_wrapper">
<div class="frame_list">
<div v-for="(instance, idx) in seriesItem.instanceInfoList"
:key="instance.Id" class="frame_content"
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(studyIndex, seriesItem, index, instance)">
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1}
frame`
}}</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;" />
</el-popover>
</div>
</div>
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
</div>
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
T: {{ seriesItem.sliceThickness }}
</div>
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
:content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ seriesItem.description }}
</div>
</el-tooltip>
</div>
</div>
<!-- <el-image
class="image-preview"
style="height:72px;width:72px;"
:src="seriesItem.previewImageUrl"
fit="fill"
/> -->
<img
class="image-preview"
:src="seriesItem.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 85px;height:85px;"
fit="fill"
>
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="seriesItem.keySeries" style="color:red">
Key Images
<div
v-if="seriesItem.prefetchInstanceCount > 0 && seriesItem.prefetchInstanceCount < seriesItem.instanceCount * 100">
<el-progress
:percentage="parseInt((seriesItem.prefetchInstanceCount / seriesItem.instanceCount).toFixed(2))" />
</div>
</div>
<div v-else>#{{ seriesItem.seriesNumber }}</div>
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
<el-popover
placement="right-start"
trigger="click"
popper-class="instance_frame_wrapper"
>
<div class="frame_list">
<div
v-for="(instance, idx) in seriesItem.instanceInfoList"
:key="instance.Id"
class="frame_content"
:style="{'margin-bottom':idx<seriesItem.instanceInfoList.length-1? '5px':'0px'}"
@click="showMultiFrames(studyIndex,seriesItem, index, instance)"
>
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1} frame` }}</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" />
</el-popover>
</div>
</div>
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
</div>
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
T: {{ seriesItem.sliceThickness }}
</div>
<el-tooltip v-show="seriesItem.description" class="item" effect="dark" :content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ seriesItem.description }}
</div>
</el-tooltip>
</div>
<!-- <div style="position: absolute;bottom: -10px;left: 0;width: 100%;">
<el-progress v-if="seriesItem.prefetchInstanceCount>0 && seriesItem.prefetchInstanceCount<seriesItem.instanceCount" :percentage="Number(seriesItem.prefetchInstanceCount/seriesItem.instanceCount)*100" />
</div> -->
</div>
</div>
</el-collapse-item>
</template>
</div>
</el-collapse-item>
</template>
</el-collapse>
</div>
@@ -287,6 +278,7 @@ import { getTaskUploadedDicomStudyList } from '@/api/reading'
import requestPoolManager from '@/utils/request-pool'
import store from '@/store'
import { changeURLStatic } from '@/utils/history.js'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
import metaDataProvider from '@/utils/metaDataProvider'
cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 })
// import * as cornerstoneTools from 'cornerstone-tools'
@@ -315,6 +307,7 @@ export default {
visitInfo: '',
activeName: 'current-study',
relationStudyList: [],
relationStudyListByVisitName: [],
cachedImages: [],
isReading: null,
isStartLoad: false,
@@ -328,7 +321,8 @@ export default {
currentLoadIns: [],
isFromCRCUpload: false,
visitTaskId: null,
page: ''
page: '',
activeSeriesId: null
}
},
mounted() {
@@ -361,6 +355,7 @@ export default {
cornerstone.imageCache.purgeCache()
requestPoolManager.resetRequestPool()
})
workSpeedclose(true)
},
methods: {
// 获取某个访视下所有的检查信息
@@ -422,7 +417,9 @@ export default {
loadStatus: false,
imageloadedArr: [],
isExistMutiFrames: series.IsExistMutiFrames,
isShowPopper: false
isShowPopper: false,
subjectCode: series.SubjectCode,
visitName: series.VisitName
})
})
data.SeriesList = seriesList
@@ -459,6 +456,8 @@ export default {
}
},
showSeriesImage(e, studyIndex, seriesIndex, series) {
this.activeSeriesId = series.seriesId
workSpeedclose(true)
const element = e.currentTarget
const elements = document.querySelectorAll('[series-type]')
Array.from(elements).forEach((e) => {
@@ -521,7 +520,9 @@ export default {
isReading: series.isReading,
isDeleted: series.isDeleted,
previewImageUrl: series.previewImageUrl,
instanceCount: series.instanceCount
instanceCount: series.instanceCount,
subjectCode: series.SubjectCode,
visitName: series.VisitName
}
this.$refs.dicomViewer.loadImageStack(seriesInfo)
if (!series.loadStatus) {
@@ -707,10 +708,26 @@ export default {
spinner: 'el-icon-loading'
})
try {
const res = await getAllRelationStudyList(this.subjectVisitId)
let params = {
IsReading: !!this.isReading
}
const res = await getAllRelationStudyList(this.subjectVisitId, params)
loading.close()
this.relationStudyList = res.Result
this.relationStudyListByVisitName = []
res.Result.forEach(item => {
let index = this.relationStudyListByVisitName.findIndex(d => d.VisitName === item.VisitName)
if (index < 0) {
this.relationStudyListByVisitName.push({
VisitName: item.VisitName,
series: [item]
})
} else {
this.relationStudyListByVisitName[index].series.push(item)
}
})
} catch (e) {
console.log(e)
loading.close()
}
}
@@ -739,7 +756,8 @@ export default {
spinner: 'el-icon-loading'
})
try {
const data = await getSeriesList(`/series/list/${studyId}`)
let isReading = !!this.isReading ? `?IsReading=true` : ''
const data = await getSeriesList(`/series/list/${studyId}${isReading}`)
loading.close()
if (data.Result != null && data.Result.length > 0) {
var seriesList = []
@@ -749,9 +767,15 @@ export default {
item.InstanceInfoList.forEach(i => {
if (i.NumberOfFrames && i.NumberOfFrames > 1) {
for (let j = 0; j < i.NumberOfFrames; j++) {
if (!i.ImageId) {
i.ImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? scope.OSSclientConfig.basePath : scope.OSSclientConfig.basePath}${i.Path}?frame=${j}&instanceId=${i.Id}&idx=${index}|${seriesIndex}&isRelation=1`
}
imageIds.push(`wadouri:${localStorage.getItem('location') !== 'USA' ? scope.OSSclientConfig.basePath : scope.OSSclientConfig.basePath}${i.Path}?frame=${j}&instanceId=${i.Id}&idx=${index}|${seriesIndex}&isRelation=1`)
}
} else {
if (!i.ImageId) {
i.ImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? scope.OSSclientConfig.basePath : scope.OSSclientConfig.basePath}${i.Path}?instanceId=${i.Id}&idx=${index}|${seriesIndex}&isRelation=1`
}
imageIds.push(`wadouri:${localStorage.getItem('location') !== 'USA' ? scope.OSSclientConfig.basePath : scope.OSSclientConfig.basePath}${i.Path}?instanceId=${i.Id}&idx=${index}|${seriesIndex}&isRelation=1`)
}
})
@@ -772,12 +796,17 @@ export default {
loadStatus: false,
imageloadedArr: [],
isExistMutiFrames: item.IsExistMutiFrames,
isShowPopper: false
isShowPopper: false,
subjectCode: item.SubjectCode,
visitName: item.VisitName
})
})
scope.relationStudyList[index].seriesCount = seriesList.length
scope.relationStudyList[index].seriesList = seriesList
scope.relationStudyList[index].showSeries = true
// scope.relationStudyList[index].seriesCount = seriesList.length
// scope.relationStudyList[index].seriesList = seriesList
// scope.relationStudyList[index].showSeries = true
scope.$set(scope.relationStudyList[index], 'seriesCount', seriesList.length)
scope.$set(scope.relationStudyList[index], 'seriesList', seriesList)
scope.$set(scope.relationStudyList[index], 'showSeries', true)
scope.$forceUpdate()
}
} catch (e) {
@@ -786,6 +815,8 @@ export default {
}
},
showRelationSeriesImage(e, series, studyIndex, index) {
this.activeSeriesId = series.seriesId
workSpeedclose(true)
this.currentRelationIndex = index
const element = e.currentTarget
const elements = document.querySelectorAll('[series-type]')
@@ -805,6 +836,9 @@ export default {
this.imageList.push({ imageId: imageId, seriesId: series.seriesId, priority })
})
}
if (this.imageList.length > 0) {
this.loopLoad()
}
}
},
loadAllImages() {
@@ -864,8 +898,11 @@ export default {
}
const studyIndex = params.idx.split('|')[0]
const seriesIndex = params.idx.split('|')[1]
var series = !params.isRelation ? this.studyList[studyIndex].SeriesList[seriesIndex] : null
var series = !params.isRelation ? this.studyList[studyIndex].SeriesList[seriesIndex] : this.relationStudyList[studyIndex].seriesList[seriesIndex]
if (!series) return
if (!this.activeSeriesId) {
this.activeSeriesId = series.seriesId
}
var prefetchInstanceCount = series.prefetchInstanceCount
var instanceCount = series.instanceCount
if (series.imageloadedArr.indexOf(imageId) < 0) {
@@ -887,6 +924,11 @@ export default {
series.imageloadedArr.push(imageId)
}
}
let file = series.instanceInfoList.find(item => item.ImageId === imageId)
if (file && this.activeSeriesId === series.seriesId) {
getNetWorkSpeed()
setNetWorkSpeedSize(percentComplete, e.detail.total, imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
series.prefetchInstanceCount = instanceCount * 100
// 设置当前序列状态为已下载完成
@@ -894,96 +936,110 @@ export default {
if (!this.isFromCRCUpload) {
this.loadAllImages()
}
workSpeedclose()
}
}
}
}
</script>
<style >
.viewerContainer .el-tabs--border-card{
background:none;
<style>
.viewerContainer .el-tabs--border-card {
background: none;
border: none;
}
.viewerContainer .el-tabs__item{
.viewerContainer .el-tabs__item {
height: 35px;
line-height: 35px;
width: 100px;
padding: 0!important;
padding: 0 !important;
text-align: center;
transform: scale(.8);
}
.viewerContainer .el-tabs--border-card>.el-tabs__content{
.viewerContainer .el-tabs--border-card>.el-tabs__content {
padding: 5px 0px 0px 0px;
}
.viewerContainer ::-webkit-scrollbar {
width: 7px;
height: 7px;
width: 7px;
height: 7px;
}
.viewerContainer ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
border-radius: 10px;
background: #d0d0d0;
}
.viewerContainer .relationVisit{
.viewerContainer .relationVisit {
line-height: 20px;
cursor: pointer;
}
.viewerContainer{
display: block;
height: 100%;
margin-left: auto;
margin-right: auto;
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0,#444,#222);
min-height: 28px;
font-size: 16px;
margin: 0;
height: 30px;
padding-top: 0;
padding-left: 5px;
padding-right: 5px;
font-weight: bold;
.viewerContainer {
display: block;
height: 100%;
margin-left: auto;
margin-right: auto;
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0, #444, #222);
min-height: 28px;
font-size: 16px;
margin: 0;
height: 30px;
padding-top: 0;
padding-left: 5px;
padding-right: 5px;
font-weight: bold;
}
.viewerContainer .viewerContentWrapper {
display: flex;
flex-direction: row;
width: 100%;
padding: 5px;
/* height: 95%; */
height: 99%;
/* overflow: hidden;
width: 100%;
padding: 5px;
/* height: 95%; */
height: 99%;
/* overflow: hidden;
text-overflow: clip; */
/* white-space: nowrap; */
/* white-space: nowrap; */
}
.viewerContainer .viewerContentWrapper>div {
display: inline-block;
white-space: normal;
display: inline-block;
white-space: normal;
}
.viewerContainer .viewerLeftSidePanel {
width: 220px;
background-color: #323232;
box-sizing: border-box;
margin: 0;
padding: 0;
margin-right: 2px;
color: #D0D0D0;
overflow-y: auto;
width: 220px;
background-color: #323232;
box-sizing: border-box;
margin: 0;
padding: 0;
margin-right: 2px;
color: #D0D0D0;
overflow-y: auto;
}
.viewerContainer .viewerContentWrapper>div>.sidePanelBody {
background: rgba(50,50,50,1);
word-break: break-all;
display: table;
width: 100%;
border: 1px solid #3e3f3a;
background: rgba(50, 50, 50, 1);
word-break: break-all;
display: table;
width: 100%;
border: 1px solid #3e3f3a;
}
.viewerContainer .viewerContentWrapper>div>div.sidePanelBody>div {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}
.viewerContainer .text-desc{
.viewerContainer .text-desc {
font-weight: bold;
font-size: 13px;
text-align: center;
@@ -991,107 +1047,126 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.viewerContainer .ps {
overflow: hidden !important;
overflow-anchor: none;
-ms-overflow-style: none;
touch-action: auto;
-ms-touch-action: auto;
overflow: hidden !important;
overflow-anchor: none;
-ms-overflow-style: none;
touch-action: auto;
-ms-touch-action: auto;
}
.viewerContainer .viewerLeftSidePanel .viewernavigatorwrapper {
display: flex;
width: 220px;
/* height: 84px; */
padding: 1px 2px 1px 2px;
margin: 2px 0 1px 1px;
/* border-radius: 2px;
display: flex;
width: 220px;
/* height: 84px; */
padding: 1px 2px 1px 2px;
margin: 2px 0 1px 1px;
/* border-radius: 2px;
border: 1px solid #404040; */
}
.viewerContainer .viewernavigatorwrapper .el-progress__text{
.viewerContainer .viewernavigatorwrapper .el-progress__text {
display: none;
}
.viewerContainer .viewernavigatorwrapper .el-progress-bar{
padding-right:0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
flex:1;
padding: 3px 1px 3px 4px;
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
.viewerContainer .viewernavigatorwrapper .el-progress-bar {
padding-right: 0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
flex: 1;
padding: 3px 1px 3px 4px;
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
}
.viewerContainer .viewerSideActive {
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
}
.viewerContainer .viewerContent {
flex: 1;
height: 100%;
display: block;
background-color: black;
color: #D0D0D0;
font-size: 13px;
flex: 1;
height: 100%;
display: block;
background-color: black;
color: #D0D0D0;
font-size: 13px;
}
.viewerContainer .pane-relation-wrapper{
.viewerContainer .pane-relation-wrapper {
min-height: 500px;
}
.viewerContainer .el-collapse{
.viewerContainer .el-collapse {
border: none;
}
.viewerContainer .el-collapse-item{
background-color: #585453!important;
.viewerContainer .el-collapse-item {
background-color: #585453 !important;
color: #ddd;
}
.viewerContainer .el-collapse-item__content{
padding-bottom:5px;
background-color: #2c2c2c!important;
.viewerContainer .el-collapse-item__content {
padding-bottom: 5px;
background-color: #2c2c2c !important;
color: #ddd;
}
.viewerContainer .el-collapse-item__header{
background-color: #585453!important;
.viewerContainer .el-collapse-item__header {
background-color: #585453 !important;
color: #ddd;
border-bottom-color:#5a5a5a;
border-bottom-color: #5a5a5a;
padding-left: 5px;
height: 40px;
line-height: 20px;
}
.instance_frame_wrapper{
.instance_frame_wrapper {
min-width: 120px;
background-color: #2c2c2c;
border: 1px solid #2c2c2c;
padding: 5px;
}
.frame_list{
.frame_list {
max-height: 500px;
overflow-y: auto;
}
.instance_frame_wrapper ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.frame_content{
.frame_content {
/* height: 50px; */
padding: 10px;
display: flex;
@@ -1100,6 +1175,7 @@ export default {
font-size: 12px;
border: 1px solid #404040;
}
.frame_content:hover {
/* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@@ -1121,4 +1197,3 @@ export default {
font-size: 13px;
} */
</style>
@@ -70,6 +70,7 @@ 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 mp4 from '@/assets/file_icon/mp4.png'
import PreviewFile from '@/components/PreviewFile'
import imageViewer from './image-viewer'
import { getSystemDocumentAttachmentList } from '@/api/dictionary'
@@ -117,6 +118,7 @@ export default {
pptx,
xls,
xlsx,
mp4,
rowData: {},
list: [],
searchData: defaultSearchData(),
@@ -0,0 +1,269 @@
<template>
<el-form ref="sysTemplateFrom" v-loading="loading" :model="form" label-width="140px" size="small" :rules="rules"
class="upload-temporary-file">
<div class="base-dialog-body">
<el-form-item :label="$t('dictionary:attachment:label:code')" prop="Code">
<el-input v-model="form.Code" />
</el-form-item>
<el-form-item :label="$t('dictionary:attachment:label:businessScenario')" prop="BusinessScenarioEnum">
<el-select v-model="form.BusinessScenarioEnum" style="width: 100%" size="small" filterable>
<el-option v-for="item of $d.Common_File_BusinessScenario" :key="item.id" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="$t('dictionary:attachment:label:file')">
<div class="upload-container">
<el-upload class="upload-demo" action accept="" :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.FileTypeEnum && form.FileTypeEnum !== 0)">
<el-button size="small" type="primary" :disabled="(!form.FileTypeEnum && form.FileTypeEnum !== 0)"
:loading="btnLoading">Select</el-button>
<!-- <span slot="tip" style="margin-left: 10px" class="el-upload__tip">
(must be in xlsx/xls/doc/docx format)
</span> -->
</el-upload>
</div>
</el-form-item>
<el-form-item :label="$t('dictionary:attachment:label:name')" prop="Name">
<el-input v-model="form.Name" />
</el-form-item>
<el-form-item :label="$t('dictionary:attachment:export:form:nameCN')" prop="NameCN">
<el-input v-model="form.NameCN" />
</el-form-item>
<el-form-item v-if="form.Id !== ''" :label="$t('dictionary:attachment:label:isDeleted')">
<el-radio-group v-model="form.IsDeleted">
<el-radio v-for="item of $d.YesOrNo" :label="item.value" :key="item.id">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('dictionary:attachment:label:description')">
<el-input v-model="form.Description" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
</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.FileTypeEnum && form.FileTypeEnum !== 0)"
:loading="saveBtnLoading" @click="handleSave">{{ $t('common:button:save') }}</el-button>
</el-form-item>
</div>
</el-form>
</template>
<script>
import {
addOrUpdateCommonDocument,
uploadCommonDoc,
Upload,
} from '@/api/dictionary'
import { getBasicDataSelects } from '@/api/dictionary/dictionary'
export default {
name: 'TemplateForm',
props: {
data: {
type: Object,
default() {
return {}
},
},
},
data() {
return {
form: {
Id: '',
Code: '',
FileTypeEnum: 0,
BusinessScenarioEnum: null,
Name: '',
NameCN: '',
Path: '',
Description: '',
IsDeleted: false,
},
rules: {
Code: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur'] },
],
Name: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur'] },
],
NameCN: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur'] },
],
BusinessScenarioEnum: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur'] },
],
},
fileList: [],
btnLoading: false,
saveBtnLoading: false,
loading: false,
dictionaryList: {},
}
},
mounted() {
this.initForm()
},
methods: {
async initForm() {
await this.getDicData()
if (Object.keys(this.data).length > 0) {
if (this.data.Path) {
this.fileList = [
{
name: this.data.Name,
path: this.data.Path,
url: this.data.Path,
type: this.data.Type,
},
]
}
for (const k in this.form) {
if (this.data.hasOwnProperty(k)) {
this.form[k] = this.data[k]
}
}
}
},
// 获取文件类型下拉框
getDicData() {
this.loading = true
getBasicDataSelects(['Common_File_ModuleType', 'Common_File_Type'])
.then((res) => {
this.dictionaryList = { ...res.Result }
this.loading = false
})
.catch(() => {
this.loading = false
})
},
beforeUpload(file) {
// 检测文件类型是否符合要求
if (this.checkFileSuffix(file.name)) {
this.fileList = []
return true
} else {
this.$alert(this.$t('dictionary:attachment:export:alert:formatFile'))
return false
}
},
async handleUploadFile(param) {
this.loading = true
this.form.FileName = param.file.name
let extendName = param.file.name
.substring(param.file.name.lastIndexOf('.'))
.toLocaleLowerCase()
let file = await this.fileToBlob(param.file)
let res = await this.OSSclient.put(`/System/Tools/${this.$guid()}${extendName}`, file)
this.form.Path = this.$getObjectName(res.url)
this.form.NameCN = param.file.name
this.fileList.push({
name: param.file.name,
path: this.form.Path,
fullPath: this.form.Path,
url: this.form.Path,
})
this.loading = false
this.btnLoading = false
// Upload(formData, 1).then((res) => {
// this.fileList.push({
// name: param.file.name,
// path: res.Result.FilePath,
// fullPath: res.Result.FullFilePath,
// url: res.Result.FilePath,
// })
// })
},
fileToBlob(file) {
// 创建 FileReader 对象
const reader = new FileReader()
return new Promise((resolve) => {
// FileReader 添加 load 事件
reader.addEventListener('load', (e) => {
let blob
if (typeof e.target.result === 'object') {
blob = new Blob([e.target.result])
} else {
blob = e.target.result
}
resolve(blob)
})
// FileReader 以 ArrayBuffer 格式 读取 File 对象中数据
reader.readAsArrayBuffer(file)
})
},
handleSave() {
this.$refs.sysTemplateFrom.validate((valid) => {
if (!valid) return
if (!this.form.Name) {
this.$alert(this.$t('dictionary:attachment:message:msg1'))
return
}
this.saveBtnLoading = true
addOrUpdateCommonDocument(this.form)
.then((res) => {
this.saveBtnLoading = false
this.$emit('closeDialog')
this.$emit('getList')
this.$message.success(this.$t('common:message:savedSuccessfully'))
})
.catch(() => {
this.saveBtnLoading = false
})
})
},
handleRemoveFile() {
this.fileList = []
this.form.Path = ''
this.form.NameCN = ''
this.form.Name = ''
},
handlePreview(file) {
if (file.fullPath) {
window.open(file.fullPath, '_blank')
}
},
handleExceed(files, fileList) {
this.$message.warning(this.$t('upload:rule:maxFile1'))
},
checkFileSuffix(fileName) {
return true
var typeArr = ['xls', 'xlsx', 'doc', 'docx']
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,238 @@
<template>
<BaseContainer>
<template slot="search-container">
<el-form :inline="true" size="small">
<!-- 业务场景 -->
<el-form-item :label="$t('dictionary:attachment:label:businessScenario')">
<el-select v-model="searchData.BusinessScenarioEnum" style="width: 150px">
<el-option v-for="item of $d.Common_File_BusinessScenario" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<!-- 文件名称 -->
<el-form-item :label="$t('dictionary:attachment:label:name')">
<el-input v-model="searchData.Name" style="width: 130px" clearable />
</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">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
<el-button type="primary" style="float: right" size="small" @click="handleAdd">
{{ $t('common:button:new') }}
</el-button>
</el-form>
</template>
<template slot="main-container">
<el-table v-adaptive="{ bottomOffset: 60 }" v-loading="loading" :data="list" stripe height="100"
@sort-change="handleSortChange">
<el-table-column type="index" width="40" />
<el-table-column prop="Code" :label="$t('dictionary:attachment:label:code')" show-overflow-tooltip
sortable="custom" />
<!-- 业务场景 -->
<el-table-column prop="BusinessScenarioEnum" :label="$t('dictionary:attachment:label:businessScenario')"
show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
{{
$fd(
'Common_File_BusinessScenario',
scope.row.BusinessScenarioEnum
)
}}
</template>
</el-table-column>
<!-- 文件名称 -->
<el-table-column prop="Name" :label="$t('dictionary:attachment:label:name')" show-overflow-tooltip
sortable="custom" />
<el-table-column prop="NameCN" :label="$t('dictionary:attachment:upload:table:nameCN')" show-overflow-tooltip
sortable="custom" />
<!-- 描述 -->
<el-table-column prop="Description" :label="$t('dictionary:attachment:label:description')"
show-overflow-tooltip />
<!-- 是否废除 -->
<el-table-column prop="IsDeleted" :label="$t('dictionary:attachment:label:isDeleted')" show-overflow-tooltip
sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsDeleted" type="danger">{{
$fd('YesOrNo', scope.row.IsDeleted)
}}</el-tag>
<el-tag v-else type="primary">{{
$fd('YesOrNo', scope.row.IsDeleted)
}}</el-tag>
</template>
</el-table-column>
<!-- 上传时间 -->
<el-table-column prop="UpdateTime" :label="$t('dictionary:attachment:label:updateTime')" show-overflow-tooltip
sortable="custom" />
<el-table-column :label="$t('common:action:action')" width="300">
<template slot-scope="scope">
<!-- <el-button type="text" @click="PreviewFile(scope.row)">
{{ $t('common:button:preview') }}
</el-button> -->
<el-button type="text" @click="handleDownload(scope.row)">
{{ $t('common:button:download') }}
</el-button>
<el-button type="text" @click="handleEdit(scope.row)">
{{ $t('common:button:edit') }}
</el-button>
<el-button type="text" @click="handleDelete(scope.row)">
{{ $t('common:button:delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
<!-- 新增/编辑 -->
<el-dialog v-if="editDialog.visible" :visible.sync="editDialog.visible" :close-on-click-modal="false"
:title="editDialog.title" width="600px" custom-class="base-dialog-wrapper">
<TemplateForm :data="currentRow" @closeDialog="closeDialog" @getList="getList" />
</el-dialog>
</template>
</BaseContainer>
</template>
<script>
import {
getCommonDocumentList,
DownloadCommonDoc,
deleteCommonDocument,
} from '@/api/dictionary'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import TemplateForm from './TemplateForm'
import { downLoadFile } from '@/utils/stream.js'
const FileTypeEnum = 0
const searchDataDefault = () => {
return {
FileTypeEnum: FileTypeEnum,
BusinessScenarioEnum: null,
Name: '',
PageIndex: 1,
PageSize: 20,
}
}
export default {
name: 'ToolsTemplate',
components: { BaseContainer, Pagination, TemplateForm },
data() {
return {
searchData: searchDataDefault(),
list: [],
total: 0,
currentRow: {},
editDialog: { title: '', visible: false },
loading: false,
}
},
mounted() {
this.getList()
},
computed: {
isEN() {
return this.$i18n.locale !== 'zh'
},
},
methods: {
handleDelete(row) {
// 确定删除该模版?
this.$confirm(this.$t('dictionary:attachment:message:msg1')).then(() => {
deleteCommonDocument(row.Id).then(() => {
this.$message.success(this.$t('common:message:deletedSuccessfully'))
this.getList()
})
}).catch(() => { })
},
PreviewFile(row) {
let basePath = window.location.origin
if (window.location.protocol !== 'https:') {
basePath = 'https://irc.test.extimaging.com'
}
let data = {
name: row.NameCN,
path: basePath + row.Path,
}
this.$emit('PreviewFile', data)
},
getList() {
this.loading = true
getCommonDocumentList(this.searchData)
.then((res) => {
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
})
.catch(() => {
this.loading = false
})
},
// 新增
handleAdd() {
this.editDialog.title = this.$t('common:button:new')
this.currentRow = { FileTypeEnum: FileTypeEnum }
this.editDialog.visible = true
},
// 下载
async handleDownload(row) {
try {
this.loading = true
let fileName = this.isEN ? row.Name : row.NameCN;
let type = fileName
.substring(fileName.lastIndexOf('.'))
.toLocaleLowerCase()
if (!type) {
let extendName = row.Path
.substring(row.Path.lastIndexOf('.'))
.toLocaleLowerCase()
fileName += extendName
}
let res = await downLoadFile(this.OSSclientConfig.basePath + row.Path, fileName)
this.loading = false
} catch (err) {
this.loading = false
}
},
// 编辑
handleEdit(row) {
this.editDialog.title = this.$t('common:button:edit')
this.currentRow = { ...row }
this.editDialog.visible = true
},
handleSearch() {
this.searchData.PageIndex = 1
this.getList()
},
handleReset() {
this.searchData = searchDataDefault()
this.getList()
},
closeDialog() {
this.editDialog.visible = false
},
// 排序
handleSortChange(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>
::v-deep .search {
display: block;
}
</style>
+16 -42
View File
@@ -1,52 +1,22 @@
<template>
<div class="attachment-wrapper">
<el-tabs v-model="activeTab" @tab-click="clickTab">
<el-tab-pane
v-for="item in $d.Common_File_Type"
:key="item.value"
:label="item.label"
:name="String(item.value)"
>
<UploadTemplate
v-if="activeTab === '1' && item.value == activeTab"
@PreviewFile="PreviewFile"
/>
<ExportTemplate
v-if="activeTab === '2' && item.value == activeTab"
@PreviewFile="PreviewFile"
/>
<el-tab-pane v-for="item in $d.Common_File_Type" :key="item.value" :label="item.label" :name="String(item.value)">
<UploadTemplate v-if="activeTab === '1' && item.value == activeTab" @PreviewFile="PreviewFile" />
<ExportTemplate v-if="activeTab === '2' && item.value == activeTab" @PreviewFile="PreviewFile" />
<EmailTemplate v-if="activeTab === '3' && item.value == activeTab" />
<CommonTemplate v-if="activeTab === '4' && item.value == activeTab" />
<SignatureTemplate
v-if="activeTab === '5' && item.value == activeTab"
/>
<SignatureTemplate v-if="activeTab === '5' && item.value == activeTab" />
<ToolsTemplate v-if="activeTab === '0' && item.value == activeTab" />
</el-tab-pane>
</el-tabs>
<!-- 预览 -->
<el-dialog
v-if="Preview.visible"
:visible.sync="Preview.visible"
:close-on-click-modal="false"
:fullscreen="true"
:title="Preview.title"
width="600px"
custom-class="base-dialog-wrapper"
>
<vue-office-docx
v-if="docxTypes.includes(Preview.type)"
:src="Preview.path"
style="height: calc(100vh - 70px)"
@rendered="renderedHandler"
@error="errorHandler"
/>
<vue-office-excel
v-else-if="excelTypes.includes(Preview.type)"
:src="Preview.path"
:options="options"
style="height: calc(100vh - 70px)"
@rendered="renderedHandler"
@error="errorHandler"
/>
<el-dialog v-if="Preview.visible" :visible.sync="Preview.visible" :close-on-click-modal="false" :fullscreen="true"
:title="Preview.title" width="600px" custom-class="base-dialog-wrapper">
<vue-office-docx v-if="docxTypes.includes(Preview.type)" :src="Preview.path" style="height: calc(100vh - 70px)"
@rendered="renderedHandler" @error="errorHandler" />
<vue-office-excel v-else-if="excelTypes.includes(Preview.type)" :src="Preview.path" :options="options"
style="height: calc(100vh - 70px)" @rendered="renderedHandler" @error="errorHandler" />
<PreviewFile v-else :file-path="Preview.path" :file-type="Preview.type" />
</el-dialog>
</div>
@@ -57,6 +27,7 @@ import ExportTemplate from './components/ExportTemplate'
import EmailTemplate from './components/EmailTemplate'
import CommonTemplate from './components/CommonTemplate'
import SignatureTemplate from './components/SignatureTemplate'
import ToolsTemplate from './components/ToolsTemplate'
import VueOfficeDocx from '@vue-office/docx'
import '@vue-office/docx/lib/index.css'
import VueOfficeExcel from '@vue-office/excel'
@@ -73,6 +44,7 @@ export default {
VueOfficeDocx,
VueOfficeExcel,
PreviewFile,
ToolsTemplate
},
data() {
return {
@@ -142,16 +114,18 @@ export default {
display: flex;
flex-direction: column;
}
.el-tabs__header {
height: 40px;
margin-bottom: 5px;
}
.el-tabs__content {
flex: 1;
.el-tab-pane {
height: 100%;
}
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -43,6 +43,7 @@
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<el-table-column type="index" width="60" />
<!-- Group -->
@@ -50,6 +51,7 @@
prop="Group"
:label="$t('template:anonymization:label:group')"
show-overflow-tooltip
sortable="custom"
/>
<!-- Element -->
<el-table-column
@@ -57,24 +59,28 @@
:label="$t('template:anonymization:label:element')"
show-overflow-tooltip
min-width="110"
sortable="custom"
/>
<!-- Tag Description -->
<el-table-column
prop="TagDescription"
:label="$t('template:anonymization:label:tagDescription')"
min-width="110"
sortable="custom"
/>
<!-- Tag DescriptionCN -->
<el-table-column
prop="TagDescriptionCN"
:label="$t('template:anonymization:label:tagDescriptionCN')"
min-width="110"
sortable="custom"
/>
<!-- Value Representation -->
<el-table-column
prop="ValueRepresentation"
:label="$t('template:anonymization:label:valueRepresentation')"
min-width="110"
sortable="custom"
/>
<!-- Is Fixed -->
<el-table-column :label="$t('template:anonymization:label:isFixed')" width="100">
@@ -92,6 +98,7 @@
prop="ReplaceValue"
:label="$t('template:anonymization:label:valueReplace')"
min-width="110"
sortable="custom"
/>
<!-- Is Enable -->
<el-table-column :label="$t('template:anonymization:label:isEnable')" width="100" fixed="right">
@@ -139,7 +146,9 @@ const searchDataDefault = () => {
IsAdd: false,
TagDescriptionCN: '',
PageIndex: 1,
PageSize: 500
PageSize: 500,
Asc: false,
SortField: ''
}
}
export default {
@@ -159,6 +168,17 @@ export default {
this.getList()
},
methods: {
// 排序
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()
},
switchChange(event, item) {
this.loading = true
addOrUpdateSystemAnonymization(item).then(res => {
@@ -43,6 +43,7 @@
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<el-table-column type="index" width="60" />
<!-- Group -->
@@ -50,6 +51,7 @@
prop="Group"
:label="$t('template:anonymization:label:group')"
show-overflow-tooltip
sortable="custom"
/>
<!-- Element -->
<el-table-column
@@ -57,24 +59,28 @@
:label="$t('template:anonymization:label:element')"
show-overflow-tooltip
min-width="110"
sortable="custom"
/>
<!-- Tag Description -->
<el-table-column
prop="TagDescription"
:label="$t('template:anonymization:label:tagDescription')"
min-width="110"
sortable="custom"
/>
<!-- Tag DescriptionCN -->
<el-table-column
prop="TagDescriptionCN"
:label="$t('template:anonymization:label:tagDescriptionCN')"
min-width="110"
sortable="custom"
/>
<!-- Value Representation -->
<el-table-column
prop="ValueRepresentation"
:label="$t('template:anonymization:label:valueRepresentation')"
min-width="110"
sortable="custom"
/>
<!-- Is Fixed -->
<el-table-column :label="$t('template:anonymization:label:isFixed')" width="100">
@@ -92,6 +98,7 @@
prop="ReplaceValue"
:label="$t('template:anonymization:label:valueReplace')"
min-width="110"
sortable="custom"
/>
<!-- Is Enable -->
<el-table-column :label="$t('template:anonymization:label:isEnable')" width="100" fixed="right">
@@ -138,7 +145,9 @@ const searchDataDefault = () => {
TagDescriptionCN: '',
IsAdd: true,
PageIndex: 1,
PageSize: 500
PageSize: 500,
Asc: false,
SortField: ''
}
}
export default {
@@ -158,6 +167,17 @@ export default {
this.getList()
},
methods: {
// 排序
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()
},
switchChange(event, item) {
this.loading = true
addOrUpdateSystemAnonymization(item).then(res => {
@@ -30,13 +30,13 @@
<el-form-item
:label="$t('trials:auditRecord:table:criterion')"
>
<el-select v-model="searchData.CriterionTypeEnum">
<el-select v-model="searchData.CriterionTypeEnum" clearable>
<el-option v-for="item of $d.CriterionType" :key="item.id" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
<!-- 是否通用 -->
<el-form-item :label="$t('dictionary:medicalAudit:label:IsGeneral')">
<el-select v-model="searchData.IsGeneral">
<el-select v-model="searchData.IsGeneral" clearable>
<el-option v-for="item of $d.YesOrNo" :key="item.id" :value="item.value" :label="item.label" />
</el-select>
</el-form-item>
@@ -51,6 +51,9 @@
>
{{ $t('common:button:reset') }}
</el-button>
<el-button type="primary" @click="handleExport">
{{ $t('common:button:export') }}
</el-button>
</el-form-item>
</el-form>
<span style="margin-left:auto">
@@ -66,6 +69,7 @@
:data="list"
stripe
height="100"
@sort-change="handleSortByColumn"
>
<!-- 序号 -->
<el-table-column
@@ -79,6 +83,7 @@
:label="$t('trials:MIMqcCfg:table:questionName')"
show-overflow-tooltip
min-width="140"
sortable="custom"
/>
<!-- 审核问题 -->
<el-table-column
@@ -86,6 +91,7 @@
:label="$t('common:title:languageType')"
show-overflow-tooltip
min-width="160"
sortable="custom"
>
<template slot-scope="scope">
{{ $fd('LanguageType', scope.row.LanguageType) }}
@@ -97,6 +103,7 @@
:label="$t('trials:qcCfg:table:type')"
show-overflow-tooltip
min-width="140"
sortable="custom"
>
<template slot-scope="scope">
{{ $fd('QcType', scope.row.Type) }}
@@ -108,6 +115,7 @@
:label="$t('trials:qcCfg:table:typeValue')"
show-overflow-tooltip
min-width="140"
sortable="custom"
/>
<!-- 任务类型 -->
<el-table-column
@@ -115,6 +123,7 @@
:label="$t('trials:medicalFeedbackCfg:title:taskType')"
show-overflow-tooltip
min-width="140"
sortable="custom"
>
<template slot-scope="scope">
<el-tag v-if="scope.row.ReadingCategory === 1" type="primary">
@@ -136,6 +145,7 @@
:label="$t('trials:auditRecord:table:criterion')"
min-width="160"
show-overflow-tooltip
sortable="custom"
>
<template slot-scope="scope">
<div>
@@ -148,6 +158,7 @@
:label="$t('dictionary:medicalAudit:label:IsGeneral')"
min-width="120"
show-overflow-tooltip
sortable="custom"
>
<template slot-scope="scope">
<div>
@@ -161,6 +172,7 @@
:label="$t('trials:qcCfg:table:parentQs')"
show-overflow-tooltip
min-width="140"
sortable="custom"
/>
<!-- 父问题触发值 -->
<el-table-column
@@ -168,12 +180,14 @@
:label="$t('trials:qcCfg:table:parentTriggerValue')"
show-overflow-tooltip
min-width="160"
sortable="custom"
/>
<!-- 是否必填 -->
<el-table-column
prop="IsRequired"
:label="$t('trials:qcCfg:table:isRequired')"
min-width="120"
sortable="custom"
>
<template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsRequired) }}
@@ -184,6 +198,7 @@
prop="IsEnable"
:label="$t('trials:qcCfg:table:isEnable')"
min-width="120"
sortable="custom"
>
<template slot-scope="scope">
{{ $fd('YesOrNo', scope.row.IsEnable) }}
@@ -215,7 +230,7 @@
</template>
<script>
import { getReadingMedicineSystemQuestionList, deleteReadingMedicineSystemQuestion } from '@/api/dictionary'
import { GetReadingMedicineSystemQuestionList_Export } from '@/api/export'
import BoxContent from '@/components/BoxContent'
import Pagination from '@/components/Pagination'
import MedicalAuditForm from './MedicalAuditForm'
@@ -228,7 +243,9 @@ const searchDataDefault = () => {
LanguageType: null,
Type: '',
CriterionTypeEnum: null,
IsGeneral: null
IsGeneral: null,
Asc: false,
SortField: ''
}
}
export default {
@@ -248,6 +265,20 @@ export default {
this.getList()
},
methods: {
async handleExport() {
await GetReadingMedicineSystemQuestionList_Export(this.searchData)
},
// 排序
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()
},
// 获取受试者列表信息
getList() {
this.loading = true
@@ -14,7 +14,7 @@
<el-form-item :label="$t('dictionary:sign:label:sceneType')" prop="Name">
<el-input v-model="form.Name" />
</el-form-item>
<el-form-item label="$t('dictionary:sign:label:value')" prop="Value">
<el-form-item :label="$t('dictionary:sign:label:value')" prop="Value">
<el-input v-model="form.Value" type="textarea" rows="5" />
</el-form-item>
<el-form-item :label="$t('dictionary:sign:label:valueCN')" prop="ValueCN">
+8
View File
@@ -164,6 +164,14 @@ export default {
},
},
],
Email: [
{
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: this.$t('rules:email'),
trigger: 'blur,change',
},
]
},
userId: null,
loading: false,
+3
View File
@@ -125,6 +125,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.EmailOrPhone && reg.test(this.form.EmailOrPhone)) {
this.sendDisabled = false
callback()
@@ -172,6 +172,13 @@ export default {
],
Phone: [
{ required: true, validator: checkPhone, trigger: 'blur' }
],
Email: [
{
pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address',
trigger: 'blur,change',
}
]
},
siteOptions: [],
@@ -117,6 +117,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.Email && reg.test(this.form.Email)) {
this.sendDisabled = false
callback()
@@ -136,6 +136,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.Email && reg.test(this.form.Email)) {
this.sendDisabled = false
callback()
+6
View File
@@ -158,6 +158,9 @@ export default {
callback(new Error(this.$t('trials:researchForm:formRule:specify')))
} else {
var reg = /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.EmailOrPhone && reg.test(this.form.EmailOrPhone)) {
callback()
} else {
@@ -170,6 +173,9 @@ export default {
callback(new Error(this.$t('trials:researchForm:formRule:specify')))
} else {
var reg = /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.ReplaceUserEmailOrPhone && reg.test(this.form.ReplaceUserEmailOrPhone)) {
callback()
} else {
+8 -1
View File
@@ -49,7 +49,7 @@
<el-input v-model="form.Phone" :disabled="!(state === 0 && userTypeEnumInt === 0) || isHistory" />
</el-form-item>
<!-- 联系邮箱 -->
<el-form-item :label="$t('trials:researchForm:form:contactorEmail')">
<el-form-item :label="$t('trials:researchForm:form:contactorEmail')" prop="Email">
<el-input v-model="form.Email" disabled />
</el-form-item>
@@ -183,6 +183,13 @@ export default {
],
Phone: [
{ required: true, validator: checkPhone, trigger: 'blur' }
],
Email: [
{
pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address',
trigger: 'blur,change',
}
]
},
siteOptions: [],
@@ -108,6 +108,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.Email && reg.test(this.form.Email)) {
this.sendDisabled = false
callback()
@@ -139,6 +139,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.Email && reg.test(this.form.Email)) {
this.sendDisabled = false
callback()
+6
View File
@@ -131,6 +131,9 @@ export default {
callback(new Error(this.$t('trials:researchForm:formRule:specify')))
} else {
var reg = /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.EmailOrPhone && reg.test(this.form.EmailOrPhone)) {
// this.sendDisabled = false
callback()
@@ -145,6 +148,9 @@ export default {
callback(new Error(this.$t('trials:researchForm:formRule:specify')))
} else {
var reg = /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.ReplaceUserEmailOrPhone && reg.test(this.form.ReplaceUserEmailOrPhone)) {
callback()
} else {
@@ -110,6 +110,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.form.EmailOrPhone && reg.test(this.form.EmailOrPhone)) {
this.sendDisabled = false
callback()
@@ -125,6 +128,9 @@ export default {
} else {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (
this.form.ReplaceUserEmailOrPhone &&
reg.test(this.form.ReplaceUserEmailOrPhone)
+2 -1
View File
@@ -285,7 +285,8 @@ export default {
trigger: 'blur',
},
{
type: 'email',
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address',
trigger: 'blur,change',
},
@@ -254,7 +254,7 @@
<el-date-picker
v-model="form.StartTime"
type="year"
value-format="yyyy-MM-DD"
value-format="yyyy"
>
</el-date-picker>
</el-form-item>
@@ -265,7 +265,7 @@
<el-date-picker
v-model="form.EndTime"
type="year"
value-format="yyyy-MM-DD"
value-format="yyyy"
>
</el-date-picker>
</el-form-item>
@@ -904,12 +904,23 @@ export default {
try {
if (key === 'clinicalTrials') {
let validate = await this.$refs.clinicalTrialsFrom.validate()
console.log(validate, 'validate')
if (!validate) return false
this.form.DoctorId = this.reviewerId
if (this.trialId) {
this.form.TrialId = this.trialId
}
if (this.form.StartTime) {
let arr = this.form.StartTime.split("-")
if (arr.length <= 1) {
this.form.StartTime = `${this.form.StartTime}-01-01`
}
}
if (this.form.EndTime) {
let arr = this.form.EndTime.split("-")
if (arr.length <= 1) {
this.form.EndTime = `${this.form.EndTime}-12-31`
}
}
this.loading = true
let res = await addOrUpdateTrialExperience(this.form)
this.loading = false
@@ -38,7 +38,11 @@
<span v-if="isEN">{{ DATA.DepartmentOther }}</span>
<span v-else>{{ DATA.DepartmentOtherCN }}</span>
</span>
<span class="el-icon-user" v-if="DATA.Rank || DATA.RankCN">
<span class="el-icon-user" v-if="DATA.RankOther || DATA.RankOtherCN">
<span v-if="isEN">{{ DATA.RankOther }}</span>
<span v-else>{{ DATA.RankOtherCN }}</span>
</span>
<span class="el-icon-user" v-else-if="DATA.Rank || DATA.RankCN">
<span v-if="isEN">{{ DATA.Rank }}</span>
<span v-else>{{ DATA.RankCN }}</span>
</span>
@@ -327,7 +331,13 @@
:label="$t('curriculumVitae:info:form:physician')"
prop="RankId"
>
<el-select v-model="form.RankId" clearable placeholder="">
<el-select v-model="form.RankId" clearable placeholder="" @change="()=>{
if($fd('Rank', form.RankId, 'id') !==
$t('curriculumVitae:selectLabel:Other')){
form.RankOther = null
form.RankOtherCN = null
}
}">
<el-option
v-for="item in $d.Rank"
:key="item.id"
@@ -548,7 +558,8 @@ export default {
trigger: 'blur',
},
{
type: 'email',
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: this.$t('rules:email'),
trigger: 'blur,change',
},
@@ -72,7 +72,11 @@
reviewerData.EmploymentView.DepartmentOtherCN
}}</span>
</span>
<span class="el-icon-user"
<span class="el-icon-user" v-if="reviewerData.EmploymentView.RankOther || reviewerData.EmploymentView.RankOtherCN">
<span v-if="isEN">{{ reviewerData.EmploymentView.RankOther }}</span>
<span v-else>{{ reviewerData.EmploymentView.RankOtherCN }}</span>
</span>
<span class="el-icon-user" v-else
><span v-if="isEN">{{ reviewerData.EmploymentView.Rank }}</span>
<span v-else>{{ reviewerData.EmploymentView.RankCN }}</span></span
>
@@ -417,7 +421,12 @@
v-if="true"
>
<template slot-scope="scope">
<span>{{ $fd('Indication', scope.row.IndicationEnum) }}</span>
<span v-if="scope.row.EvaluationContent">
{{ scope.row.EvaluationContent }}
</span>
<span v-else>
{{ $fd('Indication', scope.row.IndicationEnum) }}
</span>
</template>
</el-table-column>
<el-table-column
+3
View File
@@ -530,6 +530,9 @@ export default {
emailList.forEach((item) => {
var pattern =
/^([A-Za-z0-9_\-\.\u4e00-\u9fa5])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,8})$/
if (this.$reg().EmailRegexStr) {
pattern = new RegExp(this.$reg().EmailRegexStr)
}
if (!pattern.test(item)) {
isError = true
}
@@ -439,7 +439,8 @@ export default {
trigger: 'blur',
},
{
type: 'email',
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address',
trigger: ['blur'],
},
+7
View File
@@ -219,6 +219,13 @@ export default {
sortable: 'custom',
showOverflowTooltip: true,
},
{
prop: 'JoinedTrialCount',
label: this.$t('system:userlist:table:JoinedTrialCount'),
minWidth: 150,
sortable: 'custom',
showOverflowTooltip: true,
},
{
prop: 'LastLoginTime',
label: this.$t('system:userlist:table:LastLoginTime'),
+4 -1
View File
@@ -289,7 +289,10 @@ export default {
},
handleEmailChange() {
var reg =
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/
if (this.$reg().EmailRegexStr) {
reg = new RegExp(this.$reg().EmailRegexStr)
}
if (this.userForm.EMail && reg.test(this.userForm.EMail)) {
this.sendDisabled = false
} else {
@@ -535,7 +535,8 @@ export default {
trigger: 'blur',
},
{
type: 'email',
// type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr),
message: this.$t('rules:email'),
trigger: 'blur,change',
},
@@ -610,6 +611,9 @@ export default {
emailList.forEach((item) => {
var pattern =
/^([A-Za-z0-9_\-\.\u4e00-\u9fa5])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,8})$/
if (this.$reg().EmailRegexStr) {
pattern = new RegExp(this.$reg().EmailRegexStr)
}
if (!pattern.test(item)) {
isError = true
}
@@ -842,7 +842,7 @@ export default {
var trialId = this.$route.query.trialId
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(
`/${trialId}/Read/${this.subjectId}/visit/${param.file.name}`,
`/${trialId}/Read/${this.subjectId}/${this.visitTaskId}/${param.file.name}`,
file
)
console.log(res)
@@ -1,15 +1,7 @@
<template>
<div
id="canvas"
ref="canvas"
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<div id="canvas" ref="canvas" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
@mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
<!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@@ -18,46 +10,39 @@
</div>
<!-- 切换访视 -->
<div
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1"
class="info-visit"
@dblclick.stop="preventDefault($event)"
>
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum <= minVistNum?'not-allowed':'pointer',color:stack.visitTaskNum <= minVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
<div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
@dblclick.stop="preventDefault($event)">
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="blind_name_wrapper">
{{ stack.taskBlindName }}
</div>
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum >= maxVistNum?'not-allowed':'pointer',color:stack.visitTaskNum >= maxVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
<div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2>
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div>
</div>
<div class="info-image">
<div v-show="mousePosition.mo">
Pos: {{ mousePosition.x?mousePosition.x.toFixed(0):'' }}, {{ mousePosition.y?mousePosition.y.toFixed(0):'' }}
Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) : '' }}
</div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
<div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }}
</div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.suv.toFixed(3) :mousePosition.suv.toFixed(digitPlaces) }}
SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.suv.toFixed(3) : mousePosition.suv.toFixed(digitPlaces) }}
</div>
<div v-else-if="mousePosition.mo">
Density: {{ mousePosition.mo }}
@@ -81,8 +66,12 @@
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> -->
</div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)">
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
<div ref="sliderBox" class="my_slider_box"
style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }}
@@ -117,7 +106,7 @@
</div>
</template>
<style>
.my_slider_box:after{
.my_slider_box:after {
content: '';
position: absolute;
bottom: -20px;
@@ -308,7 +297,10 @@ export default {
}
},
computed: {
...mapGetters(['visitTaskList', 'currentReadingTaskState'])
...mapGetters(['visitTaskList', 'currentReadingTaskState']),
NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}`
}
},
watch: {
currentReadingTaskState: {
@@ -572,7 +564,7 @@ export default {
type: 'warning'
}).then(() => {
}).catch(() => {})
}).catch(() => { })
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
@@ -1111,7 +1103,7 @@ export default {
var frame = imageInfo.frame
this.stack.frame = this.stack.isExistMutiFrames ? parseInt(frame) : null
this.stack.instanceId = instanceId
const element = this.$refs.canvas
cornerstone.enable(element)
element.tabIndex = 0
@@ -1165,7 +1157,7 @@ export default {
// const scope = this
Array.from(toolButtons).forEach((toolBtn) => {
// Add the tool
// Add the tool
const toolName = toolBtn.getAttribute('data-tool')
const apiTool = cornerstoneTools[`${toolName}Tool`]
if (apiTool) {
@@ -1173,17 +1165,17 @@ export default {
if (!toolAlreadyAddedToElement) {
if (toolName === 'Length') {
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true }})
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true } })
} else if (toolName === 'Bidirectional') {
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true }})
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true } })
} else if (toolName === 'ArrowAnnotate') {
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'RectangleRoi') {
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'Probe' && parseInt(localStorage.getItem('CriterionType')) === 21) {
cornerstoneTools.addToolForElement(element, ProbeTool, { configuration: { fixedRadius: 5, handleRadius: true, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces }})
cornerstoneTools.addToolForElement(element, ProbeTool, { configuration: { fixedRadius: 5, handleRadius: true, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces } })
} else {
cornerstoneTools.addToolForElement(element, apiTool)
}
@@ -1209,10 +1201,10 @@ export default {
mouseButtonMask: 4
})
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
}
// cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip'])
@@ -1229,7 +1221,7 @@ export default {
this.stack.frame = this.stack.isExistMutiFrames ? parseInt(frame) : null
this.stack.instanceId = instanceId
this.height = (this.stack.currentImageIdIndex) * 100 / (this.stack.imageIds.length - 1)
resolve()
})
},
@@ -1257,9 +1249,8 @@ export default {
data.string('x00080030')
)
this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${
this.stack.imageIds.length
}`
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010'
)}`
@@ -1462,9 +1453,9 @@ export default {
var element = cornerstone.getEnabledElement(this.canvas)
const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image)
const dx =
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
const dy =
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
const length = Math.sqrt(dx * dx + dy * dy)
return length.toFixed(this.digitPlaces)
@@ -1478,9 +1469,9 @@ export default {
if (imagePlane) {
return {
rowPixelSpacing:
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
colPixelSpacing:
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
}
}
@@ -1537,7 +1528,7 @@ export default {
},
debounce(callback, delay) {
let timerId
return function() {
return function () {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
@@ -2020,88 +2011,102 @@ export default {
</script>
<style lang="scss" scoped>
.context-menu-wrapper{
position: absolute;
ul{
list-style: none;
margin: 0px;
padding: 0px;
.context-menu-wrapper {
position: absolute;
ul {
list-style: none;
margin: 0px;
padding: 0px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width:80px;
box-sizing: border-box;
}
.menu{
display: none;
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div{
div {
padding: 5px;
}
}
.submenu{
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div{
padding: 5px;
}
}
}
}
.menu li:hover{
background-color: #ff5722;
.submenu{
display:block;
}
}
.menu_active{
cursor: pointer;
}
.menu_disabled{
cursor: not-allowed;
}
}
.info-visit{
position: absolute;
left:50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper{
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper{
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding:0 10px;
font-size: 14px;
.menu li:hover {
background-color: #ff5722;
.submenu {
display: block;
}
}
.info-cd{
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding: 0 10px;
font-size: 14px;
}
}
.info-cd {
position: absolute;
left: 10px;
top: 5px;
@@ -2110,6 +2115,7 @@ export default {
font-size: 18px;
cursor: pointer;
}
.info-series {
position: absolute;
left: 10px;
@@ -2119,6 +2125,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-image {
position: absolute;
left: 10px;
@@ -2138,6 +2145,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-instance {
position: absolute;
right: 15px;
@@ -2170,6 +2178,7 @@ export default {
margin: 10px;
cursor: default;
}
.menu__item:hover {
color: #ff0000;
}
@@ -2189,11 +2198,12 @@ li:hover {
background-color: #e0e0e2;
color: white;
}
.msg-div {
position: absolute;
z-index: 10;
background-color: rgba(255, 255, 255, 0.5);
color: #000;
padding: 5px 20px;
}
position: absolute;
z-index: 10;
background-color: rgba(255, 255, 255, 0.5);
color: #000;
padding: 5px 20px;
}
</style>
@@ -721,7 +721,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -739,7 +739,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -872,7 +872,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -37,7 +37,7 @@
<div class="add-icon" @click.prevent="downloadTpl">
<i class="el-icon-download" />
</div>
<div class="add-icon" style="margin: 0 5px;" @click.prevent="uploadTpl(item.LesionType)">
<div class="add-icon" style="margin: 0 5px;" @click.prevent="uploadTpl(item.LesionType, item.QuestionName)">
<i class="el-icon-upload2" />
</div>
<div class="add-icon" @click.prevent="handleAddOrEdit('add', item)">
@@ -174,7 +174,7 @@
<!-- 导入 -->
<el-dialog v-if="upload.visible" :visible.sync="upload.visible" :close-on-click-modal="false"
:title="upload.title" width="500px">
<UploadExcel :visit-task-id="visitTaskId" @close="uploadDlgClose" />
<UploadExcel :visit-task-id="visitTaskId" :TableName="upload.TableName" @close="uploadDlgClose" />
</el-dialog>
</div>
</div>
@@ -233,7 +233,7 @@ export default {
formChanged: false,
digitPlaces: 2,
addOrEdit: { visible: false, title: '' },
upload: { visible: false, title: '' },
upload: { visible: false, title: '', TableName: '' },
qsList: [],
answersList: [],
qsForm: {},
@@ -668,7 +668,8 @@ export default {
console.log(e)
}
},
uploadTpl(lesionType) {
uploadTpl(lesionType, TableName) {
this.upload.TableName = TableName
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} `
this.upload.visible = true
},
@@ -33,12 +33,16 @@
</el-form>
</template>
<script>
import { uploadIVUSTemplate } from '@/api/reading'
import { readingImport } from '@/api/reading'
export default {
props: {
visitTaskId: {
type: String,
required: true
},
TableName: {
type: String,
default: ''
}
},
data() {
@@ -63,7 +67,9 @@ export default {
var data = new FormData()
data.append('file', param.file)
data.append('visitTaskId', this.visitTaskId)
await uploadIVUSTemplate(data)
data.append('readingImportType', 0)
data.append('TableName', this.TableName)
await readingImport(data)
this.$emit('close')
this.$message.success('导入成功!')
loading.close()
@@ -1073,7 +1073,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -715,7 +715,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -276,7 +276,7 @@ export default {
window.opener.postMessage(data, window.location)
},
async receiveMsg(event) {
console.log('nonedicoms', event.data.type)
// console.log('nonedicoms', event.data.type)
if (event.data.type === 'isCanActiveNoneDicomTool') {
if (event.data.data.isCanActiveTool) {
// this.model = 'ArrowAnnotate'
@@ -304,8 +304,12 @@ export default {
// this.canvasData.splice(i, 1)
// }
// }
var idx = this.canvasData.findIndex(item => item.data.uuid === event.data.data.data.uuid)
this.canvasData.splice(idx, 1)
if (!event.data.data) {
this.canvasData = []
} else {
var idx = this.canvasData.findIndex(item => item.data.uuid === event.data.data.data.uuid)
this.canvasData.splice(idx, 1)
}
ctx.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height) //
await ctx.drawImage(
this.currentImg,
@@ -512,6 +516,11 @@ export default {
this.canvasData.push(this.currentDrawData) //
}
}
//
if (this.model) {
var data = { type: 'setMeasurement', data: this.currentDrawData }
window.opener.postMessage(data, window.location)
}
this.isMouseDown = false //
this.model = ''
this.lesionName = ''
@@ -519,9 +528,6 @@ export default {
x: e.offsetX, //
y: e.offsetY
}
//
var data = { type: 'setMeasurement', data: this.currentDrawData }
window.opener.postMessage(data, window.location)
},
//
@@ -37,7 +37,7 @@
<div class="add-icon" @click.prevent="downloadTpl(item.LesionType)">
<i class="el-icon-download" />
</div>
<div class="add-icon" style="margin: 0 5px;" @click.prevent="uploadTpl(item.LesionType)">
<div class="add-icon" style="margin: 0 5px;" @click.prevent="uploadTpl(item.LesionType, item.QuestionName)">
<i class="el-icon-upload2" />
</div>
<div class="add-icon" @click.prevent="handleAddOrEdit('add', item)">
@@ -169,7 +169,7 @@
<!-- 导入 -->
<el-dialog v-if="upload.visible" :visible.sync="upload.visible" :close-on-click-modal="false"
:title="upload.title" width="500px">
<UploadExcel :visit-task-id="visitTaskId" :lesion-type="upload.lesionType" @close="uploadDlgClose" />
<UploadExcel :visit-task-id="visitTaskId" :lesion-type="upload.lesionType" :TableName="upload.TableName" @close="uploadDlgClose" />
</el-dialog>
</div>
</div>
@@ -228,7 +228,7 @@ export default {
formChanged: false,
digitPlaces: 2,
addOrEdit: { visible: false, title: '', lesionType: null },
upload: { visible: false, title: '', lesionType: null },
upload: { visible: false, title: '', lesionType: null, TableName: '', },
qsList: [],
answersList: [],
qsForm: {},
@@ -669,8 +669,9 @@ export default {
console.log(e)
}
},
uploadTpl(lesionType) {
uploadTpl(lesionType, TableName) {
this.upload.lesionType = lesionType
this.upload.TableName = TableName
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} `
this.upload.visible = true
},
@@ -33,7 +33,7 @@
</el-form>
</template>
<script>
import { uploadOCTFCTTemplate, uploadOCTLipidAngleTemplate } from '@/api/reading'
import { readingImport } from '@/api/reading'
export default {
props: {
visitTaskId: {
@@ -43,6 +43,10 @@ export default {
lesionType: {
type: Number,
required: true
},
TableName: {
type: String,
default: ''
}
},
data() {
@@ -67,10 +71,13 @@ export default {
var data = new FormData()
data.append('file', param.file)
data.append('visitTaskId', this.visitTaskId)
data.append('TableName', this.TableName)
if (this.lesionType === 112) {
await uploadOCTFCTTemplate(data)
data.append('readingImportType', 1)
await readingImport(data)
} else {
await uploadOCTLipidAngleTemplate(data)
data.append('readingImportType', 2)
await readingImport(data)
}
this.$emit('close')
this.$message.success('导入成功!')
@@ -590,7 +590,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -452,7 +452,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -7,14 +7,10 @@
<div class="visit-name-wrapper">
<div v-if="(visitTaskList.length > 0)" style="display: flex;flex-direction: row;">
<div
v-for="s in visitTaskList"
:key="s.VisitTaskId"
class="visit-item"
:class="{'visit-item-active': activeTaskVisitId==s.VisitTaskId}"
@click.prevent="handleClick(s)"
>{{ s.TaskBlindName }}</div>
<div v-for="s in visitTaskList" :key="s.VisitTaskId" class="visit-item"
:class="{ 'visit-item-active': activeTaskVisitId == s.VisitTaskId }" @click.prevent="handleClick(s)">
{{
s.TaskBlindName }}</div>
</div>
@@ -22,23 +18,12 @@
</div>
<div class="right">
<div
v-for="s in visitTaskList"
v-show="activeTaskVisitId === s.VisitTaskId"
:key="s.VisitTaskId"
class="study-wrapper"
>
<StudyList
v-if="selectArr.includes(s.VisitTaskId)"
:ref="s.VisitTaskId"
:visit-task-id="s.VisitTaskId"
:trial-id="trialId"
:subject-visit-id="s.VisitId"
:task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
@loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms"
/>
<div v-for="s in visitTaskList" v-show="activeTaskVisitId === s.VisitTaskId" :key="s.VisitTaskId"
class="study-wrapper">
<StudyList v-if="selectArr.includes(s.VisitTaskId)" :ref="s.VisitTaskId" :visit-task-id="s.VisitTaskId"
:trial-id="trialId" :subject-visit-id="s.VisitId" :task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo" @loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms" />
</div>
</div>
</div>
@@ -46,20 +31,12 @@
</div>
<div class="dicom-viewer">
<div class="container">
<DicomViewer
v-if="activeTaskVisitId"
ref="dicomViewer"
:is-show="isShow"
:question-form-change-state="questionFormChangeState"
:question-form-change-num="questionFormChangeNum"
:is-exists-clinical-data="isExistsClinicalData"
:is-exists-no-dicom-file="isExistsNoDicomFile"
<DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :is-show="isShow"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
:is-exists-manual="isExistsManual"
:iseCRFShowInDicomReading="iseCRFShowInDicomReading"
@previewCD="previewCD"
/>
:is-reading-task-view-in-order="isReadingTaskViewInOrder" :is-exists-manual="isExistsManual"
:iseCRFShowInDicomReading="iseCRFShowInDicomReading" @previewCD="previewCD" />
</div>
</div>
@@ -75,6 +52,7 @@ import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import { workSpeedclose } from "@/utils"
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
// import metaDataProvider from '@/utils/metaDataProvider'
// cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 });
@@ -248,6 +226,7 @@ export default {
DicomEvent.$off('addNoneDicomMeasureData')
DicomEvent.$off('selectSeries')
window.removeEventListener('beforeunload', e => { cornerstone.imageCache.purgeCache() })
workSpeedclose(true)
},
methods: {
async getVisitInfo() {
@@ -474,7 +453,7 @@ export default {
getSeriesInfoByMark(baseSeries, visitTaskId, obj, visitTaskIdx) {
var seriesInfo = null
if (obj) {
// ID
// ID
var index = visitTaskIdx
if (index > -1) {
var idx = -1
@@ -497,7 +476,7 @@ export default {
// var instanceIdx = series.instanceList.findIndex(imageId => !!~imageId.indexOf(instanceId))
const frame = this.visitTaskList[index].MeasureData[idx].MeasureData.frame
// const filterStr = series.isExistMutiFrames ? `frame=${frame}&instanceId=${instanceId}` : `instanceId=${instanceId}`
let n = series.instanceInfoList.findIndex(k=>k.Id === instanceId)
let n = series.instanceInfoList.findIndex(k => k.Id === instanceId)
let filterStr = ''
if (n > -1 && series.isExistMutiFrames) {
if (series.instanceInfoList[n].NumberOfFrames > 0) {
@@ -538,13 +517,15 @@ export default {
params[key] = value
}
params.percentComplete = e.detail.percentComplete
params.FileTotal = e.detail.total
params.imageId = imageId
store.dispatch('reading/setImageLoadedProgress', params)
}
}
}
</script>
<style lang="scss" scoped>
.dicom-container{
.dicom-container {
width: 100%;
height: 100%;
display: flex;
@@ -553,32 +534,35 @@ export default {
background-color: #000;
box-sizing: border-box;
user-select: none;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.dicom-viewer{
.dicom-viewer {
flex: 1;
width: 100%;
box-sizing: border-box;
.container{
.container {
width: 100%;
height: 100%;
box-sizing: border-box;
}
}
.dicom-list{
width: 200px;
padding: 5px 0px;
.dicom-list {
width: 200px;
padding: 5px 0px;
box-sizing: border-box;
.container{
.container {
width: 100%;
height: 100%;
box-sizing: border-box;
@@ -586,7 +570,8 @@ export default {
// border: 1px solid #ccc;
}
::v-deep.el-tabs{
::v-deep.el-tabs {
box-sizing: border-box;
padding: 0 5px;
height: 100%;
@@ -594,26 +579,31 @@ export default {
flex-direction: column;
// justify-content: flex-start;
border: 1px solid #727272;
.el-tabs__item{
.el-tabs__item {
color: #fff;
}
.el-tabs__header{
.el-tabs__header {
height: 55px;
margin:0px;
margin: 0px;
box-sizing: border-box;
}
.el-tabs__content{
.el-tabs__content {
flex: 1;
margin:0px;
margin: 0px;
overflow-y: auto;
box-sizing: border-box;
}
.el-tabs__item{
.el-tabs__item {
color: #fff;
}
}
.dicom-desc{
.dicom-desc {
font-weight: bold;
font-size: 13px;
text-align: center;
@@ -621,23 +611,28 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.ps {
overflow-anchor: none;
touch-action: auto;
}
.series-active {
background-color: #607d8b!important;
border: 1px solid #607d8b!important;
background-color: #607d8b !important;
border: 1px solid #607d8b !important;
}
::v-deep.el-progress__text{
::v-deep.el-progress__text {
color: #ccc;
font-size: 12px;
}
.series{
.series {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.series-wrapper {
display: flex;
flex-direction: row;
@@ -650,11 +645,13 @@ export default {
border-radius: 2px;
border: 1px solid #404040;
background-color: #3a3a3a;
.el-progress__text{
.el-progress__text {
display: none;
}
.el-progress-bar{
padding-right:0px;
.el-progress-bar {
padding-right: 0px;
}
.image-preview {
@@ -663,9 +660,11 @@ export default {
border: 2px solid #252525;
cursor: pointer;
}
.image-desc {
vertical-align: top;
p{
p {
width: 100px;
white-space: nowrap;
overflow: hidden;
@@ -678,7 +677,8 @@ export default {
}
}
}
.related-study-wrapper{
.related-study-wrapper {
box-sizing: border-box;
height: 100%;
padding-bottom: 5px;
@@ -686,14 +686,15 @@ export default {
flex-direction: row;
justify-content: space-between;
.left{
.left {
position: relative;
width: 25px;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
.visit-name-wrapper{
.visit-name-wrapper {
position: absolute;
top: 5px;
@@ -708,30 +709,34 @@ export default {
// flex-direction: row;
// align-content: flex-start;
}
.visit-item{
.visit-item {
margin-left: 10px;
white-space: nowrap;
padding: 0px 4px;
padding: 0px 4px;
border: 1px solid #999999;
border-bottom:none ;
border-bottom: none;
text-align: center;
background-color: #4e4e4e;
color: #d5d5d5;
cursor: pointer;
// margin-left: 10px;
}
.visit-item-active{
.visit-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.right{
width:170px;
flex:1;
.right {
width: 170px;
flex: 1;
height: 100%;
border-left: 1px solid #4a4a4a;
color: #d5d5d5;
.study-wrapper{
.study-wrapper {
width: 100%;
height: 100%;
}
@@ -801,7 +801,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -727,7 +727,7 @@ export default {
file = this.convertBase64ToBlob(file)
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -1,15 +1,7 @@
<template>
<div
id="canvas"
ref="canvas"
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<div id="canvas" ref="canvas" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
@mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
<!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@@ -18,46 +10,39 @@
</div>
<!-- 切换访视 -->
<div
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1"
class="info-visit"
@dblclick.stop="preventDefault($event)"
>
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum <= minVistNum?'not-allowed':'pointer',color:stack.visitTaskNum <= minVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
<div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
@dblclick.stop="preventDefault($event)">
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="blind_name_wrapper">
{{ stack.taskBlindName }}
</div>
<div
class="arrw_div_wrapper"
:style="{cursor:stack.visitTaskNum >= maxVistNum?'not-allowed':'pointer',color:stack.visitTaskNum >= maxVistNum?'#888':'#fff'}"
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
<div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2>
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div>
</div>
<div class="info-image">
<div v-show="mousePosition.mo">
Pos: {{ mousePosition.x?mousePosition.x.toFixed(0):'' }}, {{ mousePosition.y?mousePosition.y.toFixed(0):'' }}
Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) : '' }}
</div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
<div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }}
</div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.suv.toFixed(3) :mousePosition.suv.toFixed(digitPlaces) }}
SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.suv.toFixed(3) : mousePosition.suv.toFixed(digitPlaces) }}
</div>
<div v-else-if="mousePosition.mo">
Density: {{ mousePosition.mo }}
@@ -81,8 +66,12 @@
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> -->
</div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)">
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
<div ref="sliderBox" class="my_slider_box"
style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }}
@@ -117,7 +106,7 @@
</div>
</template>
<style>
.my_slider_box:after{
.my_slider_box:after {
content: '';
position: absolute;
bottom: -20px;
@@ -306,7 +295,10 @@ export default {
}
},
computed: {
...mapGetters(['visitTaskList', 'currentReadingTaskState'])
...mapGetters(['visitTaskList', 'currentReadingTaskState']),
NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}`
}
},
watch: {
currentReadingTaskState: {
@@ -562,7 +554,7 @@ export default {
mouseDown(e) {
this.image = e.detail.image
var pointNearTool = this.pointNearTool(e)
if (pointNearTool) {
if (pointNearTool) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
@@ -574,7 +566,7 @@ export default {
type: 'warning'
}).then(() => {
}).catch(() => {})
}).catch(() => { })
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
@@ -672,7 +664,7 @@ export default {
this.sliderInfo.isMove = false
},
getMeasureData() {
console.log('getMeasureData')
console.log('getMeasureData')
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.stack.visitTaskId)
this.measureData = this.visitTaskList[idx].MeasureData
const ToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager
@@ -889,7 +881,7 @@ export default {
} else {
// cornerstoneTools.setToolEnabledForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
cornerstoneTools.setToolPassiveForElement(element, data.MeasureData.type, { mouseButtonMask: 1 })
}
if (this.stack.instanceId.includes(data.InstanceId) && ((this.stack.isExistMutiFrames && (data.MeasureData.frame === this.stack.frame) && data.MeasureData) || (!this.stack.isExistMutiFrames && data.MeasureData))) {
// console.log('renderMeasuredData', this.stack.frame,data.MeasureData.frame,this.stack.isExistMutiFrames)
@@ -991,32 +983,32 @@ export default {
var idx = this.measureData.findIndex(item => item.MeasureData && item.MeasureData.data && item.MeasureData.data.uuid === toolState.data[i].uuid)
if (idx > -1) {
DicomEvent.$emit('setCollapseActive', this.measureData[idx])
const measureData = {}
// var markName = this.measureData[idx].OrderMarkName
var markName = this.measureData[idx].MeasureData.data.remark
if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) {
const questionInfo = this.measureData[idx]
measureData.orderMarkName = markName
this.$emit('moveMeasureData', { measureData, questionInfo, orderMarkName: markName})
}
if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') {
const questionInfo = this.measureData[idx]
const canvas = this.canvas.querySelector('canvas')
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
measureData.frame = this.stack.frame ? this.stack.frame : 0
measureData.data = toolState.data[i]
measureData.type = toolType
measureData.thick = this.dicomInfo.thick
measureData.location = this.dicomInfo.location
measureData.ww = Math.round(viewport.voi.windowWidth)
measureData.wc = Math.round(viewport.voi.windowCenter)
measureData.data.active = false
this.$emit('modifyMeasureData', { measureData, questionInfo })
}
const measureData = {}
// var markName = this.measureData[idx].OrderMarkName
var markName = this.measureData[idx].MeasureData.data.remark
if (this.activeToolName === 'Eraser' && this.disabledMarks.indexOf(markName) === -1) {
const questionInfo = this.measureData[idx]
measureData.orderMarkName = markName
this.$emit('moveMeasureData', { measureData, questionInfo, orderMarkName: markName })
}
if ((this.disabledMarks.indexOf(markName) === -1 || !this.disabledMarks) && this.activeToolName !== 'Eraser') {
const questionInfo = this.measureData[idx]
const canvas = this.canvas.querySelector('canvas')
measureData.pictureBaseStr = canvas.toDataURL('image/png', 1)
measureData.studyId = this.stack.studyId
measureData.seriesId = this.stack.seriesId
measureData.instanceId = instanceId
measureData.frame = this.stack.frame ? this.stack.frame : 0
measureData.data = toolState.data[i]
measureData.type = toolType
measureData.thick = this.dicomInfo.thick
measureData.location = this.dicomInfo.location
measureData.ww = Math.round(viewport.voi.windowWidth)
measureData.wc = Math.round(viewport.voi.windowCenter)
measureData.data.active = false
this.$emit('modifyMeasureData', { measureData, questionInfo })
}
break
}
}
@@ -1118,7 +1110,7 @@ export default {
const toolButtons = document.querySelectorAll('[data-tool]')
// const scope = this
Array.from(toolButtons).forEach((toolBtn) => {
// Add the tool
// Add the tool
const toolName = toolBtn.getAttribute('data-tool')
const apiTool = cornerstoneTools[`${toolName}Tool`]
if (apiTool) {
@@ -1126,17 +1118,17 @@ export default {
if (!toolAlreadyAddedToElement) {
if (toolName === 'Length') {
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true }})
cornerstoneTools.addToolForElement(element, LengthTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true } })
} else if (toolName === 'Bidirectional') {
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true }})
// cornerstoneTools.addToolForElement(element, BidirectionalTool, { digits: this.digitPlaces })
// , handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true
cornerstoneTools.addToolForElement(element, BidirectionalTool, { configuration: { digits: this.digitPlaces, hideHandlesIfMoving: true } })
} else if (toolName === 'ArrowAnnotate') {
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
cornerstoneTools.addToolForElement(element, ArrowAnnotateTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'RectangleRoi') {
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true }})
cornerstoneTools.addToolForElement(element, RectangleRoiTool, { configuration: { allowEmptyLabel: true, handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true } })
} else if (toolName === 'CircleRoi') {
cornerstoneTools.addToolForElement(element, CircleRoiTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true, showMinMax: true }})
cornerstoneTools.addToolForElement(element, CircleRoiTool, { configuration: { handleRadius: false, drawHandlesOnHover: true, hideHandlesIfMoving: true, digits: this.digitPlaces, drawHandles: true, showMinMax: true } })
} else {
cornerstoneTools.addToolForElement(element, apiTool)
}
@@ -1162,10 +1154,10 @@ export default {
mouseButtonMask: 4
})
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
// if (!cornerstoneTools.getToolForElement(element, OrientationMarkersTool)) {
// cornerstoneTools.addToolForElement(element, OrientationMarkersTool)
// }
// cornerstoneTools.setToolActiveForElement(element, 'OrientationMarkers', { })
}
// cornerstoneTools.addStackStateManager(this.canvas, ['stack', 'stackPrefetch', 'playClip'])
@@ -1210,9 +1202,8 @@ export default {
data.string('x00080030')
)
this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${
this.stack.imageIds.length
}`
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010'
)}`
@@ -1392,9 +1383,9 @@ export default {
var element = cornerstone.getEnabledElement(this.canvas)
const { rowPixelSpacing, colPixelSpacing } = this.getPixelSpacing(element.image)
const dx =
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
(data.handles.end.x - data.handles.start.x) * (colPixelSpacing || 1)
const dy =
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
(data.handles.end.y - data.handles.start.y) * (rowPixelSpacing || 1)
const length = Math.sqrt(dx * dx + dy * dy)
return length.toFixed(this.digitPlaces)
@@ -1408,9 +1399,9 @@ export default {
if (imagePlane) {
return {
rowPixelSpacing:
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
imagePlane.rowPixelSpacing || imagePlane.rowImagePixelSpacing,
colPixelSpacing:
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
imagePlane.columnPixelSpacing || imagePlane.colImagePixelSpacing
}
}
@@ -1462,7 +1453,7 @@ export default {
},
debounce(callback, delay) {
let timerId
return function() {
return function () {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
@@ -1480,7 +1471,7 @@ export default {
if (imageId) {
ToolStateManager.clearImageIdToolState(imageId)
let elements = cornerstone.getEnabledElementsByImageId(imageId)
elements.map(el=>{
elements.map(el => {
cornerstone.updateImage(el.element)
})
}
@@ -1766,7 +1757,7 @@ export default {
this.activeToolName = toolName
this.$nextTick(() => {
// console.log(cornerstoneTools.isToolActiveForElement(this.canvas, 'Bidirectional'))
if (toolName === 'Zoom') {
cornerstoneTools.setToolActiveForElement(this.canvas, 'Zoom', {
mouseButtonMask: [1, 2]
@@ -1920,7 +1911,7 @@ export default {
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
if (isNaN(params.frame)){
if (isNaN(params.frame)) {
params.frame = 0
}
return params
@@ -1942,88 +1933,102 @@ export default {
</script>
<style lang="scss" scoped>
.context-menu-wrapper{
position: absolute;
ul{
list-style: none;
margin: 0px;
padding: 0px;
.context-menu-wrapper {
position: absolute;
ul {
list-style: none;
margin: 0px;
padding: 0px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
margin: 0;
border: 1px solid #2a2a2a;
border-radius: 3px;
height: auto;
min-height: 50px;
line-height: 1.5em;
width:80px;
box-sizing: border-box;
}
.menu{
display: none;
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div{
div {
padding: 5px;
}
}
.submenu{
position: absolute;
left: 77px;
top: -1px;
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div{
padding: 5px;
}
}
}
}
.menu li:hover{
background-color: #ff5722;
.submenu{
display:block;
}
}
.menu_active{
cursor: pointer;
}
.menu_disabled{
cursor: not-allowed;
}
}
.info-visit{
position: absolute;
left:50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper{
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper{
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding:0 10px;
font-size: 14px;
.menu li:hover {
background-color: #ff5722;
.submenu {
display: block;
}
}
.info-cd{
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
top: 5px;
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
background-color: #3f3f3f;
text-align: center;
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
background-color: #00000057;
color: #fff;
padding: 0 10px;
font-size: 14px;
}
}
.info-cd {
position: absolute;
left: 10px;
top: 5px;
@@ -2032,6 +2037,7 @@ export default {
font-size: 18px;
cursor: pointer;
}
.info-series {
position: absolute;
left: 10px;
@@ -2041,6 +2047,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-image {
position: absolute;
left: 10px;
@@ -2060,6 +2067,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-instance {
position: absolute;
right: 15px;
@@ -2092,6 +2100,7 @@ export default {
margin: 10px;
cursor: default;
}
.menu__item:hover {
color: #ff0000;
}
@@ -2111,11 +2120,12 @@ li:hover {
background-color: #e0e0e2;
color: white;
}
.msg-div {
position: absolute;
z-index: 10;
background-color: rgba(255, 255, 255, 0.5);
color: #000;
padding: 5px 20px;
}
position: absolute;
z-index: 10;
background-color: rgba(255, 255, 255, 0.5);
color: #000;
padding: 5px 20px;
}
</style>
@@ -91,7 +91,7 @@
</div>
</template>
<script>
import { submitDicomVisitTask } from '@/api/trials'
import { submitDicomVisitTask, verifyVisitTaskQuestions } from '@/api/trials'
import { getCustomTag, submitCustomTag, resetReadingTask } from '@/api/reading'
import { setSkipReadingCache } from '@/api/reading'
import DicomEvent from './../components/DicomEvent'
@@ -358,6 +358,7 @@ export default {
async handleConfirm() {
var res = await this.$refs['QuestionsPreview'].handleSave(false)
if (res) {
await verifyVisitTaskQuestions({ visitTaskId: this.visitTaskId })
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
@@ -353,7 +353,7 @@ export default {
validatorNumberInput(rule, value, callback) {
let reg = new RegExp(/^(?:-?(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)|NE)$/, 'g')
if (value === '') {
callback(new Error(this.$t('common:ruleMessage:specify')));
callback();
} else {
if (!reg.test(value)) {
callback(new Error(this.$t('trials:reading:ruleMessage:validatorNumberInput')));
@@ -261,7 +261,7 @@ export default {
validatorNumberInput(rule, value, callback) {
let reg = new RegExp(/^(?:-?(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)|NE)$/, 'g')
if (value === '') {
callback(new Error(this.$t('common:ruleMessage:specify')));
callback();
} else {
if (!reg.test(value)) {
callback(new Error(this.$t('trials:reading:ruleMessage:validatorNumberInput')));
@@ -483,7 +483,7 @@ export default {
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${param.file.name}`, file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${param.file.name}`, file)
console.log(res)
this.fileList.push({ name: param.file.name, path: this.$getObjectName(res.url), url: this.$getObjectName(res.url) })
this.urls.push(this.$getObjectName(res.url))
@@ -5,42 +5,22 @@
<div class="related-study-wrapper">
<div class="left">
<div class="visit-name-wrapper">
<div
v-if="visitTaskList.length > 0"
style="display: flex; flex-direction: row"
>
<div
v-for="s in visitTaskList"
:key="s.VisitTaskId"
class="visit-item"
:class="{
'visit-item-active': activeTaskVisitId == s.VisitTaskId,
}"
@click.prevent="handleClick(s)"
>
<div v-if="visitTaskList.length > 0" style="display: flex; flex-direction: row">
<div v-for="s in visitTaskList" :key="s.VisitTaskId" class="visit-item" :class="{
'visit-item-active': activeTaskVisitId == s.VisitTaskId,
}" @click.prevent="handleClick(s)">
{{ s.TaskBlindName }}
</div>
</div>
</div>
</div>
<div class="right">
<div
v-for="s in visitTaskList"
v-show="activeTaskVisitId === s.VisitTaskId"
:key="s.VisitTaskId"
class="study-wrapper"
>
<StudyList
v-if="selectArr.includes(s.VisitTaskId)"
:ref="s.VisitTaskId"
:visit-task-id="s.VisitTaskId"
:trial-id="trialId"
:subject-visit-id="s.VisitId"
:task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
@loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms"
/>
<div v-for="s in visitTaskList" v-show="activeTaskVisitId === s.VisitTaskId" :key="s.VisitTaskId"
class="study-wrapper">
<StudyList v-if="selectArr.includes(s.VisitTaskId)" :ref="s.VisitTaskId" :visit-task-id="s.VisitTaskId"
:trial-id="trialId" :subject-visit-id="s.VisitId" :task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo" @loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms" />
</div>
</div>
</div>
@@ -48,21 +28,12 @@
</div>
<div class="dicom-viewer">
<div class="container">
<DicomViewer
v-if="activeTaskVisitId"
ref="dicomViewer"
:trial-id="trialId"
:trial-reading-criterion-id="trialReadingCriterionId"
:subject-id="subjectId"
:is-show="isShow"
:is-exists-clinical-data="isExistsClinicalData"
:is-exists-no-dicom-file="isExistsNoDicomFile"
<DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :trial-id="trialId"
:trial-reading-criterion-id="trialReadingCriterionId" :subject-id="subjectId" :is-show="isShow"
:is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
:isExistsManual="isExistsManual"
:IseCRFShowInDicomReading="IseCRFShowInDicomReading"
@previewCD="previewCD"
/>
:is-reading-task-view-in-order="isReadingTaskViewInOrder" :isExistsManual="isExistsManual"
:IseCRFShowInDicomReading="IseCRFShowInDicomReading" @previewCD="previewCD" />
</div>
</div>
</div>
@@ -77,6 +48,7 @@ import { mapGetters } from "vuex";
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import { workSpeedclose } from "@/utils"
// import metaDataProvider from '@/utils/metaDataProvider'
// cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 });
var config = {
@@ -261,6 +233,7 @@ export default {
DicomEvent.$off("addNoneDicomMeasureData");
DicomEvent.$off("selectSeries");
window.removeEventListener('beforeunload', e => { cornerstone.imageCache.purgeCache() })
workSpeedclose(true)
},
methods: {
async getVisitInfo() {
@@ -547,7 +520,7 @@ export default {
if (obj) {
let index = Math.floor(
obj.imageIds.length *
((baseSeries.imageIdIndex + 1) / baseSeries.imageIds)
((baseSeries.imageIdIndex + 1) / baseSeries.imageIds)
);
obj.imageIdIndex = index > 0 ? index - 1 : 0;
}
@@ -636,7 +609,7 @@ export default {
// );
let frame = this.visitTaskList[index].MeasureData[idx].MeasureData.frame
// let filterStr = series.isExistMutiFrames ? `frame=${frame}&instanceId=${instanceId}` : `instanceId=${instanceId}`
let n = series.instanceInfoList.findIndex(k=>k.Id === instanceId)
let n = series.instanceInfoList.findIndex(k => k.Id === instanceId)
let filterStr = ''
if (n > -1 && series.isExistMutiFrames) {
if (series.instanceInfoList[n].NumberOfFrames > 0) {
@@ -683,12 +656,15 @@ export default {
},
cornerstoneimageloadprogress(e) {
const imageId = e.detail.imageId
console.log(imageId,'imageId')
const params = {}
const searchParams = new URLSearchParams(imageId.split('?')[1])
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
params.percentComplete = e.detail.percentComplete
params.FileTotal = e.detail.total
params.imageId = imageId
store.dispatch('reading/setImageLoadedProgress', params)
}
},
@@ -699,22 +675,27 @@ export default {
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,
.el-table__expanded-cell {
background-color: #000;
@@ -728,19 +709,23 @@ export default {
color: #fff;
border-color: #444444;
}
::v-deep .el-table__body tr > td {
::v-deep .el-table__body tr>td {
background-color: #000 !important;
color: #fff;
border-color: #444444;
}
::v-deep .el-table__body tr:hover > td {
::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;
@@ -753,10 +738,12 @@ export default {
justify-content: flex-start;
background-color: #000;
box-sizing: border-box;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
@@ -773,6 +760,7 @@ export default {
box-sizing: border-box;
}
}
.dicom-list {
width: 200px;
padding: 5px 0px;
@@ -785,6 +773,7 @@ export default {
border: 1px solid #727272;
// border: 1px solid #ccc;
}
.el-tabs {
box-sizing: border-box;
padding: 0 5px;
@@ -793,24 +782,29 @@ export default {
flex-direction: column;
// justify-content: flex-start;
border: 1px solid #727272;
.el-tabs__item {
color: #fff;
}
::v-deep .el-tabs__header {
height: 55px;
margin: 0px;
box-sizing: border-box;
}
::v-deep .el-tabs__content {
flex: 1;
margin: 0px;
overflow-y: auto;
box-sizing: border-box;
}
::v-deep .el-tabs__item {
color: #fff;
}
}
.dicom-desc {
font-weight: bold;
font-size: 13px;
@@ -819,23 +813,28 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.ps {
overflow-anchor: none;
touch-action: auto;
}
.series-active {
background-color: #607d8b !important;
border: 1px solid #607d8b !important;
}
::v-deep .el-progress__text {
color: #ccc;
font-size: 12px;
}
.series {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.series-wrapper {
display: flex;
flex-direction: row;
@@ -848,9 +847,11 @@ export default {
border-radius: 2px;
border: 1px solid #404040;
background-color: #3a3a3a;
.el-progress__text {
display: none;
}
.el-progress-bar {
padding-right: 0px;
}
@@ -861,8 +862,10 @@ export default {
border: 2px solid #252525;
cursor: pointer;
}
.image-desc {
vertical-align: top;
p {
width: 100px;
white-space: nowrap;
@@ -876,6 +879,7 @@ export default {
}
}
}
.related-study-wrapper {
box-sizing: border-box;
height: 100%;
@@ -891,6 +895,7 @@ export default {
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
.visit-name-wrapper {
position: absolute;
top: 5px;
@@ -906,6 +911,7 @@ export default {
// flex-direction: row;
// align-content: flex-start;
}
.visit-item {
margin-left: 10px;
white-space: nowrap;
@@ -918,17 +924,20 @@ export default {
cursor: pointer;
// margin-left: 10px;
}
.visit-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.right {
width: 170px;
flex: 1;
height: 100%;
border-left: 1px solid #4a4a4a;
color: #d5d5d5;
.study-wrapper {
width: 100%;
height: 100%;
@@ -85,7 +85,7 @@
</template>
</el-select>
<span
v-else-if="questionForm[scope.row.QuestionId] instanceof Array && scope.row.Type === 'select' || scope.row.Type === 'radio'">
v-else-if="questionForm[scope.row.QuestionId] instanceof Array && (scope.row.Type === 'select' || scope.row.Type === 'radio')">
{{ questionForm[scope.row.QuestionId][scope.row.xfIndex][scope.row.TableQuestionId] }}
</span>
<el-select
@@ -407,7 +407,8 @@ export default {
// this.$set(v, 'xfIndex', i)
}
if (v.Type === 'number') {
this.$set(this.questionForm, v.QuestionId, v.Answers[this.visitTaskId] === '' ? parseFloat(0).toFixed(this.digitPlaces) : v.Answers[this.visitTaskId])
// this.$set(this.questionForm, v.QuestionId, v.Answers[this.visitTaskId] === '' ? parseFloat(0).toFixed(this.digitPlaces) : v.Answers[this.visitTaskId])
this.$set(this.questionForm, v.QuestionId, v.Answers[this.visitTaskId])
}
if (v.Childrens.length > 0) {
this.setChild(v.Childrens)
@@ -425,7 +426,8 @@ export default {
this.$set(this.questionForm, i.QuestionId, tableAnswers)
}
if (i.Type === 'number') {
this.$set(this.questionForm, i.QuestionId, i.Answers[this.visitTaskId] === '' ? parseFloat(0).toFixed(this.digitPlaces) : i.Answers[this.visitTaskId])
// this.$set(this.questionForm, i.QuestionId, i.Answers[this.visitTaskId] === '' ? parseFloat(0).toFixed(this.digitPlaces) : i.Answers[this.visitTaskId])
this.$set(this.questionForm, i.QuestionId, i.Answers[this.visitTaskId])
}
if (i.Childrens && i.Childrens.length > 0 && i.Type !== 'table' && i.Type !== 'basicTable') {
this.setChild(i.Childrens)
@@ -120,7 +120,7 @@ name: "CustomizeReportPageUpload",
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${param.file.name}`, file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${param.file.name}`, file)
console.log(res)
this.fileList.push({ name: param.file.name, path: this.$getObjectName(res.url), url: this.$getObjectName(res.url)})
this.urls.push(this.$getObjectName(res.url))
@@ -346,6 +346,7 @@ export default {
localStorage.setItem('digitPlaces', res.Result.DigitPlaces)
localStorage.setItem('IsExistUnprocessedFeedback', res.Result.IsExistUnprocessedFeedback)
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
sessionStorage.setItem('taskInfo', JSON.stringify(res.Result))
this.readingCategory = res.Result.ReadingCategory
this.readingVersionEnum = res.Result.ReadingVersionEnum
this.questionFormChangeState = false
@@ -1,5 +1,5 @@
<template>
<div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" element-loading-text="Loading..."
<div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
@mouseleave="sliderMouseleave" :style="{ color: series.Modality === 'PT' || isMip ? '#666' : '#ddd' }">
<div v-if="series && taskInfo" class="left-top-text">
@@ -190,7 +190,7 @@ export default {
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.$nextTick(() => {
@@ -786,7 +786,12 @@ export default {
e.stopPropagation()
e.preventDefault()
}
}
},
computed: {
NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}`
}
},
}
</script>
<style lang="scss" scoped>
@@ -280,11 +280,11 @@
>
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
<!-- 更多 -->
<!-- 更多 :class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']" -->
<div
v-if="criterionType === 0"
:title="$t('trials:reading:button:more')"
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']"
:class="['tool-item']"
@click.stop="showPanel($event)"
@mouseleave="toolMouseout"
>
@@ -297,7 +297,8 @@
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<ul v-if="readingTaskState < 2" style="width:100px;">
<!--v-if="readingTaskState < 2"-->
<ul style="width:100px;">
<li v-for="i in customizeStandards" :key="i.toolName" style="text-align:left;">
<a href="#" @click.prevent="setMoreToolActive(i.toolName)">
<svg-icon :icon-class="i.icon" class="svg-icon" style="margin-right: 5px;" />
@@ -632,6 +633,7 @@ import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import { getNetWorkSpeed, setNetWorkSpeedSizeAll, workSpeedclose } from "@/utils"
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
@@ -783,8 +785,9 @@ export default {
loadingText: null,
toolNames: ['Length', 'Bidirectional', 'RectangleROI', 'ArrowAnnotate', 'CircleROI', 'Eraser'],
// resetAnnotation: false , // 使
saveCustomAnnotationTimer: null,
//
//
downloadImageVisible: false,
uploadImageVisible: false,
uploadSubjectId: null,
@@ -871,7 +874,7 @@ export default {
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.isReadingTaskViewInOrder = this.taskInfo.IsReadingTaskViewInOrder
this.criterionType = this.taskInfo.CriterionType
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
@@ -880,6 +883,7 @@ export default {
this.tools = getCustomizeStandardsTools(this.taskInfo.ReadingToolList)
const toolNames = this.tools.map(i => i.toolName)
this.customizeStandards = config.customizeStandards.filter(item => !toolNames.includes(item.toolName))
console.log(this.customizeStandards,'this.customizeStandards')
} else {
this.tools = getTools(this.criterionType)
}
@@ -897,7 +901,7 @@ export default {
this.initLoader()
this.getWwcTpl()
})
this.getTrialCriterion()
DicomEvent.$on('isCanActiveNoneDicomTool', data => {
this.open.postMessage({ type: 'isCanActiveNoneDicomTool', data: data }, window.location)
})
@@ -1053,6 +1057,7 @@ export default {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
i.MeasureData.markId = i.MarkId
}
annotationUIDs.push(i.MeasureData.annotationUID)
}
@@ -1360,6 +1365,16 @@ export default {
const isCriticalSequence = this.visitTaskList[taskIndex].StudyList[studyIndex].IsCriticalSequence
const keyImages = this.visitTaskList[taskIndex].KeyImages
const series = this.visitTaskList[taskIndex].StudyList[studyIndex].SeriesList[seriesIndex]
if (series && series.length > 0) {
let file = series.find(item => item.ImageId === imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSizeAll(percentComplete, detail.total, imageId)
}
}
if (percentComplete === 100) {
workSpeedclose()
}
this.setImageLoadedProgress(series, percentComplete, instanceId)
if (!isCriticalSequence && series.IsBeMark && keyImages.length > 0) {
const i = keyImages.findIndex(i => i.Id === params.instanceId)
@@ -1497,14 +1512,32 @@ export default {
this.setToolsPassive()
},
annotationRemovedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
try{
if (!annotation) return
if (this. readingTaskState === 2 && !annotation.data.label) return false
if (this.readingTaskState === 2) {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
} else {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
} catch (e) {
cornerstoneTools.annotation.state.addAnnotation(annotation)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
console.log(e)
}
},
async customAnnotationCompletedListener(e) {
@@ -1540,8 +1573,16 @@ export default {
if (this.isNumber(operateStateEnum)) {
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].bindAnnotationToQuestion(annotation)
} else {
setTimeout(()=>{this.saveCustomAnnotation(annotation)},500)
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},500)
}
} else {
// if (this.isNumber(operateStateEnum)) {
// this.removeAnnotation(annotation)
// }
}
}
@@ -1572,8 +1613,10 @@ export default {
params.Id = annotation.id ? annotation.id : ''
params.MeasureData = JSON.stringify(measureData)
params.NumberOfFrames = annotation.numberOfFrames
params.MarkId = annotation.annotationUID
const res = await submitCustomTag(params)
annotation.id = res.Result
annotation.markId = params.MarkId
})
} catch (e) {
@@ -1600,7 +1643,11 @@ export default {
if (isBound || this.isNumber(operateStateEnum)) {
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].updateAnnotationToQuestion(annotation)
} else {
this.saveCustomAnnotation(annotation)
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},1000)
}
}
this.setToolsPassive()
@@ -1610,6 +1657,7 @@ export default {
try {
// if ( this.resetAnnotation && this.isFusion ) return false
if (!annotation) return false
if (this.readingTaskState === 2 && !annotation.data.label) return false
if (this.readingTaskState === 2) {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
@@ -1636,8 +1684,12 @@ export default {
annotation.data.label +
'?'
)
if (annotation.id) {
deleteCustomTag(annotation.id)
if (annotation.markId) {
let res = await deleteCustomTag(annotation.markId)
if (!res.IsSuccess) throw ''
}else if (annotation.id) {
let res = await deleteCustomTag(annotation.id)
if (!res.IsSuccess) throw ''
}
}
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
@@ -1706,6 +1758,7 @@ export default {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
i.MeasureData.markId = i.MarkId
}
annotationUIDs.push(i.MeasureData.annotationUID)
}
@@ -1731,6 +1784,7 @@ export default {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
i.MeasureData.markId = i.MarkId
}
}
return i
@@ -1919,7 +1973,7 @@ export default {
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${parseFloat(area)} ${areaUnit}`
: `Area: ${parseFloat(area).toFixed(2)} ${areaUnit}`
textLines.push(areaLine)
}
@@ -2007,7 +2061,7 @@ export default {
},
//
setAnnotateToolActive(toolName) {
if (this.readingTaskState === 2) return
// if (this.readingTaskState === 2) return
const toolObj = this.tools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
@@ -2060,7 +2114,7 @@ export default {
}
},
setMoreToolActive(toolName) {
if (this.readingTaskState === 2) return
// if (this.readingTaskState === 2) return
this.setToolsPassive()
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
@@ -2097,7 +2151,10 @@ export default {
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
const isCurrentTask = series.TaskInfo.IsCurrentTask
const readingTaskState = this.readingTaskState
if (!isCurrentTask || readingTaskState >= 2) {
if (isCurrentTask && readingTaskState >= 2) {
this.tools[i].isDisabled = false
e.target.style.cursor = 'pointer'
} else if (!isCurrentTask || readingTaskState >= 2) {
this.tools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
@@ -2914,9 +2971,25 @@ export default {
try {
const that = this
//
const { value } = await this.$prompt(this.$t('trials:noneDicom:message:msg1'), '', {
let message = this.$t('trials:noneDicom:message:msg1')
// if (isShowCancelButton) {
// message = `<div><p>${this.$t('trials:noneDicom:message:msg1')}</p><p style='font-size:12px' class='customPromptTip'>${this.$t('trials:noneDicom:message:saveTip')}</p></div>`
// }
const { value } = await this.$prompt( message, '', {
showClose: false,
showCancelButton: isShowCancelButton,
cancelButtonText: isShowCancelButton ? this.$t('trials:reading:button:temporarySave') : this.$t('common:button:cancel'),
confirmButtonText: isShowCancelButton ? this.$t('trials:reading:button:enduringSave') : this.$t('trials:reading:button:relevancy'),
// showCancelButton: isShowCancelButton,
// dangerouslyUseHTMLString: isShowCancelButton,
inputValidator: (res) => {
if (!res) {
return isShowCancelButton ? this.$t('trials:noneDicom:message:saveTip') : this.$t('trials:noneDicom:message:saveTipByRelevancy')
}
},
inputErrorMessage: this.$t('trials:noneDicom:message:saveTip'),
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
const value = instance.inputValue
@@ -2933,7 +3006,8 @@ export default {
}
})
return value
} catch {
} catch(err) {
console.log(err)
return null
}
},
@@ -3172,9 +3246,9 @@ export default {
try {
file = this.convertBase64ToBlob(file)
const trialId = this.$route.query.trialId
const taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
const taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -3216,6 +3290,11 @@ export default {
DicomEvent.$off('isCanActiveNoneDicomTool')
DicomEvent.$off('removeNoneDicomMeasureData')
DicomEvent.$off('addNoneDicomMeasureData')
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
workSpeedclose(true)
}
}
</script>
@@ -235,7 +235,7 @@ export default {
mounted() {
this.trialId = this.$route.query.trialId
this.visitTaskId = this.visitInfo.VisitTaskId
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.isBaseLineTask = this.taskInfo.IsBaseLine
this.criterionType = this.taskInfo.CriterionType
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
@@ -1325,7 +1325,7 @@ export default {
file = this.convertBase64ToBlob(file)
const trialId = this.$route.query.trialId
const subjectId = this.taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -1489,6 +1489,7 @@ export default {
this.getOuterQuestions()
this.getTableQuestions()
this.$emit('resetAnnotations', this.visitTaskId)
DicomEvent.$emit('removeNoneDicomMeasureData', null)
}
loading.close()
} catch (e) {
@@ -1745,9 +1746,9 @@ export default {
if (this.activeName) {
return this.checkToolCanActive(toolName)
} else {
if (this.isBaseLineTask && (toolName === 'Bidirectional' || toolName === 'Length' || toolName === 'RectangleRoi')) {
if (this.isBaseLineTask && (toolName === 'Bidirectional' || toolName === 'Length' || toolName === 'RectangleRoi' || toolName === 'RectangleROI')) {
return { isCanActiveTool: true, reason: '' }
} else if (!this.isBaseLineTask && (toolName === 'ArrowAnnotate' || toolName === 'Bidirectional' || toolName === 'Length' || toolName === 'RectangleRoi')) {
} else if (!this.isBaseLineTask && (toolName === 'ArrowAnnotate' || toolName === 'Bidirectional' || toolName === 'Length' || toolName === 'RectangleRoi' || toolName === 'RectangleROI')) {
return { isCanActiveTool: true, reason: '' }
} else if (this.isBaseLineTask && toolName === 'ArrowAnnotate') {
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg54') }
@@ -1770,10 +1771,10 @@ export default {
} else if (innerForm.LesionType === 0 && isLymphNodes === 0 && toolName !== 'Length') {
// '使'
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg7') }
} else if (innerForm.LesionType === 1 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'RectangleRoi') {
} else if (innerForm.LesionType === 1 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'RectangleRoi' && toolName !== 'RectangleROI') {
// '使'
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg8') }
} else if (innerForm.LesionType === 1 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'RectangleRoi') {
} else if (innerForm.LesionType === 1 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'RectangleRoi' && toolName !== 'RectangleROI') {
// '使'
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg9') }
} else if (toolName === 'ArrowAnnotate') {
@@ -1788,10 +1789,10 @@ export default {
} else if (innerForm.LesionType === 0 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'ArrowAnnotate') {
// 使
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg11') }
} else if (innerForm.LesionType === 1 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'ArrowAnnotate' && toolName !== 'RectangleRoi') {
} else if (innerForm.LesionType === 1 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'ArrowAnnotate' && toolName !== 'RectangleRoi' && toolName !== 'RectangleROI') {
// 使
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg52') }
} else if (innerForm.LesionType === 1 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'ArrowAnnotate' && toolName !== 'RectangleRoi') {
} else if (innerForm.LesionType === 1 && isLymphNodes === 0 && toolName !== 'Length' && toolName !== 'ArrowAnnotate' && toolName !== 'RectangleRoi' && toolName !== 'RectangleROI') {
// 使
return { isCanActiveTool: false, reason: this.$t('trials:reading:warnning:msg53') }
} else if (innerForm.LesionType === 2 && isLymphNodes === 1 && toolName !== 'Bidirectional' && toolName !== 'ArrowAnnotate') {
@@ -327,7 +327,7 @@ export default {
},
created() { this.getTableHeight() },
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.criterionType = this.taskInfo.CriterionType
this.visitTaskId = this.taskInfo.VisitTaskId
this.isBaselineTask = this.taskInfo.IsBaseLine
@@ -367,7 +367,7 @@ export default {
getTableHeight() {
this.height = window.innerHeight - 170
},
async getReportInfo(IsCalculate) {
async getReportInfo(IsCalculate = true) {
this.loading = true
try {
var params = {
@@ -153,7 +153,7 @@ export default {
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.studyList = this.visitTaskInfo.StudyList
@@ -3,7 +3,7 @@
ref="viewport"
class="viewport-wrapper"
v-loading="loading"
element-loading-text="Loading..."
:element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)"
@mouseup="sliderMouseup"
@mousemove="sliderMousemove"
@@ -174,7 +174,7 @@ export default {
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.$nextTick(() => {
@@ -539,7 +539,12 @@ export default {
e.stopPropagation()
e.preventDefault()
}
}
},
computed: {
NSTip() {
return `NS: ${this.$store.state.trials.uploadTip}`
}
},
}
</script>
<style lang="scss" scoped>
@@ -1,6 +1,6 @@
<template>
<div class="visit-review-container">
<el-tabs v-model="activeName">
<el-tabs v-model="activeName" @tab-click="handleClick">
<!-- 阅片 -->
<el-tab-pane v-if="taskInfo" :label="$t('trials:reading:tabTitle:review')" name="read">
<read-page ref="readPage" :reading-tool="readingTool" />
@@ -12,11 +12,13 @@
name="report"
>
<report-page
v-if="activeName === 'report' && taskInfo.CriterionType !== 0"
v-if="taskInfo.CriterionType !== 0"
ref="reportPage"
@setReadingTaskState="setReadingTaskState"
/>
<customize-report-page
v-if="activeName === 'report' && taskInfo.CriterionType === 0"
v-if="taskInfo.CriterionType === 0"
ref="reportPage"
@setReadingTaskState="setReadingTaskState"
/>
</el-tab-pane>
@@ -47,12 +49,16 @@ export default {
}
},
mounted() {
console.log(this.readingTool)
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
},
methods: {
setReadingTaskState(state) {
this.$refs['readPage'].setReadingTaskState(state)
},
handleClick(tab) {
if (tab.name === 'report') {
this.$refs['reportPage'].getReportInfo()
}
}
}
}
@@ -21,7 +21,7 @@
<el-table-column :prop="item.Id" :label="item.QuestionName" :key="item.Id"
v-for="item of question.TableQuestions.Questions" show-overflow-tooltip :render-header="renderHeader">
<template slot-scope="scope">
<span v-if="item.Type === 'upload'">
<span v-if="item.Type === 'upload' || item.Type === 'screenshot'">
{{ scope.row[item.Id] === '' ? '' : scope.row[item.Id] ? scope.row[item.Id].split('|').length : '' }}
</span>
<span v-else-if="item.Type === 'number'">
@@ -82,7 +82,8 @@
validator: question.Type === 'number' && !question.TypeValue ? validatorNumberInput : (rule, value, callback) => { callback() },
trigger: ['blur', 'change']
}
]" :class="[question.Type === 'group' ? 'mb' : question.Type === 'upload' ? 'uploadWrapper' : '']">
]"
:class="[question.Type === 'group' ? 'mb' : question.Type === 'upload' || question.Type === 'screenshot' ? 'uploadWrapper' : '']">
<!-- 输入框 -->
<el-input v-if="question.Type === 'input'" v-model="questionForm[question.Id]"
:disabled="question.TableQuestionType === 2 || readingTaskState === 2" />
@@ -161,12 +162,13 @@
</el-select>
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;">
<el-input type="number" @change="(val) => { formItemNumberChange(val, question) }"
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)"
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
v-model="questionForm[question.Id]"
:title="questionsMarkStatus[question.Id] ? questionsMarkStatus[question.Id].OrderMarkName : ''"
:title="questionsMarkStatus[question.Id] ? questionsMarkStatus[question.Id].OrderMarkName : question.Remark"
:disabled="(questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1"
style="width: 150px;">
style="width: 150px;margin-right: 5px;">
<template v-if="question.Unit !== 0" slot="append">
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
</template>
@@ -236,18 +238,18 @@
</el-input>
</template>
<!-- 上传图像 -->
<el-upload v-if="question.Type === 'upload'" :disabled="readingTaskState === 2" action
:accept="question.FileType" :limit="question.ImageCount === 0 ? 100 : question.ImageCount"
:on-preview="handlePictureCardPreview"
:before-upload="(file) => { return handleBeforeUpload(file, question.FileType) }"
<el-upload v-if="question.Type === 'upload' || question.Type === 'screenshot'"
:disabled="readingTaskState === 2" action :accept="question.FileType"
:limit="question.ImageCount === 0 ? 100 : question.ImageCount" :on-preview="handlePictureCardPreview"
:before-upload="(file) => { return handleBeforeUpload(file, question.FileType, question.Type) }"
:http-request="uploadScreenshot" :on-remove="handleRemove" :file-list="fileList"
:class="{ disabled: question.ImageCount === 0 ? false : fileList.length >= question.ImageCount }">
<el-button slot="default" class="el-icon-plus" v-if="readingTaskState < 2">
{{ this.$t('common:button:upload') }}
</el-button>
</el-upload>
<viewer v-if="question.Type === 'upload' && imgVisible" :ref="imageUrl" style="margin:0 10px;"
:images="[imageUrl]">
<viewer v-if="(question.Type === 'upload' || question.Type === 'screenshot') && imgVisible" :ref="imageUrl"
style="margin:0 10px;" :images="[imageUrl]">
<img v-show="false" crossorigin="anonymous" :src="imageUrl" alt="Image">
</viewer>
</el-form-item>
@@ -274,23 +276,28 @@
</base-model> -->
<!-- 预览文件 -->
<el-dialog v-if="previewVisible" :visible.sync="previewVisible" :title="$t('common:button:preview')"
<!-- <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>
</el-dialog> -->
</div>
</template>
<script>
import { deleteReadingRowAnswer, getQuestionCalculateRelation, submitTableQuestion, deleteCustomTag } from '@/api/trials'
import QuestionTableFormItem from './QuestionTableFormItem'
import BaseModel from '@/components/BaseModel'
import PreviewFile from '@/components/PreviewFile/index'
// import PreviewFile from '@/components/PreviewFile/index'
import { mapGetters } from 'vuex'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
export default {
name: 'QuestionFormItem',
components: { QuestionTableFormItem, BaseModel, PreviewFile },
components: {
QuestionTableFormItem,
BaseModel,
// PreviewFile
},
props: {
isBaseline: {
type: Boolean,
@@ -353,9 +360,9 @@ export default {
digitPlaces: 2,
CalculationTabelList: [],
classArr: [],
previewVisible: false,
currentPath: '',
currentType: '',
// previewVisible: false,
// currentPath: '',
// currentType: '',
markTableQuestions: [],
parentQsId: ''
}
@@ -439,12 +446,14 @@ export default {
// }
// }
// }, 300)
if (this.question.Type === 'upload') {
if (this.question.Type === 'upload' || this.question.Type === 'screenshot') {
if (this.questionForm[this.question.Id]) {
this.urls = this.questionForm[this.question.Id].split('|')
this.fileList = []
this.urls.map((url, index) => {
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${index + 1}`, url: `${url}` })
var index = url.lastIndexOf('.')
var suffix = url.substring(index + 1, url.length)
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${index + 1}(${suffix})`, url: `${url}` })
})
}
}
@@ -461,6 +470,22 @@ export default {
})
}
}
DicomEvent.$on('openAddTableCol', (data) => {
let { RowId } = data
if (this.questionForm[this.question.Id] && Array.isArray(this.questionForm[this.question.Id]) && this.questionForm[this.question.Id].length > 0) {
let index = this.questionForm[this.question.Id].findIndex(item => item.RowId === RowId)
if (!!~index) {
this.openAddTableCol(this.question, index)
}
}
})
DicomEvent.$on('operateImageMarker', (data) => {
let { operateStateEnum, QuestionId, picturePath } = data
if (this.question.Id === QuestionId) {
this.operateImageMarker({ operateStateEnum, question: this.question, picturePath })
}
})
},
methods: {
numberInput(id) {
@@ -469,7 +494,7 @@ export default {
validatorNumberInput(rule, value, callback) {
let reg = new RegExp(/^(?:-?(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)|NE)$/, 'g')
if (value === '') {
callback(new Error(this.$t('common:ruleMessage:specify')));
callback();
} else {
if (!reg.test(value)) {
callback(new Error(this.$t('trials:reading:ruleMessage:validatorNumberInput')));
@@ -492,7 +517,7 @@ export default {
}
},
handleMarkedQsBlur(value, a, b, question) {
this.$set(a, b, parseFloat(value).toFixed(this.digitPlaces))
this.$set(a, b, !isNaN(parseFloat(value)) ? parseFloat(value).toFixed(this.digitPlaces) : value)
this.$emit('operateImageMarker', { operateStateEnum: 6, question })
},
async deleteTableCol(row, index) {
@@ -694,7 +719,7 @@ export default {
}
}
if (isExistUnSaved) {
if (this.addOrEdit.type = 'add') {
if (this.addOrEdit.type === 'add') {
//
const confirm = await this.$confirm(this.$t("dicom3D:CustomizeQuestionFormItem:confirm:markNoSaveAndclose"), {
type: 'warning',
@@ -1087,6 +1112,7 @@ export default {
// },
async operateImageMarker(obj) {
// if (obj.question.IsTableQuestion && this.question.Id !== obj.question.ParentQsId) return
if (obj.operateStateEnum === 5 && this.questionForm[obj.question.Id] && isNaN(parseFloat(this.questionForm[obj.question.Id])) && this.questionForm[obj.question.Id] !== 'NE') return false
if (obj.operateStateEnum === 4) {
//
this.$set(this.questionForm, obj.question.Id, null)
@@ -1193,7 +1219,7 @@ export default {
}
}
if (isExistUnSaved) {
if (this.addOrEdit.type = 'add') {
if (this.addOrEdit.type === 'add') {
//
const confirm = await this.$confirm(this.$t("dicom3D:CustomizeQuestionFormItem:confirm:markNoSaveAndclose"), {
type: 'warning',
@@ -1229,13 +1255,22 @@ export default {
text: 'Loading',
spinner: 'el-icon-loading'
})
var fileName = param.file.name
let file = await this.fileToBlob(param.file)
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.$set(this.QuestionsForm, this.question.Id, this.urls.length > 0 ? this.urls.join('|') : '')
try {
var fileName = param.file.name
var index = fileName.lastIndexOf('.')
var suffix = fileName.substring(index + 1, fileName.length)
let file = await this.fileToBlob(param.file)
// let res = await this.OSSclient.put(`/${this.$route.query.trialId}/Customize/${this.visitTaskId}/${fileName}`, file)
const taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
let res = await this.OSSclient.put(`/${this.$route.query.trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}`, file)
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${this.fileList.length + 1}(${suffix})`, 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.$set(this.QuestionsForm, this.question.Id, this.urls.length > 0 ? this.urls.join('|') : '')
} catch (err) {
console.log(err)
}
loading.close()
// uploadReadingAnswerImage(this.$route.query.trialId, this.visitTaskId, formData).then(res => {
// if (res.IsSuccess) {
@@ -1248,7 +1283,10 @@ export default {
// loading.close()
// })
},
handleBeforeUpload(file, accept) {
handleBeforeUpload(file, accept, type) {
if (type === 'screenshot') {
accept = '.png,.jpeg,.jpg'
}
//
if (this.checkFileSuffix(file.name, accept) || accept === '-1') {
return true
@@ -1271,11 +1309,27 @@ export default {
handlePictureCardPreview(file) {
var suffix = file.url.substring(file.url.lastIndexOf(".") + 1)
suffix = suffix ? suffix.toLowerCase() : ''
if (suffix === 'doc' || suffix === 'docx' || suffix === 'pdf') {
if (['ppt',
'pptx',
'doc',
'docx',
'xls',
'xlsx'].includes(suffix)) {
// window.open(this.OSSclientConfig.basePath + file.url,'_blank')
this.currentPath = file.url
this.currentType = suffix
this.previewVisible = true
this.$onlyOffice({
path: file.url,
type: suffix,
title: file.name
})
// this.currentPath = file.url
// this.currentType = suffix
// this.previewVisible = true
} else if (['pdf'].includes(suffix)) {
this.$preview({
path: file.url,
type: 'pdf',
title: file.name,
})
} else {
this.imageUrl = this.OSSclientConfig.basePath + file.url
this.imgVisible = true
@@ -55,7 +55,7 @@
<script>
import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, getQuestionCalculateRelation, saveTaskQuestion } from '@/api/trials'
import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, verifyVisitTaskQuestions, getQuestionCalculateRelation, saveTaskQuestion } from '@/api/trials'
import { setSkipReadingCache, resetReadingTask, saveTableQuestionMark } from '@/api/reading'
import const_ from '@/const/sign-code'
import QuestionFormItem from './QuestionFormItem'
@@ -114,7 +114,7 @@ export default {
window.addEventListener('message', this.receiveMsg)
this.trialId = this.$route.query.trialId
this.visitTaskId = this.visitInfo.VisitTaskId
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.isBaseLineTask = this.taskInfo.IsBaseLine
this.criterionId = this.taskInfo.TrialReadingCriterionId
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
@@ -242,6 +242,8 @@ export default {
async handleSubmit() {
const valid = await this.$refs['questions'].validate()
if (!valid) return
await this.handleSave(false)
await verifyVisitTaskQuestions({ visitTaskId: this.visitTaskId })
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
@@ -577,7 +579,6 @@ export default {
try {
const answers = [{ id: obj.question.Id, answer: this.questionForm[obj.question.Id] }]
const markInfo = []
const index = this.questionMarkInfoList.findIndex(
item => item.QuestionId === this.operateQuestionId
)
@@ -594,16 +595,21 @@ export default {
}
annotation = this.getAnnotationByRow(obj.question.Id, false)
//
let picturePath = ''
let picturePath = obj.picturePath
if (annotation) {
const base64Str = await this.getScreenshots({
visitTaskId: this.visitTaskId,
annotation
})
const pictureObj = await this.uploadScreenshots(`${Date.now()}`, base64Str)
picturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
if (!picturePath) {
const pictureObj = await this.uploadScreenshots(`${Date.now()}`, base64Str)
picturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
}
markInfo[0].PicturePath = picturePath
}
if (markInfo && markInfo.length > 0) {
markInfo[0].MarkId = annotation ? annotation.annotationUID : null
}
await saveTaskQuestion(-10, {
visitTaskId: this.visitTaskId,
answers,
@@ -612,8 +618,20 @@ export default {
this.$set(this.questionsMarkStatus, obj.question.Id, {
isMarked: !!this.questionMarkInfoList[index]?.MeasureData,
isSaved: true,
OrderMarkName: this.questionMarkInfoList[index].OrderMarkName
OrderMarkName: index > -1 ? this.questionMarkInfoList[index].OrderMarkName : ''
})
this.resetOperateState()
if (annotation) {
for (let i = 0; i < this.questionMarkInfoList.length; i++) {
const item = this.questionMarkInfoList[i]
if (!item.MeasureData || item.MeasureData.annotationUID !== annotation.annotationUID) continue
const isTableMode = item.TableQuestionId
if (!isTableMode && this.questionsMarkStatus[item.QuestionId] && !this.questionsMarkStatus[item.QuestionId].isSaved && this.questionsMarkStatus[item.QuestionId].isMarked) {
DicomEvent.$emit('operateImageMarker', { QuestionId: item.QuestionId, operateStateEnum: 5, picturePath })
break
}
}
}
} catch (e) {
console.error('保存失败:', e)
} finally {
@@ -639,8 +657,8 @@ export default {
}
annotation = this.getAnnotationByRow(this.operateRowId)
//
let picturePath = ''
if (annotation) {
let picturePath = obj.picturePath
if (annotation && !picturePath) {
const base64Str = await this.getScreenshots({
visitTaskId: this.visitTaskId,
annotation
@@ -665,13 +683,25 @@ export default {
QuestionType: 0,
RowId: this.operateRowId,
TableQuestionId: this.operateQuestionId,
RowIndex: obj.rowIndex
RowIndex: obj.rowIndex,
MarkId: annotation ? annotation.annotationUID : null
}, -10)
this.$set(this.questionsMarkStatus,
`${this.operateRowId}_${this.operateQuestionId}`,
{ isMarked: !!annotation, isSaved: true, OrderMarkName: annotation.data.label }
{ isMarked: !!annotation, isSaved: true, OrderMarkName: annotation ? annotation.data.label : '' }
)
this.resetOperateState()
if (annotation) {
for (let i = 0; i < this.questionMarkInfoList.length; i++) {
const item = this.questionMarkInfoList[i]
if (!item.MeasureData || item.MeasureData.annotationUID !== annotation.annotationUID) continue
const isTableMode = item.TableQuestionId
if (isTableMode && ((this.questionsMarkStatus[`${item.RowId}_${item.TableQuestionId}`] && !this.questionsMarkStatus[`${item.RowId}_${item.TableQuestionId}`].isSaved && this.questionsMarkStatus[`${item.RowId}_${item.TableQuestionId}`].isMarked) || (this.questionsMarkStatus[`${item.TableQuestionId}`] && !this.questionsMarkStatus[`${item.TableQuestionId}`].isSaved && this.questionsMarkStatus[`${item.TableQuestionId}`].isMarked))) {
DicomEvent.$emit('operateImageMarkerTable', { QuestionId: item.TableQuestionId, operateStateEnum: 7, picturePath })
break
}
}
}
} catch (e) {
console.error('表格保存失败:', e)
} finally {
@@ -729,32 +759,45 @@ export default {
return this.handleNullState(annotation)
}
if (!this.operateQuestionId) return
if ([0, 3].includes(currentState)) {
if ([0, 1, 3].includes(currentState)) {
//
if (!annotation.data.label || annotation.markTool !== this.imageTool) {
let message = this.$t("dicom3D:CustomizeQuestionList:alert:markNoSaveToQuestion").replace("xxx", this.$t(`dicom3D:mark:${this.imageTool}`))
this.$alert(message)
return
}
if (currentState === 3) {
const conflictIndex = this.questionMarkInfoList.findIndex(
item => item.MeasureData.annotationUID === annotation.annotationUID
&& item.RowId !== this.operateRowId && this.operateRowId
)
if (conflictIndex > -1) {
//
this.$alert(this.$t("dicom3D:CustomizeQuestionList:alert:markIsbind"))
return
}
// if (currentState === 3) {
let conflictIndex = this.questionMarkInfoList.findIndex(
item => item.MeasureData.annotationUID === annotation.annotationUID
&& item.RowId !== this.operateRowId && item.RowId
)
// && this.operateRowId
console.log(conflictIndex)
if (conflictIndex > -1) {
//
this.$alert(this.$t("dicom3D:CustomizeQuestionList:alert:markIsbind"))
return
}
conflictIndex = this.questionMarkInfoList.findIndex(
item => item.MeasureData.annotationUID === annotation.annotationUID
&& !item.TableQuestionId && this.isTableQuestion
)
if (conflictIndex > -1) {
//
this.$alert(this.$t("dicom3D:CustomizeQuestionList:alert:markIsbind2"))
return
}
// }
//
const message = currentState === 0 ? this.$t("dicom3D:CustomizeQuestionList:confirm:bindYesOrNo") : this.$t("dicom3D:CustomizeQuestionList:confirm:updateYesOrNo")
const result = await this.$confirm(message, {
type: 'warning',
distinguishCancelAndClose: true
})
const isConfirmed = result === 'confirm'
if (!isConfirmed) return
const message = currentState === 0 ? this.$t("dicom3D:CustomizeQuestionList:confirm:bindYesOrNo") : currentState === 3 ? this.$t("dicom3D:CustomizeQuestionList:confirm:updateYesOrNo") : null
if (message) {
const result = await this.$confirm(message, {
type: 'warning',
distinguishCancelAndClose: true
})
const isConfirmed = result === 'confirm'
if (!isConfirmed) return
}
}
const targetIndex = this.findTargetIndex()
if (targetIndex === -1) {
@@ -885,6 +928,9 @@ export default {
qsArr.push({ ...item })
const keyId = item.RowId ? `${item.RowId}_${item.TableQuestionId}` : item.TableQuestionId;
this.$set(this.questionsMarkStatus, keyId, questionMarkInfo);
if (item.RowId) {
DicomEvent.$emit('openAddTableCol', { RowId: item.RowId })
}
}
if (qsArr.length > 0) {
store.dispatch('dicom3d/setOperateInfo', qsArr);
@@ -926,7 +972,7 @@ export default {
if ((TableQuestionId && QuestionId === questionId && markTableQuestions.includes(TableQuestionId)) && !RowId) {
if (questionsMarkStatus && questionsMarkStatus[TableQuestionId]) {
delete questionsMarkStatus[TableQuestionId]
}else {
} else {
return item
}
} else {
@@ -946,7 +992,7 @@ export default {
const trialId = this.$route.query.trialId
const taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -11,7 +11,8 @@
validator: question.Type === 'number' && !question.TypeValue ? validatorNumberInput : (rule, value, callback) => { callback() },
trigger: ['blur', 'change']
}
]" :class="[question.Type === 'group' ? 'mb' : question.Type === 'upload' ? 'uploadWrapper' : '']">
]"
:class="[question.Type === 'group' ? 'mb' : question.Type === 'upload' || question.Type === 'screenshot' ? 'uploadWrapper' : '']">
<!-- 输入框 -->
<el-input v-if="question.Type === 'input'" v-model="questionForm[question.Id]"
:disabled="question.TableQuestionType === 2 || (question.IsCopy && type === 'edit' && !isBaseline && questionForm.IsCurrentTaskAdd === 'False')" />
@@ -80,12 +81,13 @@
<!-- 数值 -->
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;">
<el-input type="number" @change="(val) => { formItemNumberChange(val, question) }"
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
@input="numberInput(question.Id)"
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
v-model="questionForm[question.Id]"
:title="questionsMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? questionsMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].OrderMarkName : ''"
:title="questionsMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? questionsMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].OrderMarkName : question.Remark"
:disabled="(questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1"
style="width: 150px;">
style="width: 150px;margin-right: 5px;">
<template v-if="question.Unit !== 0" slot="append">
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
</template>
@@ -156,45 +158,25 @@
</el-input>
</template>
<!-- 上传图像 -->
<el-upload v-if="question.Type === 'upload'" action :accept="question.FileType"
:limit="question.ImageCount === 0 ? 100 : question.ImageCount" :on-preview="handlePictureCardPreview"
:before-upload="handleBeforeUpload" :http-request="uploadScreenshot" list-type="picture-card"
:on-remove="handleRemove" :file-list="fileList"
<el-upload v-if="question.Type === 'upload' || question.Type === 'screenshot'" :disabled="readingTaskState === 2"
action :accept="question.FileType" :limit="question.ImageCount === 0 ? 100 : question.ImageCount"
:on-preview="handlePictureCardPreview"
:before-upload="(file) => { return handleBeforeUpload(file, question.FileType, question.Type) }"
:http-request="uploadScreenshot" :on-remove="handleRemove" :file-list="fileList"
:class="{ disabled: question.ImageCount === 0 ? false : fileList.length >= question.ImageCount }">
<i slot="default" class="el-icon-plus" />
<div slot="file" slot-scope="{file}">
<viewer :ref="file.url" :images="[imageUrl]" style="
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
">
<img class="el-upload-list__item-thumbnail" :src="OSSclientConfig.basePath + file.url"
crossOrigin="anonymous" alt="" style="max-width: 100%; max-height: 100%" />
<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>
</viewer>
</div>
<el-button slot="default" class="el-icon-plus" v-if="readingTaskState < 2">
{{ this.$t('common:button:upload') }}
</el-button>
</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">
{{ $t('trials:readingUnit:qsList:message:loading') }}<span class="dot">...</span>
</div>
</el-image>
</el-dialog>
<viewer v-if="(question.Type === 'upload' || question.Type === 'screenshot') && imgVisible" :ref="imageUrl"
style="margin:0 10px;" :images="[imageUrl]">
<img v-show="false" crossorigin="anonymous" :src="imageUrl" alt="Image">
</viewer>
</el-form-item>
</div>
</template>
<script>
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
export default {
name: 'QuestionTableFormItem',
props: {
@@ -294,18 +276,25 @@ export default {
}
},
mounted() {
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
if (this.question.Type === 'upload') {
if (this.question.Type === 'upload' || this.question.Type === 'screenshot') {
if (this.questionForm[this.question.Id]) {
this.urls = this.questionForm[this.question.Id].split('|')
this.fileList = []
this.urls.map(url => {
this.fileList.push({ name: '', url: `${url}` })
this.urls.map((url, index) => {
var index = url.lastIndexOf('.')
var suffix = url.substring(index + 1, url.length)
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${index + 1}(${suffix})`, url: `${url}` })
})
}
}
DicomEvent.$on('operateImageMarkerTable', (data) => {
let { operateStateEnum, QuestionId, picturePath } = data
if (this.question.Id === QuestionId) {
this.operateImageMarker({ operateStateEnum, question: this.question, picturePath })
}
})
if (this.type === 'edit') return
},
methods: {
@@ -319,7 +308,7 @@ export default {
validatorNumberInput(rule, value, callback) {
let reg = new RegExp(/^(?:-?(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)|NE)$/, 'g')
if (value === '') {
callback(new Error(this.$t('common:ruleMessage:specify')));
callback();
} else {
if (!reg.test(value)) {
callback(new Error(this.$t('trials:reading:ruleMessage:validatorNumberInput')));
@@ -355,6 +344,7 @@ export default {
} else {
this.$emit('setFormItemData', { key: question.Id, val: v, question: question })
}
this.$emit('formItemTableNumberChange', v, question)
},
handleBlur(value, a, b) {
if (!value) return false
@@ -363,7 +353,7 @@ export default {
}
},
handleMarkedQsBlur(value, a, b, question) {
this.$set(a, b, parseFloat(value).toFixed(this.digitPlaces))
this.$set(a, b, !isNaN(parseFloat(value)) ? parseFloat(value).toFixed(this.digitPlaces) : value)
question.IsTableQuestion = true
// this.$emit('operateImageMarker', {operateStateEnum: 6, question})
},
@@ -529,6 +519,7 @@ export default {
obj.question.IsTableQuestion = true
obj.question.RowId = this.rowId
obj.question.ParentQsId = this.parentQsId
if (obj.operateStateEnum === 7 && this.questionForm[obj.question.Id] && isNaN(parseFloat(this.questionForm[obj.question.Id])) && this.questionForm[obj.question.Id] !== 'NE') return false
if (obj.operateStateEnum === 7) {
//
// if (!obj.question.RowId) {
@@ -567,32 +558,36 @@ export default {
text: 'Loading',
spinner: 'el-icon-loading'
})
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
var fileName = param.file.name
var index = fileName.lastIndexOf('.')
var suffix = fileName.substring(index + 1, fileName.length)
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${param.file.name}`, file)
this.fileList.push({ name: param.file.name, path: this.$getObjectName(res.url), url: this.$getObjectName(res.url) })
// let res = await this.OSSclient.put(`/${this.$route.query.trialId}/Customize/${this.visitTaskId}/${fileName}`, file)
const taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
let res = await this.OSSclient.put(`/${this.$route.query.trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}`, file)
this.fileList.push({ name: `${this.$t('trials:emailManageCfg:title:fileName')}${this.fileList.length + 1}(${suffix})`, 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 })
loading.close()
},
handleBeforeUpload(file) {
handleBeforeUpload(file, accept, type) {
if (type === 'screenshot') {
accept = '.png,.jpeg,.jpg'
}
//
if (this.checkFileSuffix(file.name)) {
// this.fileList = []
if (this.checkFileSuffix(file.name, accept) || accept === '-1') {
return true
} else {
let msg = this.$t(
"trials:readingUnit:qsList:message:imageFormat"
).replace("xxx", this.question.FileType)
const msg = this.$t('trials:adReview:title:msg4').replace('xxx', accept)
this.$alert(msg)
return false
}
},
checkFileSuffix(fileName) {
checkFileSuffix(fileName, accept) {
var index = fileName.lastIndexOf('.')
var suffix = fileName.substring(index + 1, fileName.length)
if (this.question.FileType.toLocaleLowerCase().search(suffix.toLocaleLowerCase()) === -1) {
if (accept.toLocaleLowerCase().search(suffix.toLocaleLowerCase()) === -1) {
return false
} else {
return true
@@ -602,21 +597,46 @@ export default {
handlePictureCardPreview(file) {
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')
if (['ppt',
'pptx',
'doc',
'docx',
'xls',
'xlsx'].includes(suffix)) {
// window.open(this.OSSclientConfig.basePath + file.url,'_blank')
this.$onlyOffice({
path: file.url,
type: suffix,
title: file.name
})
// this.currentPath = file.url
// this.currentType = suffix
// this.previewVisible = true
} else if (['pdf'].includes(suffix)) {
this.$preview({
path: file.url,
type: 'pdf',
title: file.name,
})
} else {
this.imageUrl = this.OSSclientConfig.basePath + file.url
// this.imgVisible = true
this.$refs[file.url].$viewer.show()
this.imgVisible = true
this.$nextTick(() => {
this.$refs[this.imageUrl].$viewer.show()
})
}
},
//
handleRemove(file, fileList) {
this.imageUrl = ''
this.fileList.splice(this.fileList.findIndex(f => f.url === file.url), 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 })
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 })
}
}
}
}
@@ -275,7 +275,7 @@ export default {
}
},
async mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.trialId = this.$route.query.trialId
this.visitTaskId = this.taskInfo.VisitTaskId
this.subjectId = this.taskInfo.SubjectId
@@ -263,7 +263,7 @@ export default {
}
},
mounted() {
let digitPlaces = Number(localStorage.getItem('digitPlaces'))
let digitPlaces = Number(sessionStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
},
methods: {
@@ -276,7 +276,7 @@ export default {
mounted() {
this.trialId = this.$route.query.trialId
this.visitTaskId = this.visitInfo.VisitTaskId
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
this.isBaseLineTask = this.taskInfo.IsBaseLine
this.criterionType = this.taskInfo.CriterionType
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
@@ -1428,7 +1428,7 @@ export default {
file = this.convertBase64ToBlob(file)
const trialId = this.$route.query.trialId
const subjectId = this.taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${fileName}.png`, file)
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
@@ -292,8 +292,82 @@ const config = {
'i18nKey': 'trials:reading:button:CobbAngle',
'isDisabled': false,
'disabledReason': ''
}
]
},
{
'name': '自由曲线',
'icon': 'polygon',
'toolName': 'PlanarFreehandROI',
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
'i18nKey': 'trials:reading:button:planarFreehandROI',
'isDisabled': false,
'disabledReason': ''
},
],
'customizeStandardsNoneDicom': [
{
'name': '直径测量工具',
'icon': 'length',
'toolName': 'Length',
'props': ['length'],
'i18nKey': 'trials:reading:button:length',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '矩形工具',
'icon': 'rectangle',
'toolName': 'RectangleROI',
'props': ['area', 'mean', 'max', 'stdDev'],
'i18nKey': 'trials:reading:button:rectangle',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '箭头工具',
'icon': 'arrow',
'toolName': 'ArrowAnnotate',
'props': [],
'i18nKey': 'trials:reading:button:arrowAnnotate',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '圆形工具',
'icon': 'oval',
'toolName': 'CircleROI',
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
'i18nKey': 'trials:reading:button:Circle',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '椭圆工具',
'icon': 'elliptical',
'toolName': 'EllipticalROI',
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
'i18nKey': 'trials:reading:button:Elliptical',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '角度工具',
'icon': 'angle',
'toolName': 'Angle',
'props': ['angle'],
'i18nKey': 'trials:reading:button:Angle',
'isDisabled': false,
'disabledReason': ''
},
{
'name': '自由曲线',
'icon': 'polygon',
'toolName': 'PlanarFreehandROI',
'props': [],
'i18nKey': 'trials:reading:button:planarFreehandROI',
'isDisabled': false,
'disabledReason': ''
},
],
}
const getTools = (criterionType) => {
const standard = config.standards.find(s => s.type === criterionType)
@@ -303,4 +377,8 @@ const getCustomizeStandardsTools = (toolNames) => {
const filteredTools = config.customizeStandards.filter(item => toolNames.includes(item.toolName))
return filteredTools || []
}
export { config, getTools, getCustomizeStandardsTools }
const getCustomizeStandardsNoneDicomTools = (toolNames) => {
const filteredTools = config.customizeStandardsNoneDicom.filter(item => toolNames.includes(item.toolName))
return filteredTools || []
}
export { config, getTools, getCustomizeStandardsTools, getCustomizeStandardsNoneDicomTools }
@@ -70,6 +70,7 @@ export default {
}
const res = await getNextTask(params)
this.taskInfo = res.Result
sessionStorage.setItem('taskInfo', JSON.stringify(res.Result))
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
localStorage.setItem('digitPlaces', JSON.stringify(res.Result.DigitPlaces))
this.loading = false
@@ -318,7 +318,7 @@
:disabled="!scope.row.IsHaveQuestion || scope.row.IsInvalid"
circle
:title="$t('trials:medicalFeedback:table:view')"
icon="el-icon-edit-outline"
icon="el-icon-view"
@click="handleReadMecialAudit(scope.row)"
/>
<!-- 回复:disabled="!scope.row.IsHaveQuestion || scope.row.IsInvalid" -->
@@ -285,6 +285,7 @@ export default {
async handleSubmit() {
const valid = await this.$refs['questions'].validate()
if (!valid) return
await this.handleSave()
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
@@ -119,7 +119,7 @@ name: "UploadFile",
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${param.file.name}`, file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${param.file.name}`, file)
console.log(res)
this.fileList.push({ name: param.file.name, path: this.$getObjectName(res.url), url: this.$getObjectName(res.url)})
this.urls.push(this.$getObjectName(res.url))
@@ -1,59 +1,39 @@
<template>
<div v-loading="loading" class="reading-viewer-container">
<!-- 访视阅片 -->
<visit-review v-if="taskInfo && taskInfo.ReadingCategory=== 1" />
<visit-review v-if="taskInfo && taskInfo.ReadingCategory === 1" />
<!-- 全局阅片 -->
<global-review
v-else-if="taskInfo && taskInfo.ReadingCategory=== 2"
:trial-id="trialId"
:subject-id="taskInfo.SubjectId"
:visit-task-id="taskInfo.VisitTaskId"
:reading-category="taskInfo.ReadingCategory"
:subject-code="taskInfo.SubjectCode"
:task-blind-name="taskInfo.TaskBlindName"
:is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
<global-review v-else-if="taskInfo && taskInfo.ReadingCategory === 2" :trial-id="trialId"
:subject-id="taskInfo.SubjectId" :visit-task-id="taskInfo.VisitTaskId"
:reading-category="taskInfo.ReadingCategory" :subject-code="taskInfo.SubjectCode"
:task-blind-name="taskInfo.TaskBlindName" :is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
:is-reading-show-previous-results="taskInfo.IsReadingShowPreviousResults"
:is-exists-clinical-data="taskInfo.IsExistsClinicalData"
/>
:is-exists-clinical-data="taskInfo.IsExistsClinicalData" />
<!-- 裁判阅片 -->
<ad-review
v-else-if="taskInfo && taskInfo.ReadingCategory=== 4"
:trial-id="trialId"
:subject-id="taskInfo.SubjectId"
:visit-task-id="taskInfo.VisitTaskId"
:reading-category="taskInfo.ReadingCategory"
:subject-code="taskInfo.SubjectCode"
:task-blind-name="taskInfo.TaskBlindName"
:is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
<ad-review v-else-if="taskInfo && taskInfo.ReadingCategory === 4" :trial-id="trialId"
:subject-id="taskInfo.SubjectId" :visit-task-id="taskInfo.VisitTaskId"
:reading-category="taskInfo.ReadingCategory" :subject-code="taskInfo.SubjectCode"
:task-blind-name="taskInfo.TaskBlindName" :is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
:is-reading-show-previous-results="taskInfo.IsReadingShowPreviousResults"
:is-exists-clinical-data="taskInfo.IsExistsClinicalData"
/>
:is-exists-clinical-data="taskInfo.IsExistsClinicalData" />
<!-- 肿瘤学阅片 -->
<!-- <oncology-review v-else-if="taskInfo && taskInfo.ReadingCategory=== 5" /> -->
<el-dialog
:visible.sync="clinicalDataVisible"
:custom-class="isClinicalDataFullscreen?'full-dialog-container':'dialog-container'"
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isClinicalDataFullscreen"
>
<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" />
<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"
/>
<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>
@@ -65,6 +45,7 @@
<script>
import { getNextTask, readClinicalData } from '@/api/trials'
import store from '@/store'
import { changeURLStatic } from '@/utils/history.js'
import VisitReview from '@/views/trials/trials-panel/reading/visit-review'
import GlobalReview from '@/views/trials/trials-panel/reading/global-review'
import AdReview from '@/views/trials/trials-panel/reading/ad-review'
@@ -92,6 +73,10 @@ export default {
},
mounted() {
this.trialId = this.$route.query.trialId
if (this.$router.currentRoute.query.TokenKey) {
store.dispatch('user/setToken', this.$router.currentRoute.query.TokenKey)
changeURLStatic('TokenKey', '')
}
this.getTaskInfo()
},
methods: {
@@ -108,6 +93,7 @@ export default {
const res = await getNextTask(params)
this.taskInfo = res.Result
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
sessionStorage.setItem('taskInfo', JSON.stringify(res.Result))
localStorage.setItem('digitPlaces', JSON.stringify(res.Result.DigitPlaces))
this.loading = false
this.$nextTick(() => {
@@ -142,20 +128,24 @@ export default {
.reading-viewer-container {
width: 100%;
height: 100%;
::v-deep .dialog-container{
::v-deep .dialog-container {
margin-top: 50px !important;
width:75%;
height:80%;
width: 75%;
height: 80%;
}
::v-deep .el-dialog__body{
padding: 10px;
height: calc(100% - 70px);
}
.el-dialog__header{
::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{
.full-dialog-container {
::v-deep .is-fullscreen .el-dialog__body {
height: calc(100% - 70px);
}
}
@@ -130,18 +130,9 @@
<el-table-column prop="OriginalReReadingTask.SubjectCode" :label="$t('trials:rereadTrack:table:subjectCode')"
min-width="120" sortable="custom" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="
scope.row.OriginalReReadingTask.MedicalNo &&
scope.row.OriginalReReadingTask.MedicalNo !==
scope.row.OriginalReReadingTask.SubjectCode
">
{{
`${scope.row.OriginalReReadingTask.SubjectCode}/${scope.row.OriginalReReadingTask.MedicalNo}`
}}
<span>
{{ getSubjectCode(scope.row.OriginalReReadingTask) }}
</span>
<span v-else>{{
scope.row.OriginalReReadingTask.SubjectCode
}}</span>
</template>
</el-table-column>
<!-- 任务名称 -->
@@ -385,7 +376,8 @@
</el-input>
</el-form-item>
<el-form-item :label="$t('trials:reviewTrack:dialog:backImpactList')" />
<el-table v-loading="loading" :data="InfluenceTaskList" stripe height="100" style="min-height:200px;max-height: 300px">
<el-table v-loading="loading" :data="InfluenceTaskList" stripe height="100"
style="min-height:200px;max-height: 300px">
<!-- 任务编号 -->
<el-table-column prop="TaskCode" :label="$t('trials:reviewTrack:table:taskCode')" min-width="100"
show-overflow-tooltip />
@@ -873,6 +865,16 @@ export default {
this.getTrialCriterionList()
},
methods: {
getSubjectCode(obj) {
let str = obj.SubjectCode
if (obj.MedicalNo && obj.MedicalNo !== obj.SubjectCode) {
str += `/${obj.MedicalNo}`
}
if (obj.BlindSubjectCode && obj.BlindSubjectCode !== obj.SubjectCode) {
str += `/${obj.BlindSubjectCode}`
}
return str
},
async auditImageBack() {
try {
console.log(this.form)
@@ -45,7 +45,7 @@
<el-input
v-model="scope.row.CDISCCode"
size="mini"
:disabled="scope.row.HasChildren || (scope.row.IsTableQuestion && exportInfo.CriterionType !== 0)"
:disabled="scope.row.HasChildren || (scope.row.IsTableQuestion && exportInfo.CriterionType !== 0 && exportInfo.CriterionGroup !== 0)"
v-show="!scope.row.IsGroup || !scope.row.HasChildren"
>
</el-input>
@@ -152,7 +152,7 @@ export default {
} else if ((this.exportInfo.CriterionGroup === 0 || this.exportInfo.CriterionType === 0 ) && item.Code === 1 && (row.HasChildren || row.IsTableQuestion)) {
// (1)
return true
} else if (item.Code === 8 && (row.HasChildren || (row.IsTableQuestion && this.exportInfo.CriterionType !== 0))) {
} else if ( item.Code === 8 && (row.HasChildren || (row.IsTableQuestion && this.exportInfo.CriterionType !== 0 && this.exportInfo.CriterionGroup !== 0) )) {
// else if (item.Code === 8 && (row.HasChildren || row.IsTableQuestion))
// CDISC
return true
@@ -340,7 +340,7 @@
<el-table-column fixed="right" :label="$t('common:action:action')" width="220">
<template slot-scope="scope">
<!-- 查看阅片结果 -->
<el-button :disabled="scope.row.ReadingTaskState !== 2 && scope.row.IsSubjectQuit" icon="el-icon-view"
<el-button :disabled="scope.row.ReadingTaskState !== 2" icon="el-icon-view"
circle :title="$t('trials:reviewTrack:action:viewResults')" @click="lookReadingResults(scope.row)" />
<!-- 申请重阅 -->
<el-button :disabled="scope.row.ReadingCategory !== 1 ||
@@ -120,7 +120,7 @@ name: "CustomizeReportPageUpload",
var trialId = this.$route.query.trialId
var subjectId = this.$route.query.trialId
var file = await this.fileToBlob(param.file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/Visit/${param.file.name}`, file)
const res = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.visitTaskId}/${param.file.name}`, file)
console.log(res)
this.fileList.push({ name: param.file.name, path: this.$getObjectName(res.url), url: this.$getObjectName(res.url)})
this.urls.push(this.$getObjectName(res.url))

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